diff options
author | roberto <roberto@FreeBSD.org> | 2008-08-17 17:37:33 +0000 |
---|---|---|
committer | roberto <roberto@FreeBSD.org> | 2008-08-17 17:37:33 +0000 |
commit | 4ded1c1fa0bc21c61f91a2dbe864835986745121 (patch) | |
tree | 16d100fbc9dae63888d48b464e471ba0e5065193 /ntpd | |
parent | 8b5a86d4fda08a9c68231415812edcb26be52f79 (diff) | |
download | FreeBSD-src-4ded1c1fa0bc21c61f91a2dbe864835986745121.zip FreeBSD-src-4ded1c1fa0bc21c61f91a2dbe864835986745121.tar.gz |
Flatten the dist and various 4.n.n trees in preparation of future ntp imports.
Diffstat (limited to 'ntpd')
67 files changed, 70079 insertions, 0 deletions
diff --git a/ntpd/Makefile.am b/ntpd/Makefile.am new file mode 100644 index 0000000..0fa4e21 --- /dev/null +++ b/ntpd/Makefile.am @@ -0,0 +1,60 @@ +#AUTOMAKE_OPTIONS = ../util/ansi2knr no-dependencies +AUTOMAKE_OPTIONS = ../util/ansi2knr +bin_PROGRAMS = ntpd @MAKE_NTPDSIM@ +noinst_LIBRARIES = libntpd.a +INCLUDES = -I$(top_srcdir)/include -I../include +# LDADD might need RESLIB and ADJLIB. +# If LIBPARSE, we need libntpd.a 2wagain afterwards... +LDADD = version.o libntpd.a @LIBPARSE@ libntpd.a +# ntpd may need: +# log10 refclock_wwv.o +# sqrt ntp_control.o +# floor refclock_wwv.o +# which are (usually) provided by -lm. +ntpd_LDADD = $(LDADD) ../libntp/libntp.a -lm @LCRYPTO@ +ntpdsim_LDADD = $(LDADD) ../libntp/libntpsim.a -lm @LCRYPTO@ +ntpdsim_CFLAGS = $(CFLAGS) -DSIM +check_y2k_LDADD = $(LDADD) ../libntp/libntp.a +DISTCLEANFILES = .version version.c +#EXTRA_DIST = ntpd.mak +ETAGS_ARGS = Makefile.am +### Y2Kfixes +check_PROGRAMS = @MAKE_CHECK_Y2K@ +EXTRA_PROGRAMS = check_y2k ntpdsim + +check-local: @MAKE_CHECK_Y2K@ + test -z "@MAKE_CHECK_Y2K@" || ./@MAKE_CHECK_Y2K@ + +# SIM: cmd_args.c ntp_config.c ntp_io.c ntpd.c + ntpsim.c (include/ntpsim.h) +# ntp_resolver.c is presently unused... +ntpd_SOURCES = cmd_args.c ntp_config.c ntp_io.c ntpd.c +ntpdsim_SOURCES = $(ntpd_SOURCES) ntpsim.c +libntpd_a_SOURCES = jupiter.h map_vme.c ntp_control.c \ + ntp_crypto.c ntp_filegen.c \ + ntp_intres.c ntp_loopfilter.c ntp_monitor.c ntp_peer.c \ + ntp_proto.c ntp_refclock.c ntp_request.c \ + ntp_restrict.c ntp_timer.c ntp_util.c \ + refclock_acts.c refclock_arbiter.c refclock_arc.c refclock_as2201.c \ + refclock_atom.c refclock_bancomm.c refclock_chronolog.c \ + refclock_chu.c refclock_conf.c refclock_datum.c refclock_dumbclock.c \ + refclock_fg.c refclock_gpsvme.c refclock_heath.c refclock_hopfser.c \ + refclock_hopfpci.c refclock_hpgps.c refclock_irig.c refclock_jjy.c \ + refclock_jupiter.c refclock_leitch.c refclock_local.c \ + refclock_msfees.c refclock_mx4200.c refclock_nmea.c refclock_oncore.c \ + refclock_palisade.c refclock_palisade.h refclock_parse.c \ + refclock_pcf.c refclock_pst.c refclock_ptbacts.c refclock_shm.c \ + refclock_tpro.c refclock_trak.c refclock_true.c refclock_tt560.c \ + refclock_ulink.c refclock_usno.c refclock_wwv.c refclock_wwvb.c \ + refclock_zyfer.c refclock_ripencc.c refclock_neoclock4x.c + +$(PROGRAMS): $(LDADD) + +../libntp/libntp.a: + cd ../libntp && $(MAKE) + +../libparse/libparse.a: + cd ../libparse && $(MAKE) + +version.o: $(ntpd_OBJECTS) ../libntp/libntp.a @LIBPARSE@ Makefile $(top_srcdir)/version + env CSET=`cat $(top_srcdir)/version` $(top_builddir)/scripts/mkver ntpd + $(COMPILE) -c version.c diff --git a/ntpd/Makefile.in b/ntpd/Makefile.in new file mode 100644 index 0000000..eef0774 --- /dev/null +++ b/ntpd/Makefile.in @@ -0,0 +1,960 @@ +# Makefile.in generated by automake 1.7.7 from Makefile.am. +# @configure_input@ + +# Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 +# Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. + +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +ARLIB_DIR = @ARLIB_DIR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CHUTEST = @CHUTEST@ +CLKTEST = @CLKTEST@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DCFD = @DCFD@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EF_LIBS = @EF_LIBS@ +EF_PROGS = @EF_PROGS@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCRYPTO = @LCRYPTO@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBPARSE = @LIBPARSE@ +LIBS = @LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MAKE_ADJTIMED = @MAKE_ADJTIMED@ +MAKE_CHECK_Y2K = @MAKE_CHECK_Y2K@ +MAKE_LIBNTPSIM = @MAKE_LIBNTPSIM@ +MAKE_LIBPARSE = @MAKE_LIBPARSE@ +MAKE_LIBPARSE_KERNEL = @MAKE_LIBPARSE_KERNEL@ +MAKE_NTPDSIM = @MAKE_NTPDSIM@ +MAKE_NTPTIME = @MAKE_NTPTIME@ +MAKE_NTP_KEYGEN = @MAKE_NTP_KEYGEN@ +MAKE_PARSEKMODULE = @MAKE_PARSEKMODULE@ +MAKE_SNTP = @MAKE_SNTP@ +MAKE_TICKADJ = @MAKE_TICKADJ@ +MAKE_TIMETRIM = @MAKE_TIMETRIM@ +OBJEXT = @OBJEXT@ +OPENSSL = @OPENSSL@ +OPENSSL_INC = @OPENSSL_INC@ +OPENSSL_LIB = @OPENSSL_LIB@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_PERL = @PATH_PERL@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PATH_SH = @PATH_SH@ +PROPDELAY = @PROPDELAY@ +RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +TESTDCF = @TESTDCF@ +U = @U@ +VERSION = @VERSION@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +oldincludedir = @oldincludedir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +subdirs = @subdirs@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ + +#AUTOMAKE_OPTIONS = ../util/ansi2knr no-dependencies +AUTOMAKE_OPTIONS = ../util/ansi2knr +bin_PROGRAMS = ntpd @MAKE_NTPDSIM@ +noinst_LIBRARIES = libntpd.a +INCLUDES = -I$(top_srcdir)/include -I../include +# LDADD might need RESLIB and ADJLIB. +# If LIBPARSE, we need libntpd.a 2wagain afterwards... +LDADD = version.o libntpd.a @LIBPARSE@ libntpd.a +# ntpd may need: +# log10 refclock_wwv.o +# sqrt ntp_control.o +# floor refclock_wwv.o +# which are (usually) provided by -lm. +ntpd_LDADD = $(LDADD) ../libntp/libntp.a -lm @LCRYPTO@ +ntpdsim_LDADD = $(LDADD) ../libntp/libntpsim.a -lm @LCRYPTO@ +ntpdsim_CFLAGS = $(CFLAGS) -DSIM +check_y2k_LDADD = $(LDADD) ../libntp/libntp.a +DISTCLEANFILES = .version version.c +#EXTRA_DIST = ntpd.mak +ETAGS_ARGS = Makefile.am +### Y2Kfixes +check_PROGRAMS = @MAKE_CHECK_Y2K@ +EXTRA_PROGRAMS = check_y2k ntpdsim + +# SIM: cmd_args.c ntp_config.c ntp_io.c ntpd.c + ntpsim.c (include/ntpsim.h) +# ntp_resolver.c is presently unused... +ntpd_SOURCES = cmd_args.c ntp_config.c ntp_io.c ntpd.c +ntpdsim_SOURCES = $(ntpd_SOURCES) ntpsim.c +libntpd_a_SOURCES = jupiter.h map_vme.c ntp_control.c \ + ntp_crypto.c ntp_filegen.c \ + ntp_intres.c ntp_loopfilter.c ntp_monitor.c ntp_peer.c \ + ntp_proto.c ntp_refclock.c ntp_request.c \ + ntp_restrict.c ntp_timer.c ntp_util.c \ + refclock_acts.c refclock_arbiter.c refclock_arc.c refclock_as2201.c \ + refclock_atom.c refclock_bancomm.c refclock_chronolog.c \ + refclock_chu.c refclock_conf.c refclock_datum.c refclock_dumbclock.c \ + refclock_fg.c refclock_gpsvme.c refclock_heath.c refclock_hopfser.c \ + refclock_hopfpci.c refclock_hpgps.c refclock_irig.c refclock_jjy.c \ + refclock_jupiter.c refclock_leitch.c refclock_local.c \ + refclock_msfees.c refclock_mx4200.c refclock_nmea.c refclock_oncore.c \ + refclock_palisade.c refclock_palisade.h refclock_parse.c \ + refclock_pcf.c refclock_pst.c refclock_ptbacts.c refclock_shm.c \ + refclock_tpro.c refclock_trak.c refclock_true.c refclock_tt560.c \ + refclock_ulink.c refclock_usno.c refclock_wwv.c refclock_wwvb.c \ + refclock_zyfer.c refclock_ripencc.c refclock_neoclock4x.c + +subdir = ntpd +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +LIBRARIES = $(noinst_LIBRARIES) + +libntpd_a_AR = $(AR) cru +libntpd_a_LIBADD = +am_libntpd_a_OBJECTS = map_vme$U.$(OBJEXT) ntp_control$U.$(OBJEXT) \ + ntp_crypto$U.$(OBJEXT) ntp_filegen$U.$(OBJEXT) \ + ntp_intres$U.$(OBJEXT) ntp_loopfilter$U.$(OBJEXT) \ + ntp_monitor$U.$(OBJEXT) ntp_peer$U.$(OBJEXT) \ + ntp_proto$U.$(OBJEXT) ntp_refclock$U.$(OBJEXT) \ + ntp_request$U.$(OBJEXT) ntp_restrict$U.$(OBJEXT) \ + ntp_timer$U.$(OBJEXT) ntp_util$U.$(OBJEXT) \ + refclock_acts$U.$(OBJEXT) refclock_arbiter$U.$(OBJEXT) \ + refclock_arc$U.$(OBJEXT) refclock_as2201$U.$(OBJEXT) \ + refclock_atom$U.$(OBJEXT) refclock_bancomm$U.$(OBJEXT) \ + refclock_chronolog$U.$(OBJEXT) refclock_chu$U.$(OBJEXT) \ + refclock_conf$U.$(OBJEXT) refclock_datum$U.$(OBJEXT) \ + refclock_dumbclock$U.$(OBJEXT) refclock_fg$U.$(OBJEXT) \ + refclock_gpsvme$U.$(OBJEXT) refclock_heath$U.$(OBJEXT) \ + refclock_hopfser$U.$(OBJEXT) refclock_hopfpci$U.$(OBJEXT) \ + refclock_hpgps$U.$(OBJEXT) refclock_irig$U.$(OBJEXT) \ + refclock_jjy$U.$(OBJEXT) refclock_jupiter$U.$(OBJEXT) \ + refclock_leitch$U.$(OBJEXT) refclock_local$U.$(OBJEXT) \ + refclock_msfees$U.$(OBJEXT) refclock_mx4200$U.$(OBJEXT) \ + refclock_nmea$U.$(OBJEXT) refclock_oncore$U.$(OBJEXT) \ + refclock_palisade$U.$(OBJEXT) refclock_parse$U.$(OBJEXT) \ + refclock_pcf$U.$(OBJEXT) refclock_pst$U.$(OBJEXT) \ + refclock_ptbacts$U.$(OBJEXT) refclock_shm$U.$(OBJEXT) \ + refclock_tpro$U.$(OBJEXT) refclock_trak$U.$(OBJEXT) \ + refclock_true$U.$(OBJEXT) refclock_tt560$U.$(OBJEXT) \ + refclock_ulink$U.$(OBJEXT) refclock_usno$U.$(OBJEXT) \ + refclock_wwv$U.$(OBJEXT) refclock_wwvb$U.$(OBJEXT) \ + refclock_zyfer$U.$(OBJEXT) refclock_ripencc$U.$(OBJEXT) \ + refclock_neoclock4x$U.$(OBJEXT) +libntpd_a_OBJECTS = $(am_libntpd_a_OBJECTS) +EXTRA_PROGRAMS = check_y2k$(EXEEXT) ntpdsim$(EXEEXT) +bin_PROGRAMS = ntpd$(EXEEXT) @MAKE_NTPDSIM@ +check_PROGRAMS = @MAKE_CHECK_Y2K@ +PROGRAMS = $(bin_PROGRAMS) + +check_y2k_SOURCES = check_y2k.c +check_y2k_OBJECTS = check_y2k$U.$(OBJEXT) +check_y2k_DEPENDENCIES = version.o libntpd.a libntpd.a \ + ../libntp/libntp.a +check_y2k_LDFLAGS = +am_ntpd_OBJECTS = cmd_args$U.$(OBJEXT) ntp_config$U.$(OBJEXT) \ + ntp_io$U.$(OBJEXT) ntpd$U.$(OBJEXT) +ntpd_OBJECTS = $(am_ntpd_OBJECTS) +ntpd_DEPENDENCIES = version.o libntpd.a libntpd.a ../libntp/libntp.a +ntpd_LDFLAGS = +am__objects_1 = ntpdsim-cmd_args$U.$(OBJEXT) \ + ntpdsim-ntp_config$U.$(OBJEXT) ntpdsim-ntp_io$U.$(OBJEXT) \ + ntpdsim-ntpd$U.$(OBJEXT) +am_ntpdsim_OBJECTS = $(am__objects_1) ntpdsim-ntpsim$U.$(OBJEXT) +ntpdsim_OBJECTS = $(am_ntpdsim_OBJECTS) +ntpdsim_DEPENDENCIES = version.o libntpd.a libntpd.a \ + ../libntp/libntpsim.a +ntpdsim_LDFLAGS = + +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/check_y2k$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/cmd_args$U.Po ./$(DEPDIR)/map_vme$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_config$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_control$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_crypto$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_filegen$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_intres$U.Po ./$(DEPDIR)/ntp_io$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_loopfilter$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_monitor$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_peer$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_proto$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_refclock$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_request$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_restrict$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_timer$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntp_util$U.Po ./$(DEPDIR)/ntpd$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntpdsim-cmd_args$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntpdsim-ntp_config$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntpdsim-ntp_io$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntpdsim-ntpd$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/ntpdsim-ntpsim$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_acts$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_arbiter$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_arc$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_as2201$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_atom$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_bancomm$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_chronolog$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_chu$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_conf$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_datum$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_dumbclock$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_fg$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_gpsvme$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_heath$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_hopfpci$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_hopfser$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_hpgps$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_irig$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_jjy$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_jupiter$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_leitch$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_local$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_msfees$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_mx4200$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_neoclock4x$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_nmea$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_oncore$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_palisade$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_parse$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_pcf$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_pst$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_ptbacts$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_ripencc$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_shm$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_tpro$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_trak$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_true$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_tt560$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_ulink$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_usno$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_wwv$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_wwvb$U.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/refclock_zyfer$U.Po +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +DIST_SOURCES = $(libntpd_a_SOURCES) check_y2k.c $(ntpd_SOURCES) \ + $(ntpdsim_SOURCES) +DIST_COMMON = $(srcdir)/Makefile.in Makefile.am +SOURCES = $(libntpd_a_SOURCES) check_y2k.c $(ntpd_SOURCES) $(ntpdsim_SOURCES) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.in $(ACLOCAL_M4) + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu ntpd/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe) + +AR = ar + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) +libntpd.a: $(libntpd_a_OBJECTS) $(libntpd_a_DEPENDENCIES) + -rm -f libntpd.a + $(libntpd_a_AR) libntpd.a $(libntpd_a_OBJECTS) $(libntpd_a_LIBADD) + $(RANLIB) libntpd.a +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + $(mkinstalldirs) $(DESTDIR)$(bindir) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) $$p $(DESTDIR)$(bindir)/$$f"; \ + $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) $$p $(DESTDIR)$(bindir)/$$f || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f $(DESTDIR)$(bindir)/$$f"; \ + rm -f $(DESTDIR)$(bindir)/$$f; \ + done + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) + +clean-checkPROGRAMS: + -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS) +check_y2k$(EXEEXT): $(check_y2k_OBJECTS) $(check_y2k_DEPENDENCIES) + @rm -f check_y2k$(EXEEXT) + $(LINK) $(check_y2k_LDFLAGS) $(check_y2k_OBJECTS) $(check_y2k_LDADD) $(LIBS) +ntpd$(EXEEXT): $(ntpd_OBJECTS) $(ntpd_DEPENDENCIES) + @rm -f ntpd$(EXEEXT) + $(LINK) $(ntpd_LDFLAGS) $(ntpd_OBJECTS) $(ntpd_LDADD) $(LIBS) +ntpdsim$(EXEEXT): $(ntpdsim_OBJECTS) $(ntpdsim_DEPENDENCIES) + @rm -f ntpdsim$(EXEEXT) + $(LINK) $(ntpdsim_LDFLAGS) $(ntpdsim_OBJECTS) $(ntpdsim_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) core *.core + +distclean-compile: + -rm -f *.tab.c + +ANSI2KNR = ../util/ansi2knr +../util/ansi2knr: + cd ../util && $(MAKE) $(AM_MAKEFLAGS) ansi2knr + +mostlyclean-kr: + -test "$U" = "" || rm -f *_.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/check_y2k$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd_args$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/map_vme$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_config$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_control$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_crypto$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_filegen$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_intres$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_io$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_loopfilter$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_monitor$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_peer$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_proto$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_refclock$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_request$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_restrict$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_timer$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntp_util$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpd$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpdsim-cmd_args$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpdsim-ntp_config$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpdsim-ntp_io$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpdsim-ntpd$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ntpdsim-ntpsim$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_acts$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_arbiter$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_arc$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_as2201$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_atom$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_bancomm$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_chronolog$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_chu$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_conf$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_datum$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_dumbclock$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_fg$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_gpsvme$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_heath$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_hopfpci$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_hopfser$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_hpgps$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_irig$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_jjy$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_jupiter$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_leitch$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_local$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_msfees$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_mx4200$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_neoclock4x$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_nmea$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_oncore$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_palisade$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_parse$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_pcf$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_pst$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_ptbacts$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_ripencc$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_shm$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_tpro$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_trak$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_true$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_tt560$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_ulink$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_usno$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_wwv$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_wwvb$U.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/refclock_zyfer$U.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" \ +@am__fastdepCC_TRUE@ -c -o $@ `test -f '$<' || echo '$(srcdir)/'`$<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `test -f '$<' || echo '$(srcdir)/'`$< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" \ +@am__fastdepCC_TRUE@ -c -o $@ `if test -f '$<'; then $(CYGPATH_W) '$<'; else $(CYGPATH_W) '$(srcdir)/$<'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `if test -f '$<'; then $(CYGPATH_W) '$<'; else $(CYGPATH_W) '$(srcdir)/$<'; fi` + +ntpdsim-cmd_args$U.o: cmd_args$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-cmd_args$U.o -MD -MP -MF "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-cmd_args$U.o `test -f 'cmd_args$U.c' || echo '$(srcdir)/'`cmd_args$U.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo" "$(DEPDIR)/ntpdsim-cmd_args$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='cmd_args$U.c' object='ntpdsim-cmd_args$U.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-cmd_args$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-cmd_args$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-cmd_args$U.o `test -f 'cmd_args$U.c' || echo '$(srcdir)/'`cmd_args$U.c + +ntpdsim-cmd_args$U.obj: cmd_args$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-cmd_args$U.obj -MD -MP -MF "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-cmd_args$U.obj `if test -f 'cmd_args$U.c'; then $(CYGPATH_W) 'cmd_args$U.c'; else $(CYGPATH_W) '$(srcdir)/cmd_args$U.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo" "$(DEPDIR)/ntpdsim-cmd_args$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-cmd_args$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='cmd_args$U.c' object='ntpdsim-cmd_args$U.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-cmd_args$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-cmd_args$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-cmd_args$U.obj `if test -f 'cmd_args$U.c'; then $(CYGPATH_W) 'cmd_args$U.c'; else $(CYGPATH_W) '$(srcdir)/cmd_args$U.c'; fi` + +ntpdsim-ntp_config$U.o: ntp_config$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntp_config$U.o -MD -MP -MF "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntp_config$U.o `test -f 'ntp_config$U.c' || echo '$(srcdir)/'`ntp_config$U.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo" "$(DEPDIR)/ntpdsim-ntp_config$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntp_config$U.c' object='ntpdsim-ntp_config$U.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntp_config$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntp_config$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntp_config$U.o `test -f 'ntp_config$U.c' || echo '$(srcdir)/'`ntp_config$U.c + +ntpdsim-ntp_config$U.obj: ntp_config$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntp_config$U.obj -MD -MP -MF "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntp_config$U.obj `if test -f 'ntp_config$U.c'; then $(CYGPATH_W) 'ntp_config$U.c'; else $(CYGPATH_W) '$(srcdir)/ntp_config$U.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo" "$(DEPDIR)/ntpdsim-ntp_config$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntp_config$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntp_config$U.c' object='ntpdsim-ntp_config$U.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntp_config$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntp_config$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntp_config$U.obj `if test -f 'ntp_config$U.c'; then $(CYGPATH_W) 'ntp_config$U.c'; else $(CYGPATH_W) '$(srcdir)/ntp_config$U.c'; fi` + +ntpdsim-ntp_io$U.o: ntp_io$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntp_io$U.o -MD -MP -MF "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntp_io$U.o `test -f 'ntp_io$U.c' || echo '$(srcdir)/'`ntp_io$U.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo" "$(DEPDIR)/ntpdsim-ntp_io$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntp_io$U.c' object='ntpdsim-ntp_io$U.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntp_io$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntp_io$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntp_io$U.o `test -f 'ntp_io$U.c' || echo '$(srcdir)/'`ntp_io$U.c + +ntpdsim-ntp_io$U.obj: ntp_io$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntp_io$U.obj -MD -MP -MF "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntp_io$U.obj `if test -f 'ntp_io$U.c'; then $(CYGPATH_W) 'ntp_io$U.c'; else $(CYGPATH_W) '$(srcdir)/ntp_io$U.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo" "$(DEPDIR)/ntpdsim-ntp_io$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntp_io$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntp_io$U.c' object='ntpdsim-ntp_io$U.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntp_io$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntp_io$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntp_io$U.obj `if test -f 'ntp_io$U.c'; then $(CYGPATH_W) 'ntp_io$U.c'; else $(CYGPATH_W) '$(srcdir)/ntp_io$U.c'; fi` + +ntpdsim-ntpd$U.o: ntpd$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntpd$U.o -MD -MP -MF "$(DEPDIR)/ntpdsim-ntpd$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntpd$U.o `test -f 'ntpd$U.c' || echo '$(srcdir)/'`ntpd$U.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntpd$U.Tpo" "$(DEPDIR)/ntpdsim-ntpd$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntpd$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntpd$U.c' object='ntpdsim-ntpd$U.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntpd$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntpd$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntpd$U.o `test -f 'ntpd$U.c' || echo '$(srcdir)/'`ntpd$U.c + +ntpdsim-ntpd$U.obj: ntpd$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntpd$U.obj -MD -MP -MF "$(DEPDIR)/ntpdsim-ntpd$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntpd$U.obj `if test -f 'ntpd$U.c'; then $(CYGPATH_W) 'ntpd$U.c'; else $(CYGPATH_W) '$(srcdir)/ntpd$U.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntpd$U.Tpo" "$(DEPDIR)/ntpdsim-ntpd$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntpd$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntpd$U.c' object='ntpdsim-ntpd$U.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntpd$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntpd$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntpd$U.obj `if test -f 'ntpd$U.c'; then $(CYGPATH_W) 'ntpd$U.c'; else $(CYGPATH_W) '$(srcdir)/ntpd$U.c'; fi` + +ntpdsim-ntpsim$U.o: ntpsim$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntpsim$U.o -MD -MP -MF "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntpsim$U.o `test -f 'ntpsim$U.c' || echo '$(srcdir)/'`ntpsim$U.c; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo" "$(DEPDIR)/ntpdsim-ntpsim$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntpsim$U.c' object='ntpdsim-ntpsim$U.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntpsim$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntpsim$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntpsim$U.o `test -f 'ntpsim$U.c' || echo '$(srcdir)/'`ntpsim$U.c + +ntpdsim-ntpsim$U.obj: ntpsim$U.c +@am__fastdepCC_TRUE@ if $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -MT ntpdsim-ntpsim$U.obj -MD -MP -MF "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo" \ +@am__fastdepCC_TRUE@ -c -o ntpdsim-ntpsim$U.obj `if test -f 'ntpsim$U.c'; then $(CYGPATH_W) 'ntpsim$U.c'; else $(CYGPATH_W) '$(srcdir)/ntpsim$U.c'; fi`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo" "$(DEPDIR)/ntpdsim-ntpsim$U.Po"; \ +@am__fastdepCC_TRUE@ else rm -f "$(DEPDIR)/ntpdsim-ntpsim$U.Tpo"; exit 1; \ +@am__fastdepCC_TRUE@ fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='ntpsim$U.c' object='ntpdsim-ntpsim$U.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ depfile='$(DEPDIR)/ntpdsim-ntpsim$U.Po' tmpdepfile='$(DEPDIR)/ntpdsim-ntpsim$U.TPo' @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ntpdsim_CFLAGS) $(CFLAGS) -c -o ntpdsim-ntpsim$U.obj `if test -f 'ntpsim$U.c'; then $(CYGPATH_W) 'ntpsim$U.c'; else $(CYGPATH_W) '$(srcdir)/ntpsim$U.c'; fi` +check_y2k_.c: check_y2k.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/check_y2k.c; then echo $(srcdir)/check_y2k.c; else echo check_y2k.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +cmd_args_.c: cmd_args.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/cmd_args.c; then echo $(srcdir)/cmd_args.c; else echo cmd_args.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +map_vme_.c: map_vme.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/map_vme.c; then echo $(srcdir)/map_vme.c; else echo map_vme.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_config_.c: ntp_config.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_config.c; then echo $(srcdir)/ntp_config.c; else echo ntp_config.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_control_.c: ntp_control.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_control.c; then echo $(srcdir)/ntp_control.c; else echo ntp_control.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_crypto_.c: ntp_crypto.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_crypto.c; then echo $(srcdir)/ntp_crypto.c; else echo ntp_crypto.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_filegen_.c: ntp_filegen.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_filegen.c; then echo $(srcdir)/ntp_filegen.c; else echo ntp_filegen.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_intres_.c: ntp_intres.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_intres.c; then echo $(srcdir)/ntp_intres.c; else echo ntp_intres.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_io_.c: ntp_io.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_io.c; then echo $(srcdir)/ntp_io.c; else echo ntp_io.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_loopfilter_.c: ntp_loopfilter.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_loopfilter.c; then echo $(srcdir)/ntp_loopfilter.c; else echo ntp_loopfilter.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_monitor_.c: ntp_monitor.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_monitor.c; then echo $(srcdir)/ntp_monitor.c; else echo ntp_monitor.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_peer_.c: ntp_peer.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_peer.c; then echo $(srcdir)/ntp_peer.c; else echo ntp_peer.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_proto_.c: ntp_proto.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_proto.c; then echo $(srcdir)/ntp_proto.c; else echo ntp_proto.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_refclock_.c: ntp_refclock.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_refclock.c; then echo $(srcdir)/ntp_refclock.c; else echo ntp_refclock.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_request_.c: ntp_request.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_request.c; then echo $(srcdir)/ntp_request.c; else echo ntp_request.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_restrict_.c: ntp_restrict.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_restrict.c; then echo $(srcdir)/ntp_restrict.c; else echo ntp_restrict.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_timer_.c: ntp_timer.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_timer.c; then echo $(srcdir)/ntp_timer.c; else echo ntp_timer.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntp_util_.c: ntp_util.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntp_util.c; then echo $(srcdir)/ntp_util.c; else echo ntp_util.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntpd_.c: ntpd.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntpd.c; then echo $(srcdir)/ntpd.c; else echo ntpd.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +ntpsim_.c: ntpsim.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/ntpsim.c; then echo $(srcdir)/ntpsim.c; else echo ntpsim.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_acts_.c: refclock_acts.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_acts.c; then echo $(srcdir)/refclock_acts.c; else echo refclock_acts.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_arbiter_.c: refclock_arbiter.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_arbiter.c; then echo $(srcdir)/refclock_arbiter.c; else echo refclock_arbiter.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_arc_.c: refclock_arc.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_arc.c; then echo $(srcdir)/refclock_arc.c; else echo refclock_arc.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_as2201_.c: refclock_as2201.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_as2201.c; then echo $(srcdir)/refclock_as2201.c; else echo refclock_as2201.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_atom_.c: refclock_atom.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_atom.c; then echo $(srcdir)/refclock_atom.c; else echo refclock_atom.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_bancomm_.c: refclock_bancomm.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_bancomm.c; then echo $(srcdir)/refclock_bancomm.c; else echo refclock_bancomm.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_chronolog_.c: refclock_chronolog.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_chronolog.c; then echo $(srcdir)/refclock_chronolog.c; else echo refclock_chronolog.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_chu_.c: refclock_chu.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_chu.c; then echo $(srcdir)/refclock_chu.c; else echo refclock_chu.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_conf_.c: refclock_conf.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_conf.c; then echo $(srcdir)/refclock_conf.c; else echo refclock_conf.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_datum_.c: refclock_datum.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_datum.c; then echo $(srcdir)/refclock_datum.c; else echo refclock_datum.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_dumbclock_.c: refclock_dumbclock.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_dumbclock.c; then echo $(srcdir)/refclock_dumbclock.c; else echo refclock_dumbclock.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_fg_.c: refclock_fg.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_fg.c; then echo $(srcdir)/refclock_fg.c; else echo refclock_fg.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_gpsvme_.c: refclock_gpsvme.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_gpsvme.c; then echo $(srcdir)/refclock_gpsvme.c; else echo refclock_gpsvme.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_heath_.c: refclock_heath.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_heath.c; then echo $(srcdir)/refclock_heath.c; else echo refclock_heath.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_hopfpci_.c: refclock_hopfpci.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_hopfpci.c; then echo $(srcdir)/refclock_hopfpci.c; else echo refclock_hopfpci.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_hopfser_.c: refclock_hopfser.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_hopfser.c; then echo $(srcdir)/refclock_hopfser.c; else echo refclock_hopfser.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_hpgps_.c: refclock_hpgps.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_hpgps.c; then echo $(srcdir)/refclock_hpgps.c; else echo refclock_hpgps.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_irig_.c: refclock_irig.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_irig.c; then echo $(srcdir)/refclock_irig.c; else echo refclock_irig.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_jjy_.c: refclock_jjy.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_jjy.c; then echo $(srcdir)/refclock_jjy.c; else echo refclock_jjy.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_jupiter_.c: refclock_jupiter.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_jupiter.c; then echo $(srcdir)/refclock_jupiter.c; else echo refclock_jupiter.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_leitch_.c: refclock_leitch.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_leitch.c; then echo $(srcdir)/refclock_leitch.c; else echo refclock_leitch.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_local_.c: refclock_local.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_local.c; then echo $(srcdir)/refclock_local.c; else echo refclock_local.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_msfees_.c: refclock_msfees.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_msfees.c; then echo $(srcdir)/refclock_msfees.c; else echo refclock_msfees.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_mx4200_.c: refclock_mx4200.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_mx4200.c; then echo $(srcdir)/refclock_mx4200.c; else echo refclock_mx4200.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_neoclock4x_.c: refclock_neoclock4x.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_neoclock4x.c; then echo $(srcdir)/refclock_neoclock4x.c; else echo refclock_neoclock4x.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_nmea_.c: refclock_nmea.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_nmea.c; then echo $(srcdir)/refclock_nmea.c; else echo refclock_nmea.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_oncore_.c: refclock_oncore.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_oncore.c; then echo $(srcdir)/refclock_oncore.c; else echo refclock_oncore.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_palisade_.c: refclock_palisade.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_palisade.c; then echo $(srcdir)/refclock_palisade.c; else echo refclock_palisade.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_parse_.c: refclock_parse.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_parse.c; then echo $(srcdir)/refclock_parse.c; else echo refclock_parse.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_pcf_.c: refclock_pcf.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_pcf.c; then echo $(srcdir)/refclock_pcf.c; else echo refclock_pcf.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_pst_.c: refclock_pst.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_pst.c; then echo $(srcdir)/refclock_pst.c; else echo refclock_pst.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_ptbacts_.c: refclock_ptbacts.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_ptbacts.c; then echo $(srcdir)/refclock_ptbacts.c; else echo refclock_ptbacts.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_ripencc_.c: refclock_ripencc.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_ripencc.c; then echo $(srcdir)/refclock_ripencc.c; else echo refclock_ripencc.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_shm_.c: refclock_shm.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_shm.c; then echo $(srcdir)/refclock_shm.c; else echo refclock_shm.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_tpro_.c: refclock_tpro.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_tpro.c; then echo $(srcdir)/refclock_tpro.c; else echo refclock_tpro.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_trak_.c: refclock_trak.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_trak.c; then echo $(srcdir)/refclock_trak.c; else echo refclock_trak.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_true_.c: refclock_true.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_true.c; then echo $(srcdir)/refclock_true.c; else echo refclock_true.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_tt560_.c: refclock_tt560.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_tt560.c; then echo $(srcdir)/refclock_tt560.c; else echo refclock_tt560.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_ulink_.c: refclock_ulink.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_ulink.c; then echo $(srcdir)/refclock_ulink.c; else echo refclock_ulink.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_usno_.c: refclock_usno.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_usno.c; then echo $(srcdir)/refclock_usno.c; else echo refclock_usno.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_wwv_.c: refclock_wwv.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_wwv.c; then echo $(srcdir)/refclock_wwv.c; else echo refclock_wwv.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_wwvb_.c: refclock_wwvb.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_wwvb.c; then echo $(srcdir)/refclock_wwvb.c; else echo refclock_wwvb.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +refclock_zyfer_.c: refclock_zyfer.c $(ANSI2KNR) + $(CPP) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) `if test -f $(srcdir)/refclock_zyfer.c; then echo $(srcdir)/refclock_zyfer.c; else echo refclock_zyfer.c; fi` | sed 's/^# \([0-9]\)/#line \1/' | $(ANSI2KNR) > $@ || rm -f $@ +check_y2k_.$(OBJEXT) cmd_args_.$(OBJEXT) map_vme_.$(OBJEXT) \ +ntp_config_.$(OBJEXT) ntp_control_.$(OBJEXT) ntp_crypto_.$(OBJEXT) \ +ntp_filegen_.$(OBJEXT) ntp_intres_.$(OBJEXT) ntp_io_.$(OBJEXT) \ +ntp_loopfilter_.$(OBJEXT) ntp_monitor_.$(OBJEXT) ntp_peer_.$(OBJEXT) \ +ntp_proto_.$(OBJEXT) ntp_refclock_.$(OBJEXT) ntp_request_.$(OBJEXT) \ +ntp_restrict_.$(OBJEXT) ntp_timer_.$(OBJEXT) ntp_util_.$(OBJEXT) \ +ntpd_.$(OBJEXT) ntpsim_.$(OBJEXT) refclock_acts_.$(OBJEXT) \ +refclock_arbiter_.$(OBJEXT) refclock_arc_.$(OBJEXT) \ +refclock_as2201_.$(OBJEXT) refclock_atom_.$(OBJEXT) \ +refclock_bancomm_.$(OBJEXT) refclock_chronolog_.$(OBJEXT) \ +refclock_chu_.$(OBJEXT) refclock_conf_.$(OBJEXT) \ +refclock_datum_.$(OBJEXT) refclock_dumbclock_.$(OBJEXT) \ +refclock_fg_.$(OBJEXT) refclock_gpsvme_.$(OBJEXT) \ +refclock_heath_.$(OBJEXT) refclock_hopfpci_.$(OBJEXT) \ +refclock_hopfser_.$(OBJEXT) refclock_hpgps_.$(OBJEXT) \ +refclock_irig_.$(OBJEXT) refclock_jjy_.$(OBJEXT) \ +refclock_jupiter_.$(OBJEXT) refclock_leitch_.$(OBJEXT) \ +refclock_local_.$(OBJEXT) refclock_msfees_.$(OBJEXT) \ +refclock_mx4200_.$(OBJEXT) refclock_neoclock4x_.$(OBJEXT) \ +refclock_nmea_.$(OBJEXT) refclock_oncore_.$(OBJEXT) \ +refclock_palisade_.$(OBJEXT) refclock_parse_.$(OBJEXT) \ +refclock_pcf_.$(OBJEXT) refclock_pst_.$(OBJEXT) \ +refclock_ptbacts_.$(OBJEXT) refclock_ripencc_.$(OBJEXT) \ +refclock_shm_.$(OBJEXT) refclock_tpro_.$(OBJEXT) \ +refclock_trak_.$(OBJEXT) refclock_true_.$(OBJEXT) \ +refclock_tt560_.$(OBJEXT) refclock_ulink_.$(OBJEXT) \ +refclock_usno_.$(OBJEXT) refclock_wwv_.$(OBJEXT) \ +refclock_wwvb_.$(OBJEXT) refclock_zyfer_.$(OBJEXT) : $(ANSI2KNR) +uninstall-info-am: + +ETAGS = etags +ETAGSFLAGS = + +CTAGS = ctags +CTAGSFLAGS = + +tags: TAGS + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(ETAGS_ARGS)$$tags$$unique" \ + || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique + +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) + +top_distdir = .. +distdir = $(top_distdir)/$(PACKAGE)-$(VERSION) + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkinstalldirs) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) + $(MAKE) $(AM_MAKEFLAGS) check-local +check: check-am +all-am: Makefile $(LIBRARIES) $(PROGRAMS) + +installdirs: + $(mkinstalldirs) $(DESTDIR)$(bindir) +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-checkPROGRAMS clean-generic \ + clean-noinstLIBRARIES mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile + +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +info: info-am + +info-am: + +install-data-am: + +install-exec-am: install-binPROGRAMS + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile + +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic mostlyclean-kr + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-info-am + +.PHONY: CTAGS GTAGS all all-am check check-am check-local clean \ + clean-binPROGRAMS clean-checkPROGRAMS clean-generic \ + clean-noinstLIBRARIES ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am info \ + info-am install install-am install-binPROGRAMS install-data \ + install-data-am install-exec install-exec-am install-info \ + install-info-am install-man install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-kr pdf pdf-am ps ps-am tags \ + uninstall uninstall-am uninstall-binPROGRAMS uninstall-info-am + + +check-local: @MAKE_CHECK_Y2K@ + test -z "@MAKE_CHECK_Y2K@" || ./@MAKE_CHECK_Y2K@ + +$(PROGRAMS): $(LDADD) + +../libntp/libntp.a: + cd ../libntp && $(MAKE) + +../libparse/libparse.a: + cd ../libparse && $(MAKE) + +version.o: $(ntpd_OBJECTS) ../libntp/libntp.a @LIBPARSE@ Makefile $(top_srcdir)/version + env CSET=`cat $(top_srcdir)/version` $(top_builddir)/scripts/mkver ntpd + $(COMPILE) -c version.c +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/ntpd/check_y2k.c b/ntpd/check_y2k.c new file mode 100644 index 0000000..6b83115 --- /dev/null +++ b/ntpd/check_y2k.c @@ -0,0 +1,627 @@ +/* check_y2k.c -- test ntp code constructs for Y2K correctness Y2KFixes [*/ + + /* + Code invoked by `make check`. Not part of ntpd and not to be + installed. + + On any code I even wonder about, I've cut and pasted the code + here and ran it as a test case just to be sure. + + For code not in "ntpd" proper, we have tried to call most + repaired functions from herein to properly test them + (something never done before!). This has found several bugs, + not normal Y2K bugs, that will strike in Y2K so repair them + we did. + + Program exits with 0 on success, 1 on Y2K failure (stdout messages). + Exit of 2 indicates internal logic bug detected OR failure of + what should be our correct formulas. + + While "make check" should only check logic for source within that + specific directory, this check goes outside the scope of the local + directory. It's not a perfect world (besides, there is a lot of + interdependence here, and it really needs to be tested in + a controled order). + */ + +/* { definitions lifted from ntpd.c to allow us to complie with + "#include ntp.h". I have not taken the time to reduce the clutter. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#include <stdio.h> +#include <errno.h> +#ifndef SYS_WINNT +# if !defined(VMS) /*wjm*/ +# include <sys/param.h> +# endif /* VMS */ +# if HAVE_SYS_SIGNAL_H +# include <sys/signal.h> +# endif /* HAVE_SYS_SIGNAL_H */ +# include <sys/signal.h> +# ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +# endif /* HAVE_SYS_IOCTL_H */ +# if !defined(VMS) /*wjm*/ +# include <sys/resource.h> +# endif /* VMS */ +#else +# include <signal.h> +# include <process.h> +# include <io.h> +# include "../libntp/log.h" +#endif /* SYS_WINNT */ +#if defined(HAVE_RTPRIO) +# ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +# endif +# ifdef HAVE_SYS_LOCK_H +# include <sys/lock.h> +# endif +# include <sys/rtprio.h> +#else +# ifdef HAVE_PLOCK +# ifdef HAVE_SYS_LOCK_H +# include <sys/lock.h> +# endif +# endif +#endif +#if defined(HAVE_SCHED_SETSCHEDULER) +# ifdef HAVE_SCHED_H +# include <sched.h> +# else +# ifdef HAVE_SYS_SCHED_H +# include <sys/sched.h> +# endif +# endif +#endif +#if defined(HAVE_SYS_MMAN_H) +# include <sys/mman.h> +#endif + +#ifdef HAVE_TERMIOS_H +# include <termios.h> +#endif + +#ifdef SYS_DOMAINOS +# include <apollo/base.h> +#endif /* SYS_DOMAINOS */ + +/* } end definitions lifted from ntpd.c */ + +#include "ntp_calendar.h" +#include "parse.h" + +#define GoodLeap(Year) (((Year)%4 || (!((Year)%100) && (Year)%400)) ? 0 : 13 ) + +volatile int debug = 0; /* debugging requests for parse stuff */ +char const *progname = "check_y2k"; + +long +Days ( int Year ) /* return number of days since year "0" */ +{ + long Return; + /* this is a known to be good algorithm */ + Return = Year * 365; /* first aproximation to the value */ + if ( Year >= 1 ) + { /* see notes in libparse/parse.c if you want a PROPER + * **generic algorithm. */ + Return += (Year+3) / 4; /* add in (too many) leap days */ + Return -= (Year-1) / 100; /* reduce by (too many) centurys */ + Return += (Year-1) / 400; /* get final answer */ + } + + return Return; +} + +static int year0 = 1900; /* sarting year for NTP time */ +static int yearend; /* ending year we test for NTP time. + * 32-bit systems: through 2036, the + **year in which NTP time overflows. + * 64-bit systems: a reasonable upper + **limit (well, maybe somewhat beyond + **reasonable, but well before the + **max time, by which time the earth + **will be dead.) */ +static time_t Time; +static struct tm LocalTime; + +#define Error(year) if ( (year)>=2036 && LocalTime.tm_year < 110 ) \ + Warnings++; else Fatals++ + +int +main( void ) +{ + int Fatals; + int Warnings; + int year; + + Time = time( (time_t *)NULL ) +#ifdef TESTTIMEOFFSET + + test_time_offset +#endif + ; + LocalTime = *localtime( &Time ); + + year = ( sizeof( u_long ) > 4 ) /* save max span using year as temp */ + ? ( 400 * 3 ) /* three greater gregorian cycles */ + : ((int)(0x7FFFFFFF / 365.242 / 24/60/60)* 2 ); /*32-bit limit*/ + /* NOTE: will automacially expand test years on + * 64 bit machines.... this may cause some of the + * existing ntp logic to fail for years beyond + * 2036 (the current 32-bit limit). If all checks + * fail ONLY beyond year 2036 you may ignore such + * errors, at least for a decade or so. */ + yearend = year0 + year; + + puts( " internal self check" ); + { /* verify our own logic used to verify repairs */ + unsigned long days; + + if ( year0 >= yearend ) + { + fprintf( stdout, "year0=%d NOT LESS THAN yearend=%d (span=%d)\n", + (int)year0, (int)yearend, (int)year ); + exit(2); + } + + { + int save_year; + + save_year = LocalTime.tm_year; /* save current year */ + + year = 1980; + LocalTime.tm_year = year - 1900; + Fatals = Warnings = 0; + Error(year); /* should increment Fatals */ + if ( Fatals == 0 ) + { + fprintf( stdout, + "%4d: %s(%d): FATAL DID NOT INCREMENT (Fatals=%d Warnings=%d)\n", + (int)year, __FILE__, __LINE__, (int)Fatals, (int)Warnings ); + exit(2); + } + + year = 2100; /* test year > limit but CURRENT year < limit */ + Fatals = Warnings = 0; + Error(year); /* should increment Fatals */ + if ( Warnings == 0 ) + { + fprintf( stdout, + "%4d: %s(%d): WARNING DID NOT INCREMENT (Fatals=%d Warnings=%d)\n", + (int)year, __FILE__, __LINE__, (int)Fatals, (int)Warnings ); + exit(2); + } + Fatals = Warnings = 0; + LocalTime.tm_year = year - 1900; /* everything > limit */ + Error(1980); /* should increment Fatals */ + if ( Fatals == 0 ) + { + fprintf( stdout, + "%4d: %s(%d): FATALS DID NOT INCREMENT (Fatals=%d Warnings=%d)\n", + (int)year, __FILE__, __LINE__, (int)Fatals, (int)Warnings ); + exit(2); + } + + LocalTime.tm_year = save_year; + } + + days = 365+1; /* days in year 0 + 1 more day */ + for ( year = 1; year <= 2500; year++ ) + { + long Test; + Test = Days( year ); + if ( days != Test ) + { + fprintf( stdout, "%04d: Days() DAY COUNT ERROR: s/b=%ld was=%ld\n", + year, (long)days, (long)Test ); + exit(2); /* would throw off many other tests */ + } + + Test = julian0(year); /* compare with julian0() macro */ + if ( days != Test ) + { + fprintf( stdout, "%04d: julian0() DAY COUNT ERROR: s/b=%ld was=%ld\n", + year, (long)days, (long)Test ); + exit(2); /* would throw off many other tests */ + } + + days += 365; + if ( isleap_4(year) ) days++; + } + + if ( isleap_4(1999) ) + { + fprintf( stdout, "isleap_4(1999) REPORTED TRUE\n" ); + exit(2); + } + if ( !isleap_4(2000) ) + { + fprintf( stdout, "isleap_4(2000) REPORTED FALSE\n" ); + exit(2); + } + if ( isleap_4(2001) ) + { + fprintf( stdout, "isleap_4(1999) REPORTED TRUE\n" ); + exit(2); + } + + if ( !isleap_tm(2000-1900) ) + { + fprintf( stdout, "isleap_tm(100) REPORTED FALSE\n" ); + exit(2); + } + } + + Fatals = Warnings = 0; + + puts( " include/ntp.h" ); + { /* test our new isleap_*() #define "functions" */ + + for ( year = 1400; year <= 2200; year++ ) + { + int LeapSw; + int IsLeapSw; + + LeapSw = GoodLeap(year); + IsLeapSw = isleap_4(year); + + if ( !!LeapSw != !!IsLeapSw ) + { + Error(year); + fprintf( stdout, + " %4d %2d %3d *** ERROR\n", year, LeapSw, IsLeapSw ); + break; + } + + IsLeapSw = isleap_tm(year-1900); + + if ( !!LeapSw != !!IsLeapSw ) + { + Error(year); + fprintf( stdout, + " %4d %2d %3d *** ERROR\n", year, LeapSw, IsLeapSw ); + break; + } + } + } + + puts( " include/ntp_calendar.h" ); + { /* I belive this is good, but just to be sure... */ + + /* we are testing this #define */ +#define is_leapyear(y) (y%4 == 0 && !(y%100 == 0 && !(y%400 == 0))) + + for ( year = 1400; year <= 2200; year++ ) + { + int LeapSw; + + LeapSw = GoodLeap(year); + + if ( !(!LeapSw) != !(!is_leapyear(year)) ) + { + Error(year); + fprintf( stdout, + " %4d %2d *** ERROR\n", year, LeapSw ); + break; + } + } + } + + + puts( " libparse/parse.c" ); + { + long Days1970; /* days from 1900 to 1970 */ + + struct ParseTime /* womp up a test structure to all cut/paste code */ + { + int year; + } Clock_Time, *clock_time; + + clock_time = &Clock_Time; + + /* first test this #define */ +#define days_per_year(x) ((x) % 4 ? 365 : ((x % 400) ? ((x % 100) ? 366 : 365) : 366)) + + for ( year = 1400; year <= 2200; year++ ) + { + int LeapSw; + int DayCnt; + + LeapSw = GoodLeap(year); + DayCnt = (int)days_per_year(year); + + if ( ( LeapSw ? 366 : 365 ) != DayCnt ) + { + Error(year); + fprintf( stdout, + " days_per_year() %4d %2d %3d *** ERROR\n", + year, LeapSw, DayCnt ); + break; + } + } + + /* test (what is now julian0) calculations */ + + Days1970 = Days( 1970 ); /* get days since 1970 using a known good */ + + for ( year = 1970; year < yearend; year++ ) + { + unsigned long t; + long DaysYear ; + + clock_time->year = year; + + /* here is the code we are testing, cut and pasted out of the source */ +#if 0 /* old BUGGY code that has Y2K (and many other) failures */ + /* ghealton: this logic FAILED with great frequency when run + * over a period of time, including for year 2000. True, it + * had more successes than failures, but that's not really good + * enough for critical time distribution software. + * It is so awful I wonder if it has had a history of failure + * and fixes? */ + t = (clock_time->year - 1970) * 365; + t += (clock_time->year >> 2) - (1970 >> 2); + t -= clock_time->year / 100 - 1970 / 100; + t += clock_time->year / 400 - 1970 / 400; + + /* (immediate feare of rounding errors on integer + * **divisions proved well founded) */ + +#else + /* my replacement, based on Days() above */ + t = julian0(year) - julian0(1970); +#endif + + /* compare result in t against trusted calculations */ + DaysYear = Days( year ); /* get days to this year */ + if ( t != DaysYear - Days1970 ) + { + Error(year); + fprintf( stdout, + " %4d 1970=%-8ld %4d=%-8ld %-3ld t=%-8ld *** ERROR ***\n", + year, (long)Days1970, + year, + (long)DaysYear, + (long)(DaysYear - Days1970), + (long)t ); + } + } + +#if 1 /* { */ + { + debug = 1; /* enable debugging */ + for ( year = 1970; year < yearend; year++ ) + { /* (limited by theory unix 2038 related bug lives by, but + * ends in yearend) */ + clocktime_t ct; + time_t Observed; + time_t Expected; + u_long Flag; + unsigned long t; + + ct.day = 1; + ct.month = 1; + ct.year = year; + ct.hour = ct.minute = ct.second = ct.usecond = 0; + ct.utcoffset = 0; + ct.utctime = 0; + ct.flags = 0; + + Flag = 0; + Observed = parse_to_unixtime( &ct, &Flag ); + if ( ct.year != year ) + { + fprintf( stdout, + "%04d: parse_to_unixtime(,%d) CORRUPTED ct.year: was %d\n", + (int)year, (int)Flag, (int)ct.year ); + Error(year); + break; + } + t = julian0(year) - julian0(1970); /* Julian day from 1970 */ + Expected = t * 24 * 60 * 60; + if ( Observed != Expected || Flag ) + { /* time difference */ + fprintf( stdout, + "%04d: parse_to_unixtime(,%d) FAILURE: was=%lu s/b=%lu (%ld)\n", + year, (int)Flag, + (unsigned long)Observed, (unsigned long)Expected, + ((long)Observed - (long)Expected) ); + Error(year); + break; + } + + if ( year >= YEAR_PIVOT+1900 ) + { + /* check year % 100 code we put into parse_to_unixtime() */ + ct.utctime = 0; + ct.year = year % 100; + Flag = 0; + + Observed = parse_to_unixtime( &ct, &Flag ); + + if ( Observed != Expected || Flag ) + { /* time difference */ + fprintf( stdout, +"%04d: parse_to_unixtime(%d,%d) FAILURE: was=%lu s/b=%lu (%ld)\n", + year, (int)ct.year, (int)Flag, + (unsigned long)Observed, (unsigned long)Expected, + ((long)Observed - (long)Expected) ); + Error(year); + break; + } + + /* check year - 1900 code we put into parse_to_unixtime() */ + ct.utctime = 0; + ct.year = year - 1900; + Flag = 0; + + Observed = parse_to_unixtime( &ct, &Flag ); + + if ( Observed != Expected || Flag ) + { /* time difference */ + fprintf( stdout, +"%04d: parse_to_unixtime(%d,%d) FAILURE: was=%lu s/b=%lu (%ld)\n", + year, (int)ct.year, (int)Flag, + (unsigned long)Observed, (unsigned long)Expected, + ((long)Observed - (long)Expected) ); + Error(year); + break; + } + + + } + } +#endif /* } */ + } + } + + puts( " libntp/caljulian.c" ); + { /* test caljulian() */ + struct calendar ot; + u_long ntp_time; /* NTP time */ + + year = year0; /* calculate the basic year */ + printf( " starting year %04d\n", (int)year0 ); + printf( " ending year %04d\n", (int)yearend ); + + + ntp_time = julian0( year0 ); /* NTP starts in 1900-01-01 */ +#if DAY_NTP_STARTS == 693596 + ntp_time -= 365; /* BIAS required for successful test */ +#endif + if ( DAY_NTP_STARTS != ntp_time ) + { + Error(year); + fprintf( stdout, + "%04d: DAY_NTP_STARTS (%ld) NOT TRUE VALUE OF %ld (%ld)\n", + (int)year0, + (long)DAY_NTP_STARTS, (long)ntp_time, + (long)DAY_NTP_STARTS - (long)ntp_time ); + } + + for ( ; year < yearend; year++ ) + { + + /* 01-01 for the current year */ + ntp_time = Days( year ) - Days( year0 ); /* days into NTP time */ + ntp_time *= 24 * 60 * 60; /* convert into seconds */ + caljulian( ntp_time, &ot ); /* convert January 1 */ + if ( ot.year != year + || ot.month != 1 + || ot.monthday != 1 ) + { + Error(year); + fprintf( stdout, "%lu: EXPECTED %04d-01-01: FOUND %04d-%02d-%02d\n", + (unsigned long)ntp_time, + year, + (int)ot.year, (int)ot.month, (int)ot.monthday ); + break; + } + + ntp_time += (31 + 28-1) * ( 24 * 60 * 60 ); /* advance to 02-28 */ + caljulian( ntp_time, &ot ); /* convert Feb 28 */ + if ( ot.year != year + || ot.month != 2 + || ot.monthday != 28 ) + { + Error(year); + fprintf( stdout, "%lu: EXPECTED %04d-02-28: FOUND %04d-%02d-%02d\n", + (unsigned long)ntp_time, + year, + (int)ot.year, (int)ot.month, (int)ot.monthday ); + break; + } + + { + int m; /* expected month */ + int d; /* expected day */ + + m = isleap_4(year) ? 2 : 3; + d = isleap_4(year) ? 29 : 1; + + ntp_time += ( 24 * 60 * 60 ); /* advance to the next day */ + caljulian( ntp_time, &ot ); /* convert this day */ + if ( ot.year != year + || ot.month != m + || ot.monthday != d ) + { + Error(year); + fprintf( stdout, "%lu: EXPECTED %04d-%02d-%02d: FOUND %04d-%02d-%02d\n", + (unsigned long)ntp_time, + year, m, d, + (int)ot.year, (int)ot.month, (int)ot.monthday ); + break; + } + + } + } + } + + puts( " libntp/caltontp.c" ); + { /* test caltontp() */ + struct calendar ot; + u_long ntp_time; /* NTP time */ + + year = year0; /* calculate the basic year */ + printf( " starting year %04d\n", (int)year0 ); + printf( " ending year %04d\n", (int)yearend ); + + + for ( ; year < yearend; year++ ) + { + u_long ObservedNtp; + + /* 01-01 for the current year */ + ot.year = year; + ot.month = ot.monthday = 1; /* unused, but set anyway JIC */ + ot.yearday = 1; /* this is the magic value used by caltontp() */ + ot.hour = ot.minute = ot.second = 0; + + ntp_time = Days( year ) - Days( year0 ); /* days into NTP time */ + ntp_time *= 24 * 60 * 60; /* convert into seconds */ + ObservedNtp = caltontp( &ot ); + if ( ntp_time != ObservedNtp ) + { + Error(year); + fprintf( stdout, "%d: EXPECTED %lu: FOUND %lu (%ld)\n", + (int)year, + (unsigned long)ntp_time, (unsigned long)ObservedNtp , + (long)ntp_time - (long)ObservedNtp ); + + break; + } + + /* now call caljulian as a type of failsafe supercheck */ + caljulian( ObservedNtp, &ot ); /* convert January 1 */ + if ( ot.year != year + || ot.month != 1 + || ot.monthday != 1 ) + { + Error(year); + fprintf( stdout, "%lu: caljulian FAILSAFE EXPECTED %04d-01-01: FOUND %04d-%02d-%02d\n", + (unsigned long)ObservedNtp, + year, + (int)ot.year, (int)ot.month, (int)ot.monthday ); + break; + } + } + } + + if ( Warnings > 0 ) + fprintf( stdout, "%d WARNINGS\n", Warnings ); + if ( Fatals > 0 ) + fprintf( stdout, "%d FATAL ERRORS\n", Fatals ); + return Fatals ? 1 : 0; +} + /* Y2KFixes ] */ diff --git a/ntpd/cmd_args.c b/ntpd/cmd_args.c new file mode 100644 index 0000000..3ed9b66 --- /dev/null +++ b/ntpd/cmd_args.c @@ -0,0 +1,418 @@ +/* + * cmd_args.c = command-line argument processing + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_stdlib.h" +#include "ntp_cmdargs.h" + +#ifdef SIM +#include "ntpsim.h" +#endif /* SIM */ + +/* + * Definitions of things either imported from or exported to outside + */ +extern char const *progname; +int listen_to_virtual_ips = 1; + +#ifdef SYS_WINNT +extern BOOL NoWinService; +#endif + +static const char *ntp_options = "aAbB:c:C:dD:f:gi:k:l:LmnNO:p:P:qr:s:S:t:T:W:u:v:V:xY:Z:-:"; + +#ifdef HAVE_NETINFO +extern int check_netinfo; +#endif + + +/* + * getstartup - search through the options looking for a debugging flag + */ +void +getstartup( + int argc, + char *argv[] + ) +{ + int errflg; + extern int priority_done; + int c; + +#ifdef DEBUG + debug = 0; /* no debugging by default */ +#endif + + /* + * This is a big hack. We don't really want to read command line + * configuration until everything else is initialized, since + * the ability to configure the system may depend on storage + * and the like having been initialized. Except that we also + * don't want to initialize anything until after detaching from + * the terminal, but we won't know to do that until we've + * parsed the command line. Do that now, crudely, and do it + * again later. Our ntp_getopt() is explicitly reusable, by the + * way. Your own mileage may vary. + * + * This hack is even called twice (to allow complete logging to file) + */ + errflg = 0; + progname = argv[0]; + + /* + * Decode argument list + */ + while ((c = ntp_getopt(argc, argv, ntp_options)) != EOF) + switch (c) { +#ifdef DEBUG + case 'd': + ++debug; + break; + case 'D': + debug = (int)atol(ntp_optarg); + printf("Debug1: %s -> %x = %d\n", ntp_optarg, debug, debug); + break; +#else + case 'd': + case 'D': + msyslog(LOG_ERR, "ntpd not compiled with -DDEBUG option - no DEBUG support"); + fprintf(stderr, "ntpd not compiled with -DDEBUG option - no DEBUG support\n"); + ++errflg; + break; +#endif + case 'L': + listen_to_virtual_ips = 0; + break; + case 'l': + { + FILE *new_file; + + if(strcmp(ntp_optarg, "stderr") == 0) + new_file = stderr; + else if(strcmp(ntp_optarg, "stdout") == 0) + new_file = stdout; + else + new_file = fopen(ntp_optarg, "a"); + if (new_file != NULL) { + NLOG(NLOG_SYSINFO) + msyslog(LOG_NOTICE, "logging to file %s", ntp_optarg); + if (syslog_file != NULL && + fileno(syslog_file) != fileno(new_file)) + (void)fclose(syslog_file); + + syslog_file = new_file; + syslogit = 0; + } + else + msyslog(LOG_ERR, + "Cannot open log file %s", + ntp_optarg); + } + break; + + case 'n': + case 'q': + ++nofork; +#ifdef SYS_WINNT + NoWinService = TRUE; +#endif + break; + + case 'N': + priority_done = 0; + break; + + case '?': + ++errflg; + break; + + case '-': + if ( ! strcmp(ntp_optarg, "version") ) { + printf("%.80s: %.80s\n", progname, Version); + exit(0); + } else if ( ! strcmp(ntp_optarg, "help") ) { + /* usage(); */ + /* exit(0); */ + ++errflg; + } else if ( ! strcmp(ntp_optarg, "copyright") ) { + printf("unknown\n"); + exit(0); + } else { + fprintf(stderr, "%.80s: Error unknown argument '--%.80s'\n", + progname, + ntp_optarg); + exit(12); + } + break; + + default: + break; + } + + if (errflg || ntp_optind != argc) { + (void) fprintf(stderr, "usage: %s [ -abdgmnqx ] [ -c config_file ] [ -e e_delay ]\n", progname); + (void) fprintf(stderr, "\t\t[ -f freq_file ] [ -k key_file ] [ -l log_file ]\n"); + (void) fprintf(stderr, "\t\t[ -p pid_file ] [ -r broad_delay ] [ -s statdir ]\n"); + (void) fprintf(stderr, "\t\t[ -t trust_key ] [ -v sys_var ] [ -V default_sysvar ]\n"); +#if defined(HAVE_SCHED_SETSCHEDULER) + (void) fprintf(stderr, "\t\t[ -P fixed_process_priority ]\n"); +#endif +#ifdef HAVE_CLOCKCTL + (void) fprintf(stderr, "\t\t[ -u user[:group] ] [ -i chrootdir ]\n"); +#endif + exit(2); + } + ntp_optind = 0; /* reset ntp_optind to restart ntp_getopt */ + +#ifdef DEBUG + if (debug) { +#ifdef HAVE_SETVBUF + static char buf[BUFSIZ]; + setvbuf(stdout, buf, _IOLBF, BUFSIZ); +#else + setlinebuf(stdout); +#endif + } +#endif +} + +/* + * getCmdOpts - get command line options + */ +void +getCmdOpts( + int argc, + char *argv[] + ) +{ + extern char *config_file; + struct sockaddr_in inaddrntp; + int errflg; + int c; + + /* + * Initialize, initialize + */ + errflg = 0; +#ifdef DEBUG + debug = 0; +#endif /* DEBUG */ + + progname = argv[0]; + + /* + * Decode argument list + */ + while ((c = ntp_getopt(argc, argv, ntp_options)) != EOF) { + switch (c) { + case 'a': + proto_config(PROTO_AUTHENTICATE, 1, 0., NULL); + break; + + case 'A': + proto_config(PROTO_AUTHENTICATE, 0, 0., NULL); + break; + + case 'b': + proto_config(PROTO_BROADCLIENT, 1, 0., NULL); + break; + + case 'c': + config_file = ntp_optarg; +#ifdef HAVE_NETINFO + check_netinfo = 0; +#endif + break; + + case 'd': +#ifdef DEBUG + debug++; +#else + errflg++; +#endif /* DEBUG */ + break; + + case 'D': +#ifdef DEBUG + debug = (int)atol(ntp_optarg); + printf("Debug2: %s -> %x = %d\n", ntp_optarg, debug, debug); +#else + errflg++; +#endif /* DEBUG */ + break; + + case 'f': + stats_config(STATS_FREQ_FILE, ntp_optarg); + break; + + case 'g': + allow_panic = TRUE; + break; + + case 'i': +#ifdef HAVE_CLOCKCTL + if (!ntp_optarg) + errflg++; + else + chrootdir = ntp_optarg; + break; +#else + errflg++; +#endif + case 'k': + getauthkeys(ntp_optarg); + break; + + case 'L': /* already done at pre-scan */ + case 'l': /* already done at pre-scan */ + break; + + case 'm': + inaddrntp.sin_family = AF_INET; + inaddrntp.sin_port = htons(NTP_PORT); + inaddrntp.sin_addr.s_addr = htonl(INADDR_NTP); + proto_config(PROTO_MULTICAST_ADD, 0, 0., (struct sockaddr_storage*)&inaddrntp); + sys_bclient = 1; + break; + + case 'n': /* already done at pre-scan */ + break; + + case 'N': /* already done at pre-scan */ + break; + + case 'p': + stats_config(STATS_PID_FILE, ntp_optarg); + break; + + case 'P': +#if defined(HAVE_SCHED_SETSCHEDULER) + config_priority = (int)atol(ntp_optarg); + config_priority_override = 1; +#else + errflg++; +#endif + break; + + case 'q': + mode_ntpdate = TRUE; + break; + + case 'r': + do { + double tmp; + + if (sscanf(ntp_optarg, "%lf", &tmp) != 1) { + msyslog(LOG_ERR, + "command line broadcast delay value %s undecodable", + ntp_optarg); + } else { + proto_config(PROTO_BROADDELAY, 0, tmp, NULL); + } + } while (0); + break; + + case 'u': +#ifdef HAVE_CLOCKCTL + user = malloc(strlen(ntp_optarg) + 1); + if ((user == NULL) || (ntp_optarg == NULL)) + errflg++; + (void)strncpy(user, ntp_optarg, strlen(ntp_optarg) + 1); + group = rindex(user, ':'); + if (group) + *group++ = '\0'; /* get rid of the ':' */ +#else + errflg++; +#endif + break; + case 's': + stats_config(STATS_STATSDIR, ntp_optarg); + break; + + case 't': + do { + u_long tkey; + + tkey = (int)atol(ntp_optarg); + if (tkey <= 0 || tkey > NTP_MAXKEY) { + msyslog(LOG_ERR, + "command line trusted key %s is invalid", + ntp_optarg); + } else { + authtrust(tkey, 1); + } + } while (0); + break; + + case 'v': + case 'V': + set_sys_var(ntp_optarg, strlen(ntp_optarg)+1, + (u_short) (RW | ((c == 'V') ? DEF : 0))); + break; + + case 'x': + clock_max = 600; + break; +#ifdef SIM + case 'B': + sscanf(ntp_optarg, "%lf", &ntp_node.bdly); + break; + + case 'C': + sscanf(ntp_optarg, "%lf", &ntp_node.snse); + break; + + case 'H': + sscanf(ntp_optarg, "%lf", &ntp_node.slew); + break; + + case 'O': + sscanf(ntp_optarg, "%lf", &ntp_node.clk_time); + break; + + case 'S': + sscanf(ntp_optarg, "%lf", &ntp_node.sim_time); + break; + + case 'T': + sscanf(ntp_optarg, "%lf", &ntp_node.ferr); + break; + + case 'W': + sscanf(ntp_optarg, "%lf", &ntp_node.fnse); + break; + + case 'Y': + sscanf(ntp_optarg, "%lf", &ntp_node.ndly); + break; + + case 'Z': + sscanf(ntp_optarg, "%lf", &ntp_node.pdly); + break; + +#endif /* SIM */ + default: + errflg++; + break; + } + } + + if (errflg || ntp_optind != argc) { + (void) fprintf(stderr, "usage: %s [ -abdgmnx ] [ -c config_file ] [ -e e_delay ]\n", progname); + (void) fprintf(stderr, "\t\t[ -f freq_file ] [ -k key_file ] [ -l log_file ]\n"); + (void) fprintf(stderr, "\t\t[ -p pid_file ] [ -r broad_delay ] [ -s statdir ]\n"); + (void) fprintf(stderr, "\t\t[ -t trust_key ] [ -v sys_var ] [ -V default_sysvar ]\n"); +#if defined(HAVE_SCHED_SETSCHEDULER) + (void) fprintf(stderr, "\t\t[ -P fixed_process_priority ]\n"); +#endif +#ifdef HAVE_CLOCKCTL + (void) fprintf(stderr, "\t\t[ -u user[:group] ] [ -i chrootdir ]\n"); +#endif + exit(2); + } + return; +} diff --git a/ntpd/jupiter.h b/ntpd/jupiter.h new file mode 100644 index 0000000..ed80b0c --- /dev/null +++ b/ntpd/jupiter.h @@ -0,0 +1,255 @@ +/* @(#) $Header$ (LBL) */ + +/* + * Rockwell Jupiter GPS receiver definitions + * + * This is all based on the "Zodiac GPS Receiver Family Designer's + * Guide" (dated 12/96) + */ + +#define JUPITER_SYNC 0x81ff /* sync word (book says 0xff81 !?!?) */ +#define JUPITER_ALL 0xffff /* disable all output messages */ + +/* Output messages (sent by the Jupiter board) */ +#define JUPITER_O_GPOS 1000 /* geodetic position status */ +#define JUPITER_O_EPOS 1001 /* ECEF position status */ +#define JUPITER_O_CHAN 1002 /* channel summary */ +#define JUPITER_O_VIS 1003 /* visible satellites */ +#define JUPITER_O_DGPS 1005 /* differential GPS status */ +#define JUPITER_O_MEAS 1007 /* channel measurement */ +#define JUPITER_O_ID 1011 /* receiver id */ +#define JUPITER_O_USER 1012 /* user-settings output */ +#define JUPITER_O_TEST 1100 /* built-in test results */ +#define JUPITER_O_MARK 1102 /* measurement time mark */ +#define JUPITER_O_PULSE 1108 /* UTC time mark pulse output */ +#define JUPITER_O_PORT 1130 /* serial port com parameters in use */ +#define JUPITER_O_EUP 1135 /* EEPROM update */ +#define JUPITER_O_ESTAT 1136 /* EEPROM status */ + +/* Input messages (sent to the Jupiter board) */ +#define JUPITER_I_PVTINIT 1200 /* geodetic position and velocity */ +#define JUPITER_I_USER 1210 /* user-defined datum */ +#define JUPITER_I_MAPSEL 1211 /* map datum select */ +#define JUPITER_I_ELEV 1212 /* satellite elevation mask control */ +#define JUPITER_I_CAND 1213 /* satellite candidate select */ +#define JUPITER_I_DGPS 1214 /* differential GPS control */ +#define JUPITER_I_COLD 1216 /* cold start control */ +#define JUPITER_I_VALID 1217 /* solution validity criteria */ +#define JUPITER_I_ALT 1219 /* user-entered altitude input */ +#define JUPITER_I_PLAT 1220 /* application platform control */ +#define JUPITER_I_NAV 1221 /* nav configuration */ +#define JUPITER_I_TEST 1300 /* preform built-in test command */ +#define JUPITER_I_RESTART 1303 /* restart command */ +#define JUPITER_I_PORT 1330 /* serial port com parameters */ +#define JUPITER_I_PROTO 1331 /* message protocol control */ +#define JUPITER_I_RDGPS 1351 /* raw DGPS RTCM SC-104 data */ + +struct jheader { + u_short sync; /* (JUPITER_SYNC) */ + u_short id; /* message id */ + u_short len; /* number of data short wordss (w/o cksum) */ + u_char reqid; /* JUPITER_REQID_MASK bits available as id */ + u_char flags; /* flags */ + u_short hsum; /* header cksum */ +}; + +#define JUPITER_REQID_MASK 0x3f /* bits available as id */ +#define JUPITER_FLAG_NAK 0x01 /* negative acknowledgement */ +#define JUPITER_FLAG_ACK 0x02 /* acknowledgement */ +#define JUPITER_FLAG_REQUEST 0x04 /* request ACK or NAK */ +#define JUPITER_FLAG_QUERY 0x08 /* request one shot output message */ +#define JUPITER_FLAG_LOG 0x20 /* request periodic output message */ +#define JUPITER_FLAG_CONN 0x40 /* enable periodic message */ +#define JUPITER_FLAG_DISC 0x80 /* disable periodic message */ + +#define JUPITER_H_FLAG_BITS \ + "\020\1NAK\2ACK\3REQUEST\4QUERY\5MBZ\6LOG\7CONN\10DISC" + +/* Log request messages (data payload when using JUPITER_FLAG_LOG) */ +struct jrequest { + u_short trigger; /* if 0, trigger on time trigger on + update (e.g. new almanac) */ + u_short interval; /* frequency in seconds */ + u_short offset; /* offset into minute */ + u_short dsum; /* checksum */ +}; + +/* JUPITER_O_GPOS (1000) */ +struct jgpos { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_short sseq; /* sat measurement sequence number */ + u_short navval; /* navigation soltuion validity */ + u_short navtype; /* navigation solution type */ + u_short nmeas; /* # of measurements used in solution */ + u_short polar; /* if 1 then polar navigation */ + u_short gweek; /* GPS week number */ + u_short sweek[2]; /* GPS seconds into week */ + u_short nsweek[2]; /* GPS nanoseconds into second */ + u_short utcday; /* 1 to 31 */ + u_short utcmon; /* 1 to 12 */ + u_short utcyear; /* 1980 to 2079 */ + u_short utchour; /* 0 to 23 */ + u_short utcmin; /* 0 to 59 */ + u_short utcsec; /* 0 to 59 */ + u_short utcnsec[2]; /* 0 to 999999999 */ + u_short lat[2]; /* latitude (radians) */ + u_short lon[2]; /* longitude (radians) */ + u_short height[2]; /* height (meters) */ + u_short gsep; /* geoidal separation */ + u_short speed[2]; /* ground speed (meters/sec) */ + u_short course; /* true course (radians) */ + u_short mvar; + u_short climb; + u_short mapd; + u_short herr[2]; + u_short verr[2]; + u_short terr[2]; + u_short hverr; + u_short bias[2]; + u_short biassd[2]; + u_short drift[2]; + u_short driftsd[2]; + u_short dsum; /* checksum */ +}; +#define JUPITER_O_GPOS_NAV_NOALT 0x01 /* altitude used */ +#define JUPITER_O_GPOS_NAV_NODGPS 0x02 /* no differential GPS */ +#define JUPITER_O_GPOS_NAV_NOSAT 0x04 /* not enough satellites */ +#define JUPITER_O_GPOS_NAV_MAXH 0x08 /* exceeded max EHPE */ +#define JUPITER_O_GPOS_NAV_MAXV 0x10 /* exceeded max EVPE */ + +/* JUPITER_O_CHAN (1002) */ +struct jchan { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_short sseq; /* sat measurement sequence number */ + u_short gweek; /* GPS week number */ + u_short sweek[2]; /* GPS seconds into week */ + u_short gpsns[2]; /* GPS nanoseconds from epoch */ + struct jchan2 { + u_short flags; /* flags */ + u_short prn; /* satellite PRN */ + u_short chan; /* channel number */ + } sat[12]; + u_short dsum; +}; + +/* JUPITER_O_VIS (1003) */ +struct jvis { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_short gdop; /* best possible GDOP */ + u_short pdop; /* best possible PDOP */ + u_short hdop; /* best possible HDOP */ + u_short vdop; /* best possible VDOP */ + u_short tdop; /* best possible TDOP */ + u_short nvis; /* number of visible satellites */ + struct jvis2 { + u_short prn; /* satellite PRN */ + u_short azi; /* satellite azimuth (radians) */ + u_short elev; /* satellite elevation (radians) */ + } sat[12]; + u_short dsum; /* checksum */ +}; + +/* JUPITER_O_ID (1011) */ +struct jid { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + char chans[20]; /* number of channels (ascii) */ + char vers[20]; /* software version (ascii) */ + char date[20]; /* software date (ascii) */ + char opts[20]; /* software options (ascii) */ + char reserved[20]; + u_short dsum; /* checksum */ +}; + +/* JUPITER_O_USER (1012) */ +struct juser { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_short status; /* operatinoal status */ + u_short coldtmo; /* cold start time-out */ + u_short dgpstmo; /* DGPS correction time-out*/ + u_short emask; /* elevation mask */ + u_short selcand[2]; /* selected candidate */ + u_short solflags; /* solution validity criteria */ + u_short nsat; /* number of satellites in track */ + u_short herr[2]; /* minimum expected horizontal error */ + u_short verr[2]; /* minimum expected vertical error */ + u_short platform; /* application platform */ + u_short dsum; /* checksum */ +}; + +/* JUPITER_O_PULSE (1108) */ +struct jpulse { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_short reserved[5]; + u_short sweek[2]; /* GPS seconds into week */ + short offs; /* GPS to UTC time offset (seconds) */ + u_short offns[2]; /* GPS to UTC offset (nanoseconds) */ + u_short flags; /* flags */ + u_short dsum; /* checksum */ +}; +#define JUPITER_O_PULSE_VALID 0x1 /* time mark validity */ +#define JUPITER_O_PULSE_UTC 0x2 /* GPS/UTC sync */ + +/* JUPITER_O_EUP (1135) */ +struct jeup { + u_short stime[2]; /* set time (10 ms ticks) */ + u_short seq; /* sequence number */ + u_char dataid; /* data id */ + u_char prn; /* satellite PRN */ + u_short dsum; /* checksum */ +}; + +/* JUPITER_I_RESTART (1303) */ +struct jrestart { + u_short seq; /* sequence number */ + u_short flags; + u_short dsum; /* checksum */ +}; +#define JUPITER_I_RESTART_INVRAM 0x01 +#define JUPITER_I_RESTART_INVEEPROM 0x02 +#define JUPITER_I_RESTART_INVRTC 0x04 +#define JUPITER_I_RESTART_COLD 0x80 + +/* JUPITER_I_PVTINIT (1200) */ +struct jpvtinit { + u_short flags; + u_short gweek; /* GPS week number */ + u_short sweek[2]; /* GPS seconds into week */ + u_short utcday; /* 1 to 31 */ + u_short utcmon; /* 1 to 12 */ + u_short utcyear; /* 1980 to 2079 */ + u_short utchour; /* 0 to 23 */ + u_short utcmin; /* 0 to 59 */ + u_short utcsec; /* 0 to 59 */ + u_short lat[2]; /* latitude (radians) */ + u_short lon[2]; /* longitude (radians) */ + u_short height[2]; /* height (meters) */ + u_short speed[2]; /* ground speed (meters/sec) */ + u_short course; /* true course (radians) */ + u_short climb; + u_short dsum; +}; +#define JUPITER_I_PVTINIT_FORCE 0x01 +#define JUPITER_I_PVTINIT_GPSVAL 0x02 +#define JUPITER_I_PVTINIT_UTCVAL 0x04 +#define JUPITER_I_PVTINIT_POSVAL 0x08 +#define JUPITER_I_PVTINIT_ALTVAL 0x10 +#define JUPITER_I_PVTINIT_SPDVAL 0x12 +#define JUPITER_I_PVTINIT_MAGVAL 0x14 +#define JUPITER_I_PVTINIT_CLIMBVAL 0x18 + +/* JUPITER_I_PLAT (1220) */ +struct jplat { + u_short seq; /* sequence number */ + u_short platform; /* application platform */ + u_short dsum; +}; +#define JUPITER_I_PLAT_DEFAULT 0 /* default dynamics */ +#define JUPITER_I_PLAT_LOW 2 /* pedestrian */ +#define JUPITER_I_PLAT_MED 5 /* land (e.g. automobile) */ +#define JUPITER_I_PLAT_HIGH 6 /* air */ diff --git a/ntpd/map_vme.c b/ntpd/map_vme.c new file mode 100644 index 0000000..e4569ce --- /dev/null +++ b/ntpd/map_vme.c @@ -0,0 +1,135 @@ +/********************************************************/ +/* map_vme.c */ +/* VME control of TrueTime VME-SG sync gen card */ +/* and TrueTime GPS-VME receiver card */ +/* Version for 700 series HPUX 9.0 */ +/* Richard E.Schmidt, US Naval Observatory, Washington */ +/* 27 March 94 */ +/********************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_GPSVME) +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <sys/types.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <sys/rtprio.h> /* for rtprio */ +#include <sys/lock.h> /* for plock */ +#include "/etc/conf/machine/vme2.h" +#include "/etc/conf/h/io.h" +#include "gps.h" + +/* GLOBALS */ +void *gps_base; +unsigned short *greg[NREGS]; +struct vme2_map_addr ma; /* memory mapped structure */ +int fd; /* file descriptor for VME */ + +void unmap_vme (); + +caddr_t +map_vme ( + char *filename + ) +{ + int ret; + caddr_t base; + struct vme2_io_testx tx; + caddr_t cp; + +#define VME_START_ADDR 0x00000 /* Starting address in A16N VME Space */ +#define VMESIZE 0xFF /* 256 bytes of A16N space length */ + + /* + To create the HP9000/700 series device file, /dev/vme2: + mknod /dev/vme2 c 44 0x0; chmod 600 /dev/vme2 + + Then must create /etc/vme.CFG and run /etc/vme_config and reboot. + */ + if ((fd = open (filename, O_RDWR)) < 0) { + printf("ERROR: VME bus adapter open failed. errno:%d\n", + errno); + if(errno == ENODEV) { + printf("ENODEV. Is driver in kernel? vme2 in dfile?\n"); + } + exit(errno); + } + tx.card_type = VME_A16; + tx.vme_addr = VME_START_ADDR; + tx.width = SHORT_WIDE; + + if(ioctl(fd, VME2_IO_TESTR, &tx)) { + printf("ioctl to test VME space failed. Errno: %d\n", + errno); + exit(errno); + } + if(tx.error) + printf("io_testr failed internal error %d\n",tx.error); + if(tx.access_result < 0) { + printf("io_testr failed\n"); + exit(2); + } + + /* If successful mmap the device */ + /* NOW MAP THE CARD */ + ma.card_type = VME_A16; + ma.vme_addr = VME_START_ADDR; + ma.size = VMESIZE; + + if(ioctl(fd, VME2_MAP_ADDR, &ma)) { + printf("ioctl to map VME space failed. Errno: %d\n", + errno); + exit(errno); + } + if(ma.error) { + printf("ioctl to map VME failed\n"); + exit(ENOMEM); + } + base = ma.user_addr; + return(base); +} + + +void +unmap_vme(void) +{ + if(ioctl(fd, VME2_UNMAP_ADDR, &ma)) + printf("ioctl to unmap VME space failed. Errno: %d\n", + errno); + close(fd); + return; +} + + +int +init_vme(boid) +{ + /* set up address offsets */ + + gps_base = map_vme (GPS_VME); + +/* offsets from base address: */ + + greg[0] = (unsigned short *)gps_base + GFRZ1; + greg[1] = (unsigned short *)gps_base + GUFRZ1; + greg[2] = (unsigned short *)gps_base + GREG1A; + greg[3] = (unsigned short *)gps_base + GREG1B; + greg[4] = (unsigned short *)gps_base + GREG1C; + greg[5] = (unsigned short *)gps_base + GREG1D; + greg[6] = (unsigned short *)gps_base + GREG1E; + + return (0); +} + +#else /* not (REFCLOCK && CLOCK_GPSVME) */ +int map_vme_bs; +#endif /* not (REFCLOCK && CLOCK_GPSVME) */ diff --git a/ntpd/ntp_config.c b/ntpd/ntp_config.c new file mode 100644 index 0000000..f1428b1 --- /dev/null +++ b/ntpd/ntp_config.c @@ -0,0 +1,2371 @@ +/* + * ntp_config.c - read and apply configuration information + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_NETINFO +# include <netinfo/ni.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_filegen.h" +#include "ntp_stdlib.h" +#include "ntp_config.h" +#include "ntp_cmdargs.h" + +#include <stdio.h> +#include <ctype.h> +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <signal.h> +#ifndef SIGCHLD +# define SIGCHLD SIGCLD +#endif +#if !defined(VMS) +# ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +# endif +#endif /* VMS */ + +#ifdef SYS_WINNT +# include <io.h> +extern HANDLE ResolverThreadHandle; +#endif /* SYS_WINNT */ + +#include <netdb.h> + +extern int priority_done; + +/* + * These routines are used to read the configuration file at + * startup time. An entry in the file must fit on a single line. + * Entries are processed as multiple tokens separated by white space + * Lines are considered terminated when a '#' is encountered. Blank + * lines are ignored. + */ +/* + * Translation table - keywords to function index + */ +struct keyword { + const char *text; + int keytype; +}; + +/* + * Command keywords + */ +static struct keyword keywords[] = { + { "automax", CONFIG_AUTOMAX }, + { "broadcast", CONFIG_BROADCAST }, + { "broadcastclient", CONFIG_BROADCASTCLIENT }, + { "broadcastdelay", CONFIG_BDELAY }, + { "calldelay", CONFIG_CDELAY}, +#ifdef OPENSSL + { "crypto", CONFIG_CRYPTO }, +#endif /* OPENSSL */ + { "controlkey", CONFIG_CONTROLKEY }, + { "disable", CONFIG_DISABLE }, + { "driftfile", CONFIG_DRIFTFILE }, + { "enable", CONFIG_ENABLE }, + { "filegen", CONFIG_FILEGEN }, + { "fudge", CONFIG_FUDGE }, + { "includefile", CONFIG_INCLUDEFILE }, + { "keys", CONFIG_KEYS }, + { "keysdir", CONFIG_KEYSDIR }, + { "logconfig", CONFIG_LOGCONFIG }, + { "logfile", CONFIG_LOGFILE }, + { "manycastclient", CONFIG_MANYCASTCLIENT }, + { "manycastserver", CONFIG_MANYCASTSERVER }, + { "multicastclient", CONFIG_MULTICASTCLIENT }, + { "peer", CONFIG_PEER }, + { "phone", CONFIG_PHONE }, + { "pidfile", CONFIG_PIDFILE }, + { "discard", CONFIG_DISCARD }, + { "requestkey", CONFIG_REQUESTKEY }, + { "restrict", CONFIG_RESTRICT }, + { "revoke", CONFIG_REVOKE }, + { "server", CONFIG_SERVER }, + { "setvar", CONFIG_SETVAR }, + { "statistics", CONFIG_STATISTICS }, + { "statsdir", CONFIG_STATSDIR }, + { "tick", CONFIG_ADJ }, + { "tinker", CONFIG_TINKER }, + { "tos", CONFIG_TOS }, + { "trap", CONFIG_TRAP }, + { "trustedkey", CONFIG_TRUSTEDKEY }, + { "ttl", CONFIG_TTL }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "peer", "server", "broadcast" modifier keywords + */ +static struct keyword mod_keywords[] = { + { "autokey", CONF_MOD_SKEY }, + { "burst", CONF_MOD_BURST }, + { "iburst", CONF_MOD_IBURST }, + { "key", CONF_MOD_KEY }, + { "maxpoll", CONF_MOD_MAXPOLL }, + { "minpoll", CONF_MOD_MINPOLL }, + { "mode", CONF_MOD_MODE }, /* refclocks */ + { "noselect", CONF_MOD_NOSELECT }, + { "prefer", CONF_MOD_PREFER }, + { "ttl", CONF_MOD_TTL }, /* NTP peers */ + { "version", CONF_MOD_VERSION }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "restrict" modifier keywords + */ +static struct keyword res_keywords[] = { + { "ignore", CONF_RES_IGNORE }, + { "limited", CONF_RES_LIMITED }, + { "kod", CONF_RES_DEMOBILIZE }, + { "lowpriotrap", CONF_RES_LPTRAP }, + { "mask", CONF_RES_MASK }, + { "nomodify", CONF_RES_NOMODIFY }, + { "nopeer", CONF_RES_NOPEER }, + { "noquery", CONF_RES_NOQUERY }, + { "noserve", CONF_RES_NOSERVE }, + { "notrap", CONF_RES_NOTRAP }, + { "notrust", CONF_RES_NOTRUST }, + { "ntpport", CONF_RES_NTPPORT }, + { "version", CONF_RES_VERSION }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "trap" modifier keywords + */ +static struct keyword trap_keywords[] = { + { "port", CONF_TRAP_PORT }, + { "interface", CONF_TRAP_INTERFACE }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "fudge" modifier keywords + */ +static struct keyword fudge_keywords[] = { + { "flag1", CONF_FDG_FLAG1 }, + { "flag2", CONF_FDG_FLAG2 }, + { "flag3", CONF_FDG_FLAG3 }, + { "flag4", CONF_FDG_FLAG4 }, + { "refid", CONF_FDG_REFID }, + { "stratum", CONF_FDG_STRATUM }, + { "time1", CONF_FDG_TIME1 }, + { "time2", CONF_FDG_TIME2 }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "filegen" modifier keywords + */ +static struct keyword filegen_keywords[] = { + { "disable", CONF_FGEN_FLAG_DISABLE }, + { "enable", CONF_FGEN_FLAG_ENABLE }, + { "file", CONF_FGEN_FILE }, + { "link", CONF_FGEN_FLAG_LINK }, + { "nolink", CONF_FGEN_FLAG_NOLINK }, + { "type", CONF_FGEN_TYPE }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "type" modifier keywords + */ +static struct keyword fgen_types[] = { + { "age", FILEGEN_AGE }, + { "day", FILEGEN_DAY }, + { "month", FILEGEN_MONTH }, + { "none", FILEGEN_NONE }, + { "pid", FILEGEN_PID }, + { "week", FILEGEN_WEEK }, + { "year", FILEGEN_YEAR }, + { "", CONFIG_UNKNOWN} +}; + +/* + * "enable", "disable" modifier keywords + */ +static struct keyword flags_keywords[] = { + { "auth", PROTO_AUTHENTICATE }, + { "bclient", PROTO_BROADCLIENT }, + { "calibrate", PROTO_CAL }, + { "kernel", PROTO_KERNEL }, + { "monitor", PROTO_MONITOR }, + { "ntp", PROTO_NTP }, + { "pps", PROTO_PPS }, + { "stats", PROTO_FILEGEN }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "discard" modifier keywords + */ +static struct keyword discard_keywords[] = { + { "average", CONF_DISCARD_AVERAGE }, + { "minimum", CONF_DISCARD_MINIMUM }, + { "monitor", CONF_DISCARD_MONITOR }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "tinker" modifier keywords + */ +static struct keyword tinker_keywords[] = { + { "step", CONF_CLOCK_MAX }, + { "panic", CONF_CLOCK_PANIC }, + { "dispersion", CONF_CLOCK_PHI }, + { "stepout", CONF_CLOCK_MINSTEP }, + { "allan", CONF_CLOCK_ALLAN }, + { "huffpuff", CONF_CLOCK_HUFFPUFF }, + { "freq", CONF_CLOCK_FREQ }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "tos" modifier keywords + */ +static struct keyword tos_keywords[] = { + { "minclock", CONF_TOS_MINCLOCK }, + { "minsane", CONF_TOS_MINSANE }, + { "floor", CONF_TOS_FLOOR }, + { "ceiling", CONF_TOS_CEILING }, + { "cohort", CONF_TOS_COHORT }, + { "", CONFIG_UNKNOWN } +}; + +#ifdef OPENSSL +/* + * "crypto" modifier keywords + */ +static struct keyword crypto_keywords[] = { + { "cert", CONF_CRYPTO_CERT }, + { "gqpar", CONF_CRYPTO_GQPAR }, + { "host", CONF_CRYPTO_RSA }, + { "iffpar", CONF_CRYPTO_IFFPAR }, + { "leap", CONF_CRYPTO_LEAP }, + { "mvpar", CONF_CRYPTO_MVPAR }, + { "pw", CONF_CRYPTO_PW }, + { "randfile", CONF_CRYPTO_RAND }, + { "sign", CONF_CRYPTO_SIGN }, + { "", CONFIG_UNKNOWN } +}; +#endif /* OPENSSL */ + +/* + * Address type selection, IPv4 or IPv4. + * Used on various lines. + */ +static struct keyword addr_type[] = { + { "-4", CONF_ADDR_IPV4 }, + { "-6", CONF_ADDR_IPV6 }, + { "", CONFIG_UNKNOWN } +}; + +/* + * "logconfig" building blocks + */ +struct masks { + const char *name; + unsigned long mask; +}; + +static struct masks logcfg_class[] = { + { "clock", NLOG_OCLOCK }, + { "peer", NLOG_OPEER }, + { "sync", NLOG_OSYNC }, + { "sys", NLOG_OSYS }, + { (char *)0, 0 } +}; + +static struct masks logcfg_item[] = { + { "info", NLOG_INFO }, + { "allinfo", NLOG_SYSINFO|NLOG_PEERINFO|NLOG_CLOCKINFO|NLOG_SYNCINFO }, + { "events", NLOG_EVENT }, + { "allevents", NLOG_SYSEVENT|NLOG_PEEREVENT|NLOG_CLOCKEVENT|NLOG_SYNCEVENT }, + { "status", NLOG_STATUS }, + { "allstatus", NLOG_SYSSTATUS|NLOG_PEERSTATUS|NLOG_CLOCKSTATUS|NLOG_SYNCSTATUS }, + { "statistics", NLOG_STATIST }, + { "allstatistics", NLOG_SYSSTATIST|NLOG_PEERSTATIST|NLOG_CLOCKSTATIST|NLOG_SYNCSTATIST }, + { "allclock", (NLOG_INFO|NLOG_STATIST|NLOG_EVENT|NLOG_STATUS)<<NLOG_OCLOCK }, + { "allpeer", (NLOG_INFO|NLOG_STATIST|NLOG_EVENT|NLOG_STATUS)<<NLOG_OPEER }, + { "allsys", (NLOG_INFO|NLOG_STATIST|NLOG_EVENT|NLOG_STATUS)<<NLOG_OSYS }, + { "allsync", (NLOG_INFO|NLOG_STATIST|NLOG_EVENT|NLOG_STATUS)<<NLOG_OSYNC }, + { "all", NLOG_SYSMASK|NLOG_PEERMASK|NLOG_CLOCKMASK|NLOG_SYNCMASK }, + { (char *)0, 0 } +}; + +/* + * Limits on things + */ +#define MAXTOKENS 20 /* 20 tokens on line */ +#define MAXLINE 1024 /* maximum length of line */ +#define MAXPHONE 5 /* maximum number of phone strings */ +#define MAXPPS 20 /* maximum length of PPS device string */ +#define MAXINCLUDELEVEL 5 /* maximum include file levels */ + +/* + * Miscellaneous macros + */ +#define STRSAME(s1, s2) (*(s1) == *(s2) && strcmp((s1), (s2)) == 0) +#define ISEOL(c) ((c) == '#' || (c) == '\n' || (c) == '\0') +#define ISSPACE(c) ((c) == ' ' || (c) == '\t') +#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) + +#define KEY_TYPE_MD5 4 + +/* + * File descriptor used by the resolver save routines, and temporary file + * name. + */ +int call_resolver = 1; /* ntp-genkeys sets this to 0, for example */ +static FILE *res_fp; +#ifndef SYS_WINNT +static char res_file[20]; /* enough for /tmp/ntpXXXXXX\0 */ +#define RES_TEMPFILE "/tmp/ntpXXXXXX" +#else +static char res_file[MAX_PATH]; +#endif /* SYS_WINNT */ + +/* + * Definitions of things either imported from or exported to outside + */ +char const *progname; +char sys_phone[MAXPHONE][MAXDIAL]; /* ACTS phone numbers */ +char *keysdir = NTP_KEYSDIR; /* crypto keys directory */ +char pps_device[MAXPPS + 1]; /* PPS device name */ +#if defined(HAVE_SCHED_SETSCHEDULER) +int config_priority_override = 0; +int config_priority; +#endif + +const char *config_file; +#ifdef HAVE_NETINFO + struct netinfo_config_state *config_netinfo = NULL; + int check_netinfo = 1; +#endif /* HAVE_NETINFO */ +#ifdef SYS_WINNT + char *alt_config_file; + LPTSTR temp; + char config_file_storage[MAX_PATH]; + char alt_config_file_storage[MAX_PATH]; +#endif /* SYS_WINNT */ + +#ifdef HAVE_NETINFO +/* + * NetInfo configuration state + */ +struct netinfo_config_state { + void *domain; /* domain with config */ + ni_id config_dir; /* ID config dir */ + int prop_index; /* current property */ + int val_index; /* current value */ + char **val_list; /* value list */ +}; +#endif + +/* + * Function prototypes + */ +static unsigned long get_pfxmatch P((char **, struct masks *)); +static unsigned long get_match P((char *, struct masks *)); +static unsigned long get_logmask P((char *)); +#ifdef HAVE_NETINFO +static struct netinfo_config_state *get_netinfo_config P((void)); +static void free_netinfo_config P((struct netinfo_config_state *)); +static int gettokens_netinfo P((struct netinfo_config_state *, char **, int *)); +#endif +static int gettokens P((FILE *, char *, char **, int *)); +static int matchkey P((char *, struct keyword *, int)); +static int getnetnum P((const char *, struct sockaddr_storage *, int)); +static void save_resolve P((char *, int, int, int, int, u_int, int, + keyid_t, u_char *)); +static void do_resolve_internal P((void)); +static void abort_resolve P((void)); +#if !defined(VMS) && !defined(SYS_WINNT) +static RETSIGTYPE catchchild P((int)); +#endif /* VMS */ + +/* + * get_pfxmatch - find value for prefixmatch + * and update char * accordingly + */ +static unsigned long +get_pfxmatch( + char ** s, + struct masks *m + ) +{ + while (m->name) { + if (strncmp(*s, m->name, strlen(m->name)) == 0) { + *s += strlen(m->name); + return m->mask; + } else { + m++; + } + } + return 0; +} + +/* + * get_match - find logmask value + */ +static unsigned long +get_match( + char *s, + struct masks *m + ) +{ + while (m->name) { + if (strcmp(s, m->name) == 0) { + return m->mask; + } else { + m++; + } + } + return 0; +} + +/* + * get_logmask - build bitmask for ntp_syslogmask + */ +static unsigned long +get_logmask( + char *s + ) +{ + char *t; + unsigned long offset; + unsigned long mask; + + t = s; + offset = get_pfxmatch(&t, logcfg_class); + mask = get_match(t, logcfg_item); + + if (mask) + return mask << offset; + else + msyslog(LOG_ERR, "logconfig: illegal argument %s - ignored", s); + + return 0; +} + + +/* + * getconfig - get command line options and read the configuration file + */ +void +getconfig( + int argc, + char *argv[] + ) +{ + register int i; + int c; + int errflg; + int istart; + int peerversion; + int minpoll; + int maxpoll; + int ttl; + long stratum; + unsigned long ul; + keyid_t peerkey; + u_char *peerkeystr; + u_long fudgeflag; + u_int peerflags; + int hmode; + struct sockaddr_storage peeraddr; + struct sockaddr_storage maskaddr; + FILE *fp[MAXINCLUDELEVEL+1]; + FILE *includefile; + int includelevel = 0; + char line[MAXLINE]; + char *(tokens[MAXTOKENS]); + int ntokens = 0; + int tok = CONFIG_UNKNOWN; + struct interface *localaddr; + struct refclockstat clock_stat; + FILEGEN *filegen; + + /* + * Initialize, initialize + */ + errflg = 0; + /* HMS: don't initialize debug to 0 here! */ +#ifndef SYS_WINNT + config_file = CONFIG_FILE; +#else + temp = CONFIG_FILE; + if (!ExpandEnvironmentStrings((LPCTSTR)temp, (LPTSTR)config_file_storage, (DWORD)sizeof(config_file_storage))) { + msyslog(LOG_ERR, "ExpandEnvironmentStrings CONFIG_FILE failed: %m\n"); + exit(1); + } + config_file = config_file_storage; + + temp = ALT_CONFIG_FILE; + if (!ExpandEnvironmentStrings((LPCTSTR)temp, (LPTSTR)alt_config_file_storage, (DWORD)sizeof(alt_config_file_storage))) { + msyslog(LOG_ERR, "ExpandEnvironmentStrings ALT_CONFIG_FILE failed: %m\n"); + exit(1); + } + alt_config_file = alt_config_file_storage; + +#endif /* SYS_WINNT */ + progname = argv[0]; + res_fp = NULL; + memset((char *)sys_phone, 0, sizeof(sys_phone)); + ntp_syslogmask = NLOG_SYNCMASK; /* set more via logconfig */ + + /* + * install a non default variable with this daemon version + */ + (void) sprintf(line, "daemon_version=\"%s\"", Version); + set_sys_var(line, strlen(line)+1, RO); + + /* + * Say how we're setting the time of day + */ + (void) sprintf(line, "settimeofday=\"%s\"", set_tod_using); + set_sys_var(line, strlen(line)+1, RO); + + /* + * Initialize the loop. + */ + loop_config(LOOP_DRIFTINIT, 0.); + + getCmdOpts(argc, argv); + + if ( + (fp[0] = fopen(FindConfig(config_file), "r")) == NULL +#ifdef HAVE_NETINFO + /* If there is no config_file, try NetInfo. */ + && check_netinfo && !(config_netinfo = get_netinfo_config()) +#endif /* HAVE_NETINFO */ + ) { + fprintf(stderr, "getconfig: Couldn't open <%s>\n", FindConfig(config_file)); + msyslog(LOG_INFO, "getconfig: Couldn't open <%s>", FindConfig(config_file)); +#ifdef SYS_WINNT + /* Under WinNT try alternate_config_file name, first NTP.CONF, then NTP.INI */ + + if ((fp[0] = fopen(FindConfig(alt_config_file), "r")) == NULL) { + + /* + * Broadcast clients can sometimes run without + * a configuration file. + */ + + fprintf(stderr, "getconfig: Couldn't open <%s>\n", FindConfig(alt_config_file)); + msyslog(LOG_INFO, "getconfig: Couldn't open <%s>", FindConfig(alt_config_file)); + return; + } +#else /* not SYS_WINNT */ + return; +#endif /* not SYS_WINNT */ + } + + for (;;) { + if (fp[includelevel]) + tok = gettokens(fp[includelevel], line, tokens, &ntokens); +#ifdef HAVE_NETINFO + else + tok = gettokens_netinfo(config_netinfo, tokens, &ntokens); +#endif /* HAVE_NETINFO */ + + if (tok == CONFIG_UNKNOWN) { + if (includelevel > 0) { + fclose(fp[includelevel--]); + continue; + } else { + break; + } + } + + switch(tok) { + case CONFIG_PEER: + case CONFIG_SERVER: + case CONFIG_MANYCASTCLIENT: + case CONFIG_BROADCAST: + if (tok == CONFIG_PEER) + hmode = MODE_ACTIVE; + else if (tok == CONFIG_SERVER) + hmode = MODE_CLIENT; + else if (tok == CONFIG_MANYCASTCLIENT) + hmode = MODE_CLIENT; + else + hmode = MODE_BROADCAST; + + if (ntokens < 2) { + msyslog(LOG_ERR, + "No address for %s, line ignored", + tokens[0]); + break; + } + + istart = 1; + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + switch (matchkey(tokens[istart], addr_type, 0)) { + case CONF_ADDR_IPV4: + peeraddr.ss_family = AF_INET; + istart++; + break; + case CONF_ADDR_IPV6: + peeraddr.ss_family = AF_INET6; + istart++; + break; + } + + if (!getnetnum(tokens[istart], &peeraddr, 0)) { + errflg = -1; + } else { + errflg = 0; + + if ( +#ifdef REFCLOCK + !ISREFCLOCKADR(&peeraddr) && +#endif + ISBADADR(&peeraddr)) { + msyslog(LOG_ERR, + "attempt to configure invalid address %s", + stoa(&peeraddr)); + break; + } + /* + * Shouldn't be able to specify multicast + * address for server/peer! + * and unicast address for manycastclient! + */ + if (peeraddr.ss_family == AF_INET) { + if (((tok == CONFIG_SERVER) || + (tok == CONFIG_PEER)) && +#ifdef REFCLOCK + !ISREFCLOCKADR(&peeraddr) && +#endif + IN_CLASSD(ntohl(((struct sockaddr_in*)&peeraddr)->sin_addr.s_addr))) { + msyslog(LOG_ERR, + "attempt to configure invalid address %s", + stoa(&peeraddr)); + break; + } + if ((tok == CONFIG_MANYCASTCLIENT) && + !IN_CLASSD(ntohl(((struct sockaddr_in*)&peeraddr)->sin_addr.s_addr))) { + msyslog(LOG_ERR, + "attempt to configure invalid address %s", + stoa(&peeraddr)); + break; + } + } + else if(peeraddr.ss_family == AF_INET6) { + if (((tok == CONFIG_SERVER) || + (tok == CONFIG_PEER)) && +#ifdef REFCLOCK + !ISREFCLOCKADR(&peeraddr) && +#endif + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)&peeraddr)->sin6_addr)) { + msyslog(LOG_ERR, + "attempt to configure in valid address %s", + stoa(&peeraddr)); + break; + } + if ((tok == CONFIG_MANYCASTCLIENT) && + !IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)&peeraddr)->sin6_addr)) { + msyslog(LOG_ERR, + "attempt to configure in valid address %s", + stoa(&peeraddr)); + break; + } + } + } + + peerversion = NTP_VERSION; + minpoll = NTP_MINDPOLL; + maxpoll = NTP_MAXDPOLL; + peerkey = 0; + peerkeystr = (u_char *)"*"; + peerflags = 0; + ttl = 0; + istart++; + for (i = istart; i < ntokens; i++) + switch (matchkey(tokens[i], mod_keywords, 1)) { + case CONF_MOD_VERSION: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "peer/server version requires an argument"); + errflg = 1; + break; + } + peerversion = atoi(tokens[++i]); + if ((u_char)peerversion > NTP_VERSION + || (u_char)peerversion < NTP_OLDVERSION) { + msyslog(LOG_ERR, + "inappropriate version number %s, line ignored", + tokens[i]); + errflg = 1; + } + break; + + case CONF_MOD_KEY: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "key: argument required"); + errflg = 1; + break; + } + peerkey = (int)atol(tokens[++i]); + peerflags |= FLAG_AUTHENABLE; + break; + + case CONF_MOD_MINPOLL: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "minpoll: argument required"); + errflg = 1; + break; + } + minpoll = atoi(tokens[++i]); + if (minpoll < NTP_MINPOLL) { + msyslog(LOG_INFO, + "minpoll: provided value (%d) is below minimum (%d)", + minpoll, NTP_MINPOLL); + minpoll = NTP_MINPOLL; + } + break; + + case CONF_MOD_MAXPOLL: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "maxpoll: argument required" + ); + errflg = 1; + break; + } + maxpoll = atoi(tokens[++i]); + if (maxpoll > NTP_MAXPOLL) { + msyslog(LOG_INFO, + "maxpoll: provided value (%d) is above maximum (%d)", + maxpoll, NTP_MAXPOLL); + maxpoll = NTP_MAXPOLL; + } + break; + + case CONF_MOD_PREFER: + peerflags |= FLAG_PREFER; + break; + + case CONF_MOD_NOSELECT: + peerflags |= FLAG_NOSELECT; + break; + + case CONF_MOD_BURST: + peerflags |= FLAG_BURST; + break; + + case CONF_MOD_IBURST: + peerflags |= FLAG_IBURST; + break; +#ifdef OPENSSL + case CONF_MOD_SKEY: + peerflags |= FLAG_SKEY | + FLAG_AUTHENABLE; + break; +#endif /* OPENSSL */ + + case CONF_MOD_TTL: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "ttl: argument required"); + errflg = 1; + break; + } + ttl = atoi(tokens[++i]); + if (ttl >= MAX_TTL) { + msyslog(LOG_ERR, + "ttl: invalid argument"); + errflg = 1; + } + break; + + case CONF_MOD_MODE: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "mode: argument required"); + errflg = 1; + break; + } + ttl = atoi(tokens[++i]); + break; + + case CONFIG_UNKNOWN: + errflg = 1; + break; + } + if (minpoll > maxpoll) { + msyslog(LOG_ERR, + "config error: minpoll > maxpoll"); + errflg = 1; + } + if (errflg == 0) { + if (peer_config(&peeraddr, + ANY_INTERFACE_CHOOSE(&peeraddr), hmode, + peerversion, minpoll, maxpoll, peerflags, + ttl, peerkey, peerkeystr) == 0) { + msyslog(LOG_ERR, + "configuration of %s failed", + stoa(&peeraddr)); + } + if (tok == CONFIG_MANYCASTCLIENT) + proto_config(PROTO_MULTICAST_ADD, + 0, 0., &peeraddr); + + } else if (errflg == -1) { + save_resolve(tokens[1], hmode, peerversion, + minpoll, maxpoll, peerflags, ttl, + peerkey, peerkeystr); + } + break; + + case CONFIG_DRIFTFILE: + if (ntokens >= 2) + stats_config(STATS_FREQ_FILE, tokens[1]); + else + stats_config(STATS_FREQ_FILE, (char *)0); + break; + + case CONFIG_PIDFILE: + if (ntokens >= 2) + stats_config(STATS_PID_FILE, tokens[1]); + else + stats_config(STATS_PID_FILE, (char *)0); + break; + + case CONFIG_INCLUDEFILE: + if (ntokens < 2) { + msyslog(LOG_ERR, "includefile needs one argument"); + break; + } + if (includelevel >= MAXINCLUDELEVEL) { + fprintf(stderr, "getconfig: Maximum include file level exceeded.\n"); + msyslog(LOG_INFO, "getconfig: Maximum include file level exceeded."); + break; + } + includefile = fopen(FindConfig(tokens[1]), "r"); + if (includefile == NULL) { + fprintf(stderr, "getconfig: Couldn't open <%s>\n", FindConfig(tokens[1])); + msyslog(LOG_INFO, "getconfig: Couldn't open <%s>", FindConfig(tokens[1])); + break; + } + fp[++includelevel] = includefile; + break; + + case CONFIG_LOGFILE: + if (ntokens >= 2) { + FILE *new_file; + + new_file = fopen(tokens[1], "a"); + if (new_file != NULL) { + NLOG(NLOG_SYSINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "logging to file %s", tokens[1]); + if (syslog_file != NULL && + fileno(syslog_file) != fileno(new_file)) + (void)fclose(syslog_file); + + syslog_file = new_file; + syslogit = 0; + } + else + msyslog(LOG_ERR, + "Cannot open log file %s", + tokens[1]); + } + else + msyslog(LOG_ERR, "logfile needs one argument"); + break; + + case CONFIG_LOGCONFIG: + for (i = 1; i < ntokens; i++) + { + int add = 1; + int equals = 0; + char * s = &tokens[i][0]; + + switch (*s) { + case '+': + case '-': + case '=': + add = *s == '+'; + equals = *s == '='; + s++; + break; + + default: + break; + } + if (equals) { + ntp_syslogmask = get_logmask(s); + } else { + if (add) { + ntp_syslogmask |= get_logmask(s); + } else { + ntp_syslogmask &= ~get_logmask(s); + } + } +#ifdef DEBUG + if (debug) + printf("ntp_syslogmask = 0x%08lx (%s)\n", ntp_syslogmask, tokens[i]); +#endif + } + break; + + case CONFIG_BROADCASTCLIENT: + proto_config(PROTO_BROADCLIENT, 1, 0., NULL); + break; + + case CONFIG_MULTICASTCLIENT: + case CONFIG_MANYCASTSERVER: + if (ntokens > 1) { + istart = 1; + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + switch (matchkey(tokens[istart], + addr_type, 0)) { + case CONF_ADDR_IPV4: + peeraddr.ss_family = AF_INET; + istart++; + break; + case CONF_ADDR_IPV6: + peeraddr.ss_family = AF_INET6; + istart++; + break; + } + /* + * Abuse maskaddr to store the prefered ip + * version. + */ + memset((char *)&maskaddr, 0, sizeof(maskaddr)); + maskaddr.ss_family = peeraddr.ss_family; + + for (i = istart; i < ntokens; i++) { + memset((char *)&peeraddr, 0, + sizeof(peeraddr)); + peeraddr.ss_family = maskaddr.ss_family; + if (getnetnum(tokens[i], &peeraddr, 1)) + proto_config(PROTO_MULTICAST_ADD, + 0, 0., &peeraddr); + } + } else + proto_config(PROTO_MULTICAST_ADD, + 0, 0., NULL); + if (tok == CONFIG_MULTICASTCLIENT) + sys_bclient = 1; + else if (tok == CONFIG_MANYCASTSERVER) + sys_manycastserver = 1; + break; + + case CONFIG_KEYS: + if (ntokens >= 2) { + getauthkeys(tokens[1]); + } + break; + + case CONFIG_KEYSDIR: + if (ntokens < 2) { + msyslog(LOG_ERR, + "Keys directory name required"); + break; + } + keysdir = emalloc(strlen(tokens[1]) + 1); + strcpy(keysdir, tokens[1]); + break; + + case CONFIG_TINKER: + for (i = 1; i < ntokens; i++) { + int temp; + double ftemp; + + temp = matchkey(tokens[i++], tinker_keywords, 1); + if (i > ntokens - 1) { + msyslog(LOG_ERR, + "tinker: missing argument"); + errflg++; + break; + } + sscanf(tokens[i], "%lf", &ftemp); + switch(temp) { + + case CONF_CLOCK_MAX: + loop_config(LOOP_MAX, ftemp); + break; + + case CONF_CLOCK_PANIC: + loop_config(LOOP_PANIC, ftemp); + break; + + case CONF_CLOCK_PHI: + loop_config(LOOP_PHI, ftemp); + break; + + case CONF_CLOCK_MINSTEP: + loop_config(LOOP_MINSTEP, ftemp); + break; + + case CONF_CLOCK_ALLAN: + loop_config(LOOP_ALLAN, ftemp); + break; + + case CONF_CLOCK_HUFFPUFF: + loop_config(LOOP_HUFFPUFF, ftemp); + break; + + case CONF_CLOCK_FREQ: + loop_config(LOOP_FREQ, ftemp); + break; + } + } + break; + + case CONFIG_TOS: + for (i = 1; i < ntokens; i++) { + int temp; + double ftemp; + + temp = matchkey(tokens[i++], tos_keywords, 1); + if (i > ntokens - 1) { + msyslog(LOG_ERR, + "tinker: missing argument"); + errflg++; + break; + } + sscanf(tokens[i], "%lf", &ftemp); + switch(temp) { + + case CONF_TOS_MINCLOCK: + proto_config(PROTO_MINCLOCK, 0, ftemp, NULL); + break; + + case CONF_TOS_MINSANE: + proto_config(PROTO_MINSANE, 0, ftemp, NULL); + break; + + case CONF_TOS_FLOOR: + proto_config(PROTO_FLOOR, 0, ftemp, NULL); + break; + + case CONF_TOS_CEILING: + proto_config(PROTO_CEILING, 0, ftemp, NULL); + break; + + case CONF_TOS_COHORT: + proto_config(PROTO_COHORT, 0, ftemp, NULL); + break; + } + } + break; + + case CONFIG_TTL: + for (i = 1; i < ntokens && i < MAX_TTL; i++) { + sys_ttl[i - 1] = (u_char) atoi(tokens[i]); + sys_ttlmax = i - 1; + } + break; + + case CONFIG_DISCARD: + for (i = 1; i < ntokens; i++) { + int temp; + + temp = matchkey(tokens[i++], + discard_keywords, 1); + if (i > ntokens - 1) { + msyslog(LOG_ERR, + "discard: missing argument"); + errflg++; + break; + } + switch(temp) { + case CONF_DISCARD_AVERAGE: + res_avg_interval = atoi(tokens[i++]); + break; + + case CONF_DISCARD_MINIMUM: + res_min_interval = atoi(tokens[i++]); + break; + + case CONF_DISCARD_MONITOR: + mon_age = atoi(tokens[i++]); + break; + + default: + msyslog(LOG_ERR, + "discard: unknown keyword"); + break; + } + } + break; + +#ifdef OPENSSL + case CONFIG_REVOKE: + if (ntokens >= 2) + sys_revoke = (u_char) max(atoi(tokens[1]), KEY_REVOKE); + break; + + case CONFIG_AUTOMAX: + if (ntokens >= 2) + sys_automax = 1 << max(atoi(tokens[1]), 10); + break; + + case CONFIG_CRYPTO: + if (ntokens == 1) { + crypto_config(CRYPTO_CONF_NONE, NULL); + break; + } + for (i = 1; i < ntokens; i++) { + int temp; + + temp = matchkey(tokens[i++], + crypto_keywords, 1); + if (i > ntokens - 1) { + msyslog(LOG_ERR, + "crypto: missing argument"); + errflg++; + break; + } + switch(temp) { + + case CONF_CRYPTO_CERT: + crypto_config(CRYPTO_CONF_CERT, + tokens[i]); + break; + + case CONF_CRYPTO_RSA: + crypto_config(CRYPTO_CONF_PRIV, + tokens[i]); + break; + + case CONF_CRYPTO_IFFPAR: + crypto_config(CRYPTO_CONF_IFFPAR, + tokens[i]); + break; + + case CONF_CRYPTO_GQPAR: + crypto_config(CRYPTO_CONF_GQPAR, + tokens[i]); + break; + + case CONF_CRYPTO_MVPAR: + crypto_config(CRYPTO_CONF_MVPAR, + tokens[i]); + break; + + case CONF_CRYPTO_LEAP: + crypto_config(CRYPTO_CONF_LEAP, + tokens[i]); + break; + + case CONF_CRYPTO_PW: + crypto_config(CRYPTO_CONF_PW, + tokens[i]); + break; + + case CONF_CRYPTO_RAND: + crypto_config(CRYPTO_CONF_RAND, + tokens[i]); + break; + + case CONF_CRYPTO_SIGN: + crypto_config(CRYPTO_CONF_SIGN, + tokens[i]); + break; + + default: + msyslog(LOG_ERR, + "crypto: unknown keyword"); + break; + } + } + break; +#endif /* OPENSSL */ + + case CONFIG_RESTRICT: + if (ntokens < 2) { + msyslog(LOG_ERR, "restrict requires an address"); + break; + } + istart = 1; + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + switch (matchkey(tokens[istart], addr_type, 0)) { + case CONF_ADDR_IPV4: + peeraddr.ss_family = AF_INET; + istart++; + break; + case CONF_ADDR_IPV6: + peeraddr.ss_family = AF_INET6; + istart++; + break; + } + + /* + * Assume default means an IPv4 address, except + * if forced by a -4 or -6. + */ + if (STREQ(tokens[istart], "default")) { + if (peeraddr.ss_family == 0) + peeraddr.ss_family = AF_INET; + } else if (!getnetnum(tokens[istart], &peeraddr, 1)) + break; + + /* + * Use peerversion as flags, peerkey as mflags. Ick. + */ + peerversion = 0; + peerkey = 0; + errflg = 0; + SET_HOSTMASK(&maskaddr, peeraddr.ss_family); + istart++; + for (i = istart; i < ntokens; i++) { + switch (matchkey(tokens[i], res_keywords, 1)) { + case CONF_RES_MASK: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "mask keyword needs argument"); + errflg++; + break; + } + i++; + if (!getnetnum(tokens[i], &maskaddr, 1)) + errflg++; + break; + + case CONF_RES_IGNORE: + peerversion |= RES_IGNORE; + break; + + case CONF_RES_NOSERVE: + peerversion |= RES_DONTSERVE; + break; + + case CONF_RES_NOTRUST: + peerversion |= RES_DONTTRUST; + break; + + case CONF_RES_NOQUERY: + peerversion |= RES_NOQUERY; + break; + + case CONF_RES_NOMODIFY: + peerversion |= RES_NOMODIFY; + break; + + case CONF_RES_NOPEER: + peerversion |= RES_NOPEER; + break; + + case CONF_RES_NOTRAP: + peerversion |= RES_NOTRAP; + break; + + case CONF_RES_LPTRAP: + peerversion |= RES_LPTRAP; + break; + + case CONF_RES_NTPPORT: + peerkey |= RESM_NTPONLY; + break; + + case CONF_RES_VERSION: + peerversion |= RES_VERSION; + break; + + case CONF_RES_DEMOBILIZE: + peerversion |= RES_DEMOBILIZE; + break; + + case CONF_RES_LIMITED: + peerversion |= RES_LIMITED; + break; + + case CONFIG_UNKNOWN: + errflg++; + break; + } + } + if (SOCKNUL(&peeraddr)) + ANYSOCK(&maskaddr); + if (!errflg) + hack_restrict(RESTRICT_FLAGS, &peeraddr, &maskaddr, + (int)peerkey, peerversion); + break; + + case CONFIG_BDELAY: + if (ntokens >= 2) { + double tmp; + + if (sscanf(tokens[1], "%lf", &tmp) != 1) { + msyslog(LOG_ERR, + "broadcastdelay value %s undecodable", + tokens[1]); + } else { + proto_config(PROTO_BROADDELAY, 0, tmp, NULL); + } + } + break; + + case CONFIG_CDELAY: + if (ntokens >= 2) { + u_long ui; + + if (sscanf(tokens[1], "%ld", &ui) != 1) + msyslog(LOG_ERR, + "illegal value - line ignored"); + else + proto_config(PROTO_CALLDELAY, ui, 0, NULL); + } + break; + + case CONFIG_TRUSTEDKEY: + for (i = 1; i < ntokens; i++) { + keyid_t tkey; + + tkey = atol(tokens[i]); + if (tkey == 0) { + msyslog(LOG_ERR, + "trusted key %s unlikely", + tokens[i]); + } else { + authtrust(tkey, 1); + } + } + break; + + case CONFIG_REQUESTKEY: + if (ntokens >= 2) { + if (!atouint(tokens[1], &ul)) { + msyslog(LOG_ERR, + "%s is undecodable as request key", + tokens[1]); + } else if (ul == 0) { + msyslog(LOG_ERR, + "%s makes a poor request keyid", + tokens[1]); + } else { +#ifdef DEBUG + if (debug > 3) + printf( + "set info_auth_key to %08lx\n", ul); +#endif + info_auth_keyid = (keyid_t)ul; + } + } + break; + + case CONFIG_CONTROLKEY: + if (ntokens >= 2) { + keyid_t ckey; + + ckey = atol(tokens[1]); + if (ckey == 0) { + msyslog(LOG_ERR, + "%s makes a poor control keyid", + tokens[1]); + } else { + ctl_auth_keyid = ckey; + } + } + break; + + case CONFIG_TRAP: + if (ntokens < 2) { + msyslog(LOG_ERR, + "no address for trap command, line ignored"); + break; + } + istart = 1; + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + switch (matchkey(tokens[istart], addr_type, 0)) { + case CONF_ADDR_IPV4: + peeraddr.ss_family = AF_INET; + istart++; + break; + case CONF_ADDR_IPV6: + peeraddr.ss_family = AF_INET6; + istart++; + break; + } + + if (!getnetnum(tokens[istart], &peeraddr, 1)) + break; + + /* + * Use peerversion for port number. Barf. + */ + errflg = 0; + peerversion = 0; + localaddr = 0; + istart++; + for (i = istart; i < ntokens-1; i++) + switch (matchkey(tokens[i], trap_keywords, 1)) { + case CONF_TRAP_PORT: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "trap port requires an argument"); + errflg = 1; + break; + } + peerversion = atoi(tokens[++i]); + if (peerversion <= 0 + || peerversion > 32767) { + msyslog(LOG_ERR, + "invalid port number %s, trap ignored", + tokens[i]); + errflg = 1; + } + break; + + case CONF_TRAP_INTERFACE: + if (i >= ntokens-1) { + msyslog(LOG_ERR, + "trap interface requires an argument"); + errflg = 1; + break; + } + + memset((char *)&maskaddr, 0, + sizeof(maskaddr)); + maskaddr.ss_family = peeraddr.ss_family; + if (!getnetnum(tokens[++i], + &maskaddr, 1)) { + errflg = 1; + break; + } + + localaddr = findinterface(&maskaddr); + if (localaddr == NULL) { + msyslog(LOG_ERR, + "can't find interface with address %s", + stoa(&maskaddr)); + errflg = 1; + } + break; + + case CONFIG_UNKNOWN: + errflg++; + break; + } + + if (!errflg) { + if (peerversion != 0) + ((struct sockaddr_in6*)&peeraddr)->sin6_port = htons( (u_short) peerversion); + else + ((struct sockaddr_in6*)&peeraddr)->sin6_port = htons(TRAPPORT); + if (localaddr == NULL) + localaddr = ANY_INTERFACE_CHOOSE(&peeraddr); + if (!ctlsettrap(&peeraddr, localaddr, 0, + NTP_VERSION)) + msyslog(LOG_ERR, + "can't set trap for %s, no resources", + stoa(&peeraddr)); + } + break; + + case CONFIG_FUDGE: + if (ntokens < 2) { + msyslog(LOG_ERR, + "no address for fudge command, line ignored"); + break; + } + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + if (!getnetnum(tokens[1], &peeraddr, 1)) + break; + + if (!ISREFCLOCKADR(&peeraddr)) { + msyslog(LOG_ERR, + "%s is inappropriate address for the fudge command, line ignored", + stoa(&peeraddr)); + break; + } + + memset((void *)&clock_stat, 0, sizeof clock_stat); + fudgeflag = 0; + errflg = 0; + for (i = 2; i < ntokens-1; i++) { + switch (c = matchkey(tokens[i], + fudge_keywords, 1)) { + case CONF_FDG_TIME1: + if (sscanf(tokens[++i], "%lf", + &clock_stat.fudgetime1) != 1) { + msyslog(LOG_ERR, + "fudge %s time1 value in error", + stoa(&peeraddr)); + errflg = i; + break; + } + clock_stat.haveflags |= CLK_HAVETIME1; + break; + + case CONF_FDG_TIME2: + if (sscanf(tokens[++i], "%lf", + &clock_stat.fudgetime2) != 1) { + msyslog(LOG_ERR, + "fudge %s time2 value in error", + stoa(&peeraddr)); + errflg = i; + break; + } + clock_stat.haveflags |= CLK_HAVETIME2; + break; + + + case CONF_FDG_STRATUM: + if (!atoint(tokens[++i], &stratum)) + { + msyslog(LOG_ERR, + "fudge %s stratum value in error", + stoa(&peeraddr)); + errflg = i; + break; + } + clock_stat.fudgeval1 = stratum; + clock_stat.haveflags |= CLK_HAVEVAL1; + break; + + case CONF_FDG_REFID: + /* HMS: Endianness and 0 bytes? */ + /* XXX */ + strncpy((char *)&clock_stat.fudgeval2, + tokens[++i], 4); + clock_stat.haveflags |= CLK_HAVEVAL2; + break; + + case CONF_FDG_FLAG1: + case CONF_FDG_FLAG2: + case CONF_FDG_FLAG3: + case CONF_FDG_FLAG4: + if (!atouint(tokens[++i], &fudgeflag) + || fudgeflag > 1) { + msyslog(LOG_ERR, + "fudge %s flag value in error", + stoa(&peeraddr)); + errflg = i; + break; + } + switch(c) { + case CONF_FDG_FLAG1: + c = CLK_FLAG1; + clock_stat.haveflags|=CLK_HAVEFLAG1; + break; + case CONF_FDG_FLAG2: + c = CLK_FLAG2; + clock_stat.haveflags|=CLK_HAVEFLAG2; + break; + case CONF_FDG_FLAG3: + c = CLK_FLAG3; + clock_stat.haveflags|=CLK_HAVEFLAG3; + break; + case CONF_FDG_FLAG4: + c = CLK_FLAG4; + clock_stat.haveflags|=CLK_HAVEFLAG4; + break; + } + if (fudgeflag == 0) + clock_stat.flags &= ~c; + else + clock_stat.flags |= c; + break; + + case CONFIG_UNKNOWN: + errflg = -1; + break; + } + } + +#ifdef REFCLOCK + /* + * If reference clock support isn't defined the + * fudge line will still be accepted and syntax + * checked, but will essentially do nothing. + */ + if (!errflg) { + refclock_control(&peeraddr, &clock_stat, + (struct refclockstat *)0); + } +#endif + break; + + case CONFIG_STATSDIR: + if (ntokens >= 2) + stats_config(STATS_STATSDIR,tokens[1]); + break; + + case CONFIG_STATISTICS: + for (i = 1; i < ntokens; i++) { + filegen = filegen_get(tokens[i]); + + if (filegen == NULL) { + msyslog(LOG_ERR, + "no statistics named %s available", + tokens[i]); + continue; + } +#ifdef DEBUG + if (debug > 3) + printf("enabling filegen for %s statistics \"%s%s\"\n", + tokens[i], filegen->prefix, filegen->basename); +#endif + filegen->flag |= FGEN_FLAG_ENABLED; + } + break; + + case CONFIG_FILEGEN: + if (ntokens < 2) { + msyslog(LOG_ERR, + "no id for filegen command, line ignored"); + break; + } + + filegen = filegen_get(tokens[1]); + if (filegen == NULL) { + msyslog(LOG_ERR, + "unknown filegen \"%s\" ignored", + tokens[1]); + break; + } + /* + * peerversion is (ab)used for filegen file (index) + * peerkey is (ab)used for filegen type + * peerflags is (ab)used for filegen flags + */ + peerversion = 0; + peerkey = filegen->type; + peerflags = filegen->flag; + errflg = 0; + + for (i = 2; i < ntokens; i++) { + switch (matchkey(tokens[i], + filegen_keywords, 1)) { + case CONF_FGEN_FILE: + if (i >= ntokens - 1) { + msyslog(LOG_ERR, + "filegen %s file requires argument", + tokens[1]); + errflg = i; + break; + } + peerversion = ++i; + break; + case CONF_FGEN_TYPE: + if (i >= ntokens -1) { + msyslog(LOG_ERR, + "filegen %s type requires argument", + tokens[1]); + errflg = i; + break; + } + peerkey = matchkey(tokens[++i], + fgen_types, 1); + if (peerkey == CONFIG_UNKNOWN) { + msyslog(LOG_ERR, + "filegen %s unknown type \"%s\"", + tokens[1], tokens[i]); + errflg = i; + break; + } + break; + + case CONF_FGEN_FLAG_LINK: + peerflags |= FGEN_FLAG_LINK; + break; + + case CONF_FGEN_FLAG_NOLINK: + peerflags &= ~FGEN_FLAG_LINK; + break; + + case CONF_FGEN_FLAG_ENABLE: + peerflags |= FGEN_FLAG_ENABLED; + break; + + case CONF_FGEN_FLAG_DISABLE: + peerflags &= ~FGEN_FLAG_ENABLED; + break; + } + } + if (!errflg) + filegen_config(filegen, tokens[peerversion], + (u_char)peerkey, (u_char)peerflags); + break; + + case CONFIG_SETVAR: + if (ntokens < 2) { + msyslog(LOG_ERR, + "no value for setvar command - line ignored"); + } else { + set_sys_var(tokens[1], strlen(tokens[1])+1, + (u_short) (RW | + ((((ntokens > 2) + && !strcmp(tokens[2], + "default"))) + ? DEF + : 0))); + } + break; + + case CONFIG_ENABLE: + for (i = 1; i < ntokens; i++) { + int flag; + + flag = matchkey(tokens[i], flags_keywords, 1); + if (flag == CONFIG_UNKNOWN) { + msyslog(LOG_ERR, + "enable unknown flag %s", + tokens[i]); + errflg = 1; + break; + } + proto_config(flag, 1, 0., NULL); + } + break; + + case CONFIG_DISABLE: + for (i = 1; i < ntokens; i++) { + int flag; + + flag = matchkey(tokens[i], flags_keywords, 1); + if (flag == CONFIG_UNKNOWN) { + msyslog(LOG_ERR, + "disable unknown flag %s", + tokens[i]); + errflg = 1; + break; + } + proto_config(flag, 0, 0., NULL); + } + break; + + case CONFIG_PHONE: + for (i = 1; i < ntokens && i < MAXPHONE; i++) { + (void)strncpy(sys_phone[i - 1], + tokens[i], MAXDIAL); + } + sys_phone[i - 1][0] = '\0'; + break; + + case CONFIG_ADJ: { + double ftemp; + + sscanf(tokens[1], "%lf", &ftemp); + proto_config(PROTO_ADJ, 0, ftemp, NULL); + } + break; + + } + } + if (fp[0]) + (void)fclose(fp[0]); + +#ifdef HAVE_NETINFO + if (config_netinfo) + free_netinfo_config(config_netinfo); +#endif /* HAVE_NETINFO */ + +#if !defined(VMS) && !defined(SYS_VXWORKS) + /* find a keyid */ + if (info_auth_keyid == 0) + req_keyid = 65535; + else + req_keyid = info_auth_keyid; + + /* if doesn't exist, make up one at random */ + if (!authhavekey(req_keyid)) { + char rankey[9]; + int j; + + for (i = 0; i < 8; i++) + for (j = 1; j < 100; ++j) { + rankey[i] = (char) (RANDOM & 0xff); + if (rankey[i] != 0) break; + } + rankey[8] = 0; + authusekey(req_keyid, KEY_TYPE_MD5, (u_char *)rankey); + authtrust(req_keyid, 1); + if (!authhavekey(req_keyid)) { + msyslog(LOG_ERR, "getconfig: Couldn't generate a valid random key!"); + /* HMS: Should this be fatal? */ + } + } + + /* save keyid so we will accept config requests with it */ + info_auth_keyid = req_keyid; +#endif /* !defined(VMS) && !defined(SYS_VXWORKS) */ + + if (res_fp != NULL) { + if (call_resolver) { + /* + * Need name resolution + */ + do_resolve_internal(); + } + } +} + + +#ifdef HAVE_NETINFO + +/* + * get_netinfo_config - find the nearest NetInfo domain with an ntp + * configuration and initialize the configuration state. + */ +static struct netinfo_config_state * +get_netinfo_config() +{ + ni_status status; + void *domain; + ni_id config_dir; + struct netinfo_config_state *config; + + if (ni_open(NULL, ".", &domain) != NI_OK) return NULL; + + while ((status = ni_pathsearch(domain, &config_dir, NETINFO_CONFIG_DIR)) == NI_NODIR) { + void *next_domain; + if (ni_open(domain, "..", &next_domain) != NI_OK) { + ni_free(next_domain); + break; + } + ni_free(domain); + domain = next_domain; + } + if (status != NI_OK) { + ni_free(domain); + return NULL; + } + + config = (struct netinfo_config_state *)malloc(sizeof(struct netinfo_config_state)); + config->domain = domain; + config->config_dir = config_dir; + config->prop_index = 0; + config->val_index = 0; + config->val_list = NULL; + + return config; +} + + + +/* + * free_netinfo_config - release NetInfo configuration state + */ +static void +free_netinfo_config(struct netinfo_config_state *config) +{ + ni_free(config->domain); + free(config); +} + + + +/* + * gettokens_netinfo - return tokens from NetInfo + */ +static int +gettokens_netinfo ( + struct netinfo_config_state *config, + char **tokenlist, + int *ntokens + ) +{ + int prop_index = config->prop_index; + int val_index = config->val_index; + char **val_list = config->val_list; + + /* + * Iterate through each keyword and look for a property that matches it. + */ + again: + if (!val_list) { + for (; prop_index < (sizeof(keywords)/sizeof(keywords[0])); prop_index++) + { + ni_namelist namelist; + struct keyword current_prop = keywords[prop_index]; + + /* + * For each value associated in the property, we're going to return + * a separate line. We squirrel away the values in the config state + * so the next time through, we don't need to do this lookup. + */ + NI_INIT(&namelist); + if (ni_lookupprop(config->domain, &config->config_dir, current_prop.text, &namelist) == NI_OK) { + ni_index index; + + /* Found the property, but it has no values */ + if (namelist.ni_namelist_len == 0) continue; + + if (! (val_list = config->val_list = (char**)malloc(sizeof(char*) * (namelist.ni_namelist_len + 1)))) + { msyslog(LOG_ERR, "out of memory while configuring"); break; } + + for (index = 0; index < namelist.ni_namelist_len; index++) { + char *value = namelist.ni_namelist_val[index]; + + if (! (val_list[index] = (char*)malloc(strlen(value)+1))) + { msyslog(LOG_ERR, "out of memory while configuring"); break; } + + strcpy(val_list[index], value); + } + val_list[index] = NULL; + + break; + } + ni_namelist_free(&namelist); + } + config->prop_index = prop_index; + } + + /* No list; we're done here. */ + if (!val_list) return CONFIG_UNKNOWN; + + /* + * We have a list of values for the current property. + * Iterate through them and return each in order. + */ + if (val_list[val_index]) + { + int ntok = 1; + int quoted = 0; + char *tokens = val_list[val_index]; + + msyslog(LOG_INFO, "%s %s", keywords[prop_index].text, val_list[val_index]); + + (const char*)tokenlist[0] = keywords[prop_index].text; + for (ntok = 1; ntok < MAXTOKENS; ntok++) { + tokenlist[ntok] = tokens; + while (!ISEOL(*tokens) && (!ISSPACE(*tokens) || quoted)) + quoted ^= (*tokens++ == '"'); + + if (ISEOL(*tokens)) { + *tokens = '\0'; + break; + } else { /* must be space */ + *tokens++ = '\0'; + while (ISSPACE(*tokens)) tokens++; + if (ISEOL(*tokens)) break; + } + } + *ntokens = ntok + 1; + + config->val_index++; + + return keywords[prop_index].keytype; + } + + /* We're done with the current property. */ + prop_index = ++config->prop_index; + + /* Free val_list and reset counters. */ + for (val_index = 0; val_list[val_index]; val_index++) + free(val_list[val_index]); + free(val_list); val_list = config->val_list = NULL; val_index = config->val_index = 0; + + goto again; +} + +#endif /* HAVE_NETINFO */ + + +/* + * gettokens - read a line and return tokens + */ +static int +gettokens ( + FILE *fp, + char *line, + char **tokenlist, + int *ntokens + ) +{ + register char *cp; + register int ntok; + register int quoted = 0; + + /* + * Find start of first token + */ + again: + while ((cp = fgets(line, MAXLINE, fp)) != NULL) { + cp = line; + while (ISSPACE(*cp)) + cp++; + if (!ISEOL(*cp)) + break; + } + if (cp == NULL) { + *ntokens = 0; + return CONFIG_UNKNOWN; /* hack. Is recognized as EOF */ + } + + /* + * Now separate out the tokens + */ + for (ntok = 0; ntok < MAXTOKENS; ntok++) { + tokenlist[ntok] = cp; + while (!ISEOL(*cp) && (!ISSPACE(*cp) || quoted)) + quoted ^= (*cp++ == '"'); + + if (ISEOL(*cp)) { + *cp = '\0'; + break; + } else { /* must be space */ + *cp++ = '\0'; + while (ISSPACE(*cp)) + cp++; + if (ISEOL(*cp)) + break; + } + } + + /* + * Return the match + */ + *ntokens = ntok + 1; + ntok = matchkey(tokenlist[0], keywords, 1); + if (ntok == CONFIG_UNKNOWN) + goto again; + return ntok; +} + + + +/* + * matchkey - match a keyword to a list + */ +static int +matchkey( + register char *word, + register struct keyword *keys, + int complain + ) +{ + for (;;) { + if (keys->keytype == CONFIG_UNKNOWN) { + if (complain) + msyslog(LOG_ERR, + "configure: keyword \"%s\" unknown, line ignored", + word); + return CONFIG_UNKNOWN; + } + if (STRSAME(word, keys->text)) + return keys->keytype; + keys++; + } +} + + +/* + * getnetnum - return a net number (this is crude, but careful) + */ +static int +getnetnum( + const char *num, + struct sockaddr_storage *addr, + int complain + ) +{ + struct addrinfo hints; + struct addrinfo *ptr; + + /* Get host address. Looking for UDP datagram connection */ + memset(&hints, 0, sizeof (hints)); + if (addr->ss_family == AF_INET || addr->ss_family == AF_INET6) + hints.ai_family = addr->ss_family; + else + hints.ai_family = AF_UNSPEC; + + hints.ai_socktype = SOCK_DGRAM; +#ifdef DEBUG + if (debug > 3) + printf("getaddrinfo %s\n", num); +#endif + if (getaddrinfo(num, "ntp", &hints, &ptr)!=0) { + if (complain) + msyslog(LOG_ERR, + "getaddrinfo: \"%s\" invalid host address, line ignored", + num); +#ifdef DEBUG + if (debug > 3) + printf( + "getaddrinfo: \"%s\" invalid host address%s.\n", + num, (complain) + ? ", line ignored" + : ""); +#endif + return 0; + } + + memcpy(addr, ptr->ai_addr, ptr->ai_addrlen); +#ifdef DEBUG + if (debug > 1) + printf("getnetnum given %s, got %s \n", + num, stoa(addr)); +#endif + freeaddrinfo(ptr); + return 1; +} + + +#if !defined(VMS) && !defined(SYS_WINNT) +/* + * catchchild - receive the resolver's exit status + */ +static RETSIGTYPE +catchchild( + int sig + ) +{ + /* + * We only start up one child, and if we're here + * it should have already exited. Hence the following + * shouldn't hang. If it does, please tell me. + */ +#if !defined (SYS_WINNT) && !defined(SYS_VXWORKS) + (void) wait(0); +#endif /* SYS_WINNT && VXWORKS*/ +} +#endif /* VMS */ + + +/* + * save_resolve - save configuration info into a file for later name resolution + */ +static void +save_resolve( + char *name, + int mode, + int version, + int minpoll, + int maxpoll, + u_int flags, + int ttl, + keyid_t keyid, + u_char *keystr + ) +{ +#ifndef SYS_VXWORKS + if (res_fp == NULL) { +#ifndef SYS_WINNT + (void) strcpy(res_file, RES_TEMPFILE); +#else + /* no /tmp directory under NT */ + { + if(!(GetTempPath((DWORD)MAX_PATH, (LPTSTR)res_file))) { + msyslog(LOG_ERR, "cannot get pathname for temporary directory: %m"); + return; + } + (void) strcat(res_file, "ntpdXXXXXX"); + } +#endif /* SYS_WINNT */ +#ifdef HAVE_MKSTEMP + { + int fd; + + res_fp = NULL; + if ((fd = mkstemp(res_file)) != -1) + res_fp = fdopen(fd, "r+"); + } +#else + (void) mktemp(res_file); + res_fp = fopen(res_file, "w"); +#endif + if (res_fp == NULL) { + msyslog(LOG_ERR, "open failed for %s: %m", res_file); + return; + } + } +#ifdef DEBUG + if (debug) { + printf("resolving %s\n", name); + } +#endif + + (void)fprintf(res_fp, "%s %d %d %d %d %d %d %u %s\n", name, + mode, version, minpoll, maxpoll, flags, ttl, keyid, keystr); +#ifdef DEBUG + if (debug > 1) + printf("config: %s %d %d %d %d %x %d %u %s\n", name, mode, + version, minpoll, maxpoll, flags, ttl, keyid, keystr); +#endif + +#else /* SYS_VXWORKS */ + /* save resolve info to a struct */ +#endif /* SYS_VXWORKS */ +} + + +/* + * abort_resolve - terminate the resolver stuff and delete the file + */ +static void +abort_resolve(void) +{ + /* + * In an ideal world we would might reread the file and + * log the hosts which aren't getting configured. Since + * this is too much work, however, just close and delete + * the temp file. + */ + if (res_fp != NULL) + (void) fclose(res_fp); + res_fp = NULL; + +#ifndef SYS_VXWORKS /* we don't open the file to begin with */ +#if !defined(VMS) + (void) unlink(res_file); +#else + (void) delete(res_file); +#endif /* VMS */ +#endif /* SYS_VXWORKS */ +} + + +/* + * do_resolve_internal - start up the resolver function (not program) + */ +/* + * On VMS, this routine will simply refuse to resolve anything. + * + * Possible implementation: keep `res_file' in memory, do async + * name resolution via QIO, update from within completion AST. + * I'm unlikely to find the time for doing this, though. -wjm + */ +static void +do_resolve_internal(void) +{ + int i; + + if (res_fp == NULL) { + /* belch */ + msyslog(LOG_ERR, + "do_resolve_internal: Fatal: res_fp == NULL"); + exit(1); + } + + /* we are done with this now */ + (void) fclose(res_fp); + res_fp = NULL; + +#if !defined(VMS) && !defined (SYS_VXWORKS) + req_file = res_file; /* set up pointer to res file */ +#ifndef SYS_WINNT + (void) signal_no_reset(SIGCHLD, catchchild); + +#ifndef SYS_VXWORKS + i = fork(); + if (i == 0) { + /* + * this used to close everything + * I don't think this is necessary + */ + /* + * To the unknown commenter above: + * Well, I think it's better to clean up + * after oneself. I have had problems with + * refclock-io when intres was running - things + * where fine again when ntpintres was gone. + * So some systems react erratic at least. + * + * Frank Kardel + * + * 94-11-16: + * Further debugging has proven that the above is + * absolutely harmful. The internal resolver + * is still in the SIGIO process group and the lingering + * async io information causes it to process requests from + * all file decriptor causing a race between the NTP daemon + * and the resolver. which then eats data when it wins 8-(. + * It is absolutly necessary to kill any IO associations + * shared with the NTP daemon. + * + * We also block SIGIO (currently no ports means to + * disable the signal handle for IO). + * + * Thanks to wgstuken@informatik.uni-erlangen.de to notice + * that it is the ntp-resolver child running into trouble. + * + * THUS: + */ + + closelog(); + kill_asyncio(0); + + (void) signal_no_reset(SIGCHLD, SIG_DFL); + +#ifdef DEBUG + if (0) + debug = 2; +#endif + +# ifndef LOG_DAEMON + openlog("ntpd_initres", LOG_PID); +# else /* LOG_DAEMON */ + +# ifndef LOG_NTP +# define LOG_NTP LOG_DAEMON +# endif + openlog("ntpd_initres", LOG_PID | LOG_NDELAY, LOG_NTP); +#ifndef SYS_CYGWIN32 +# ifdef DEBUG + if (debug) + setlogmask(LOG_UPTO(LOG_DEBUG)); + else +# endif /* DEBUG */ + setlogmask(LOG_UPTO(LOG_DEBUG)); /* @@@ was INFO */ +# endif /* LOG_DAEMON */ +#endif + + ntp_intres(); + + /* + * If we got here, the intres code screwed up. + * Print something so we don't die without complaint + */ + msyslog(LOG_ERR, "call to ntp_intres lost"); + abort_resolve(); + exit(1); + } +#else + /* vxWorks spawns a thread... -casey */ + i = sp (ntp_intres); + /*i = taskSpawn("ntp_intres",100,VX_FP_TASK,20000,ntp_intres);*/ +#endif + if (i == -1) { + msyslog(LOG_ERR, "fork() failed, can't start ntp_intres: %m"); + (void) signal_no_reset(SIGCHLD, SIG_DFL); + abort_resolve(); + } +#else /* SYS_WINNT */ + { + /* NT's equivalent of fork() is _spawn(), but the start point + * of the new process is an executable filename rather than + * a function name as desired here. + */ + DWORD dwThreadId; + fflush(stdout); + ResolverThreadHandle = CreateThread( + NULL, /* no security attributes */ + 0, /* use default stack size */ + (LPTHREAD_START_ROUTINE) ntp_intres, /* thread function */ + NULL, /* argument to thread function */ + 0, /* use default creation flags */ + &dwThreadId); /* returns the thread identifier */ + if (ResolverThreadHandle == NULL) { + msyslog(LOG_ERR, "CreateThread() failed, can't start ntp_intres"); + abort_resolve(); + } + } +#endif /* SYS_WINNT */ +#else /* VMS VX_WORKS */ + msyslog(LOG_ERR, + "Name resolution not implemented for VMS - use numeric addresses"); + abort_resolve(); +#endif /* VMS VX_WORKS */ +} diff --git a/ntpd/ntp_control.c b/ntpd/ntp_control.c new file mode 100644 index 0000000..0ac0404 --- /dev/null +++ b/ntpd/ntp_control.c @@ -0,0 +1,2928 @@ +/* + * ntp_control.c - respond to control messages and send async traps + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_control.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> +#include <signal.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +/* + * Structure to hold request procedure information + */ +#define NOAUTH 0 +#define AUTH 1 + +#define NO_REQUEST (-1) + +struct ctl_proc { + short control_code; /* defined request code */ + u_short flags; /* flags word */ + void (*handler) P((struct recvbuf *, int)); /* handle request */ +}; + +/* + * Only one flag. Authentication required or not. + */ +#define NOAUTH 0 +#define AUTH 1 + +/* + * Request processing routines + */ +static void ctl_error P((int)); +#ifdef REFCLOCK +static u_short ctlclkstatus P((struct refclockstat *)); +#endif +static void ctl_flushpkt P((int)); +static void ctl_putdata P((const char *, unsigned int, int)); +static void ctl_putstr P((const char *, const char *, + unsigned int)); +static void ctl_putdbl P((const char *, double)); +static void ctl_putuint P((const char *, u_long)); +static void ctl_puthex P((const char *, u_long)); +static void ctl_putint P((const char *, long)); +static void ctl_putts P((const char *, l_fp *)); +static void ctl_putadr P((const char *, u_int32, struct sockaddr_storage*)); +static void ctl_putid P((const char *, char *)); +static void ctl_putarray P((const char *, double *, int)); +static void ctl_putsys P((int)); +static void ctl_putpeer P((int, struct peer *)); +#ifdef REFCLOCK +static void ctl_putclock P((int, struct refclockstat *, int)); +#endif /* REFCLOCK */ +static struct ctl_var *ctl_getitem P((struct ctl_var *, char **)); +static u_long count_var P((struct ctl_var *)); +static void control_unspec P((struct recvbuf *, int)); +static void read_status P((struct recvbuf *, int)); +static void read_variables P((struct recvbuf *, int)); +static void write_variables P((struct recvbuf *, int)); +static void read_clock_status P((struct recvbuf *, int)); +static void write_clock_status P((struct recvbuf *, int)); +static void set_trap P((struct recvbuf *, int)); +static void unset_trap P((struct recvbuf *, int)); +static struct ctl_trap *ctlfindtrap P((struct sockaddr_storage *, + struct interface *)); + +static struct ctl_proc control_codes[] = { + { CTL_OP_UNSPEC, NOAUTH, control_unspec }, + { CTL_OP_READSTAT, NOAUTH, read_status }, + { CTL_OP_READVAR, NOAUTH, read_variables }, + { CTL_OP_WRITEVAR, AUTH, write_variables }, + { CTL_OP_READCLOCK, NOAUTH, read_clock_status }, + { CTL_OP_WRITECLOCK, NOAUTH, write_clock_status }, + { CTL_OP_SETTRAP, NOAUTH, set_trap }, + { CTL_OP_UNSETTRAP, NOAUTH, unset_trap }, + { NO_REQUEST, 0 } +}; + +/* + * System variable values. The array can be indexed by the variable + * index to find the textual name. + */ +static struct ctl_var sys_var[] = { + { 0, PADDING, "" }, /* 0 */ + { CS_LEAP, RW, "leap" }, /* 1 */ + { CS_STRATUM, RO, "stratum" }, /* 2 */ + { CS_PRECISION, RO, "precision" }, /* 3 */ + { CS_ROOTDELAY, RO, "rootdelay" }, /* 4 */ + { CS_ROOTDISPERSION, RO, "rootdispersion" }, /* 5 */ + { CS_REFID, RO, "refid" }, /* 6 */ + { CS_REFTIME, RO, "reftime" }, /* 7 */ + { CS_POLL, RO, "poll" }, /* 8 */ + { CS_PEERID, RO, "peer" }, /* 9 */ + { CS_STATE, RO, "state" }, /* 10 */ + { CS_OFFSET, RO, "offset" }, /* 11 */ + { CS_DRIFT, RO, "frequency" }, /* 12 */ + { CS_JITTER, RO, "jitter" }, /* 13 */ + { CS_CLOCK, RO, "clock" }, /* 14 */ + { CS_PROCESSOR, RO, "processor" }, /* 15 */ + { CS_SYSTEM, RO, "system" }, /* 16 */ + { CS_VERSION, RO, "version" }, /* 17 */ + { CS_STABIL, RO, "stability" }, /* 18 */ + { CS_VARLIST, RO, "sys_var_list" }, /* 19 */ +#ifdef OPENSSL + { CS_FLAGS, RO, "flags" }, /* 20 */ + { CS_HOST, RO, "hostname" }, /* 21 */ + { CS_PUBLIC, RO, "hostkey" }, /* 22 */ + { CS_CERTIF, RO, "cert" }, /* 23 */ + { CS_REVTIME, RO, "refresh" }, /* 24 */ + { CS_LEAPTAB, RO, "leapseconds" }, /* 25 */ + { CS_TAI, RO, "tai" }, /* 26 */ + { CS_DIGEST, RO, "signature" }, /* 27 */ +#endif /* OPENSSL */ + { 0, EOV, "" } /* 28 */ +}; + +static struct ctl_var *ext_sys_var = (struct ctl_var *)0; + +/* + * System variables we print by default (in fuzzball order, + * more-or-less) + */ +static u_char def_sys_var[] = { + CS_VERSION, + CS_PROCESSOR, + CS_SYSTEM, + CS_LEAP, + CS_STRATUM, + CS_PRECISION, + CS_ROOTDELAY, + CS_ROOTDISPERSION, + CS_PEERID, + CS_REFID, + CS_REFTIME, + CS_POLL, + CS_CLOCK, + CS_STATE, + CS_OFFSET, + CS_DRIFT, + CS_JITTER, + CS_STABIL, +#ifdef OPENSSL + CS_HOST, + CS_DIGEST, + CS_FLAGS, + CS_PUBLIC, + CS_REVTIME, + CS_LEAPTAB, + CS_CERTIF, +#endif /* OPENSSL */ + 0 +}; + + +/* + * Peer variable list + */ +static struct ctl_var peer_var[] = { + { 0, PADDING, "" }, /* 0 */ + { CP_CONFIG, RO, "config" }, /* 1 */ + { CP_AUTHENABLE, RO, "authenable" }, /* 2 */ + { CP_AUTHENTIC, RO, "authentic" }, /* 3 */ + { CP_SRCADR, RO, "srcadr" }, /* 4 */ + { CP_SRCPORT, RO, "srcport" }, /* 5 */ + { CP_DSTADR, RO, "dstadr" }, /* 6 */ + { CP_DSTPORT, RO, "dstport" }, /* 7 */ + { CP_LEAP, RO, "leap" }, /* 8 */ + { CP_HMODE, RO, "hmode" }, /* 9 */ + { CP_STRATUM, RO, "stratum" }, /* 10 */ + { CP_PPOLL, RO, "ppoll" }, /* 11 */ + { CP_HPOLL, RO, "hpoll" }, /* 12 */ + { CP_PRECISION, RO, "precision" }, /* 13 */ + { CP_ROOTDELAY, RO, "rootdelay" }, /* 14 */ + { CP_ROOTDISPERSION, RO, "rootdispersion" }, /* 15 */ + { CP_REFID, RO, "refid" }, /* 16 */ + { CP_REFTIME, RO, "reftime" }, /* 17 */ + { CP_ORG, RO, "org" }, /* 18 */ + { CP_REC, RO, "rec" }, /* 19 */ + { CP_XMT, RO, "xmt" }, /* 20 */ + { CP_REACH, RO, "reach" }, /* 21 */ + { CP_VALID, RO, "unreach" }, /* 22 */ + { CP_TIMER, RO, "timer" }, /* 23 */ + { CP_DELAY, RO, "delay" }, /* 24 */ + { CP_OFFSET, RO, "offset" }, /* 25 */ + { CP_JITTER, RO, "jitter" }, /* 26 */ + { CP_DISPERSION, RO, "dispersion" }, /* 27 */ + { CP_KEYID, RO, "keyid" }, /* 28 */ + { CP_FILTDELAY, RO, "filtdelay=" }, /* 29 */ + { CP_FILTOFFSET, RO, "filtoffset=" }, /* 30 */ + { CP_PMODE, RO, "pmode" }, /* 31 */ + { CP_RECEIVED, RO, "received"}, /* 32 */ + { CP_SENT, RO, "sent" }, /* 33 */ + { CP_FILTERROR, RO, "filtdisp=" }, /* 34 */ + { CP_FLASH, RO, "flash" }, /* 35 */ + { CP_TTL, RO, "ttl" }, /* 36 */ + { CP_RANK, RO, "rank" }, /* 37 */ + { CP_VARLIST, RO, "peer_var_list" }, /* 38 */ +#ifdef OPENSSL + { CP_FLAGS, RO, "flags" }, /* 39 */ + { CP_HOST, RO, "hostname" }, /* 40 */ + { CP_INITSEQ, RO, "initsequence" }, /* 41 */ + { CP_INITKEY, RO, "initkey" }, /* 42 */ + { CP_INITTSP, RO, "timestamp" }, /* 43 */ + { CP_DIGEST, RO, "signature" }, /* 44 */ + { CP_IDENT, RO, "identity" }, /* 45 */ +#endif /* OPENSSL */ + { 0, EOV, "" } /* 39/46 */ +}; + + +/* + * Peer variables we print by default + */ +static u_char def_peer_var[] = { + CP_SRCADR, + CP_SRCPORT, + CP_DSTADR, + CP_DSTPORT, + CP_LEAP, + CP_STRATUM, + CP_PRECISION, + CP_ROOTDELAY, + CP_ROOTDISPERSION, + CP_REFID, + CP_REACH, + CP_VALID, + CP_HMODE, + CP_PMODE, + CP_HPOLL, + CP_PPOLL, + CP_FLASH, + CP_KEYID, + CP_TTL, + CP_OFFSET, + CP_DELAY, + CP_DISPERSION, + CP_JITTER, + CP_REFTIME, + CP_ORG, + CP_REC, + CP_XMT, + CP_FILTDELAY, + CP_FILTOFFSET, + CP_FILTERROR, +#ifdef OPENSSL + CP_HOST, + CP_DIGEST, + CP_FLAGS, + CP_IDENT, + CP_INITSEQ, +#endif /* OPENSSL */ + 0 +}; + + +#ifdef REFCLOCK +/* + * Clock variable list + */ +static struct ctl_var clock_var[] = { + { 0, PADDING, "" }, /* 0 */ + { CC_TYPE, RO, "type" }, /* 1 */ + { CC_TIMECODE, RO, "timecode" }, /* 2 */ + { CC_POLL, RO, "poll" }, /* 3 */ + { CC_NOREPLY, RO, "noreply" }, /* 4 */ + { CC_BADFORMAT, RO, "badformat" }, /* 5 */ + { CC_BADDATA, RO, "baddata" }, /* 6 */ + { CC_FUDGETIME1, RO, "fudgetime1" }, /* 7 */ + { CC_FUDGETIME2, RO, "fudgetime2" }, /* 8 */ + { CC_FUDGEVAL1, RO, "stratum" }, /* 9 */ + { CC_FUDGEVAL2, RO, "refid" }, /* 10 */ + { CC_FLAGS, RO, "flags" }, /* 11 */ + { CC_DEVICE, RO, "device" }, /* 12 */ + { CC_VARLIST, RO, "clock_var_list" }, /* 13 */ + { 0, EOV, "" } /* 14 */ +}; + + +/* + * Clock variables printed by default + */ +static u_char def_clock_var[] = { + CC_DEVICE, + CC_TYPE, /* won't be output if device = known */ + CC_TIMECODE, + CC_POLL, + CC_NOREPLY, + CC_BADFORMAT, + CC_BADDATA, + CC_FUDGETIME1, + CC_FUDGETIME2, + CC_FUDGEVAL1, + CC_FUDGEVAL2, + CC_FLAGS, + 0 +}; +#endif + + +/* + * System and processor definitions. + */ +#ifndef HAVE_UNAME +# ifndef STR_SYSTEM +# define STR_SYSTEM "UNIX" +# endif +# ifndef STR_PROCESSOR +# define STR_PROCESSOR "unknown" +# endif + +static char str_system[] = STR_SYSTEM; +static char str_processor[] = STR_PROCESSOR; +#else +# include <sys/utsname.h> +static struct utsname utsnamebuf; +#endif /* HAVE_UNAME */ + +/* + * Trap structures. We only allow a few of these, and send a copy of + * each async message to each live one. Traps time out after an hour, it + * is up to the trap receipient to keep resetting it to avoid being + * timed out. + */ +/* ntp_request.c */ +struct ctl_trap ctl_trap[CTL_MAXTRAPS]; +int num_ctl_traps; + +/* + * Type bits, for ctlsettrap() call. + */ +#define TRAP_TYPE_CONFIG 0 /* used by configuration code */ +#define TRAP_TYPE_PRIO 1 /* priority trap */ +#define TRAP_TYPE_NONPRIO 2 /* nonpriority trap */ + + +/* + * List relating reference clock types to control message time sources. + * Index by the reference clock type. This list will only be used iff + * the reference clock driver doesn't set peer->sstclktype to something + * different than CTL_SST_TS_UNSPEC. + */ +static u_char clocktypes[] = { + CTL_SST_TS_NTP, /* REFCLK_NONE (0) */ + CTL_SST_TS_LOCAL, /* REFCLK_LOCALCLOCK (1) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_TRAK (2) */ + CTL_SST_TS_HF, /* REFCLK_WWV_PST (3) */ + CTL_SST_TS_LF, /* REFCLK_WWVB_SPECTRACOM (4) */ + CTL_SST_TS_UHF, /* REFCLK_TRUETIME (5) */ + CTL_SST_TS_UHF, /* REFCLK_GOES_TRAK (6) */ + CTL_SST_TS_HF, /* REFCLK_CHU (7) */ + CTL_SST_TS_LF, /* REFCLOCK_PARSE (default) (8) */ + CTL_SST_TS_LF, /* REFCLK_GPS_MX4200 (9) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_AS2201 (10) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_ARBITER (11) */ + CTL_SST_TS_UHF, /* REFCLK_IRIG_TPRO (12) */ + CTL_SST_TS_ATOM, /* REFCLK_ATOM_LEITCH (13) */ + CTL_SST_TS_LF, /* REFCLK_MSF_EES (14) */ + CTL_SST_TS_UHF, /* REFCLK_TRUETIME (15) */ + CTL_SST_TS_UHF, /* REFCLK_IRIG_BANCOMM (16) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_DATU (17) */ + CTL_SST_TS_TELEPHONE, /* REFCLK_NIST_ACTS (18) */ + CTL_SST_TS_HF, /* REFCLK_WWV_HEATH (19) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_NMEA (20) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_VME (21) */ + CTL_SST_TS_ATOM, /* REFCLK_ATOM_PPS (22) */ + CTL_SST_TS_TELEPHONE, /* REFCLK_PTB_ACTS (23) */ + CTL_SST_TS_TELEPHONE, /* REFCLK_USNO (24) */ + CTL_SST_TS_UHF, /* REFCLK_TRUETIME (25) */ + CTL_SST_TS_UHF, /* REFCLK_GPS_HP (26) */ + CTL_SST_TS_TELEPHONE, /* REFCLK_ARCRON_MSF (27) */ + CTL_SST_TS_TELEPHONE, /* REFCLK_SHM (28) */ + CTL_SST_TS_UHF, /* REFCLK_PALISADE (29) */ + CTL_SST_TS_UHF, /* REFCLK_ONCORE (30) */ + CTL_SST_TS_UHF, /* REFCLK_JUPITER (31) */ + CTL_SST_TS_LF, /* REFCLK_CHRONOLOG (32) */ + CTL_SST_TS_LF, /* REFCLK_DUMBCLOCK (32) */ + CTL_SST_TS_LF, /* REFCLK_ULINK (33) */ + CTL_SST_TS_LF, /* REFCLK_PCF (35) */ + CTL_SST_TS_LF, /* REFCLK_WWV (36) */ + CTL_SST_TS_LF, /* REFCLK_FG (37) */ + CTL_SST_TS_UHF, /* REFCLK_HOPF_SERIAL (38) */ + CTL_SST_TS_UHF, /* REFCLK_HOPF_PCI (39) */ + CTL_SST_TS_LF, /* REFCLK_JJY (40) */ + CTL_SST_TS_UHF, /* REFCLK_TT560 (41) */ + CTL_SST_TS_UHF, /* REFCLK_ZYFER (42) */ + CTL_SST_TS_UHF, /* REFCLK_RIPENCC (43) */ + CTL_SST_TS_UHF, /* REFCLK_NEOCLOCK4X (44) */ +}; + + +/* + * Keyid used for authenticating write requests. + */ +keyid_t ctl_auth_keyid; + +/* + * We keep track of the last error reported by the system internally + */ +static u_char ctl_sys_last_event; +static u_char ctl_sys_num_events; + + +/* + * Statistic counters to keep track of requests and responses. + */ +u_long ctltimereset; /* time stats reset */ +u_long numctlreq; /* number of requests we've received */ +u_long numctlbadpkts; /* number of bad control packets */ +u_long numctlresponses; /* number of resp packets sent with data */ +u_long numctlfrags; /* number of fragments sent */ +u_long numctlerrors; /* number of error responses sent */ +u_long numctltooshort; /* number of too short input packets */ +u_long numctlinputresp; /* number of responses on input */ +u_long numctlinputfrag; /* number of fragments on input */ +u_long numctlinputerr; /* number of input pkts with err bit set */ +u_long numctlbadoffset; /* number of input pkts with nonzero offset */ +u_long numctlbadversion; /* number of input pkts with unknown version */ +u_long numctldatatooshort; /* data too short for count */ +u_long numctlbadop; /* bad op code found in packet */ +u_long numasyncmsgs; /* number of async messages we've sent */ + +/* + * Response packet used by these routines. Also some state information + * so that we can handle packet formatting within a common set of + * subroutines. Note we try to enter data in place whenever possible, + * but the need to set the more bit correctly means we occasionally + * use the extra buffer and copy. + */ +static struct ntp_control rpkt; +static u_char res_version; +static u_char res_opcode; +static associd_t res_associd; +static int res_offset; +static u_char * datapt; +static u_char * dataend; +static int datalinelen; +static int datanotbinflag; +static struct sockaddr_storage *rmt_addr; +static struct interface *lcl_inter; + +static u_char res_authenticate; +static u_char res_authokay; +static keyid_t res_keyid; + +#define MAXDATALINELEN (72) + +static u_char res_async; /* set to 1 if this is async trap response */ + +/* + * Pointers for saving state when decoding request packets + */ +static char *reqpt; +static char *reqend; + +/* + * init_control - initialize request data + */ +void +init_control(void) +{ + int i; + +#ifdef HAVE_UNAME + uname(&utsnamebuf); +#endif /* HAVE_UNAME */ + + ctl_clr_stats(); + + ctl_auth_keyid = 0; + ctl_sys_last_event = EVNT_UNSPEC; + ctl_sys_num_events = 0; + + num_ctl_traps = 0; + for (i = 0; i < CTL_MAXTRAPS; i++) + ctl_trap[i].tr_flags = 0; +} + + +/* + * ctl_error - send an error response for the current request + */ +static void +ctl_error( + int errcode + ) +{ +#ifdef DEBUG + if (debug >= 4) + printf("sending control error %d\n", errcode); +#endif + /* + * Fill in the fields. We assume rpkt.sequence and rpkt.associd + * have already been filled in. + */ + rpkt.r_m_e_op = (u_char) (CTL_RESPONSE|CTL_ERROR|(res_opcode & + CTL_OP_MASK)); + rpkt.status = htons((u_short) ((errcode<<8) & 0xff00)); + rpkt.count = 0; + + /* + * send packet and bump counters + */ + if (res_authenticate && sys_authenticate) { + int maclen; + + *(u_int32 *)((u_char *)&rpkt + CTL_HEADER_LEN) = + htonl(res_keyid); + maclen = authencrypt(res_keyid, (u_int32 *)&rpkt, + CTL_HEADER_LEN); + sendpkt(rmt_addr, lcl_inter, -2, (struct pkt *)&rpkt, + CTL_HEADER_LEN + maclen); + } else { + sendpkt(rmt_addr, lcl_inter, -3, (struct pkt *)&rpkt, + CTL_HEADER_LEN); + } + numctlerrors++; +} + + +/* + * process_control - process an incoming control message + */ +void +process_control( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + register struct ntp_control *pkt; + register int req_count; + register int req_data; + register struct ctl_proc *cc; + int properlen; + int maclen; + +#ifdef DEBUG + if (debug > 2) + printf("in process_control()\n"); +#endif + + /* + * Save the addresses for error responses + */ + numctlreq++; + rmt_addr = &rbufp->recv_srcadr; + lcl_inter = rbufp->dstadr; + pkt = (struct ntp_control *)&rbufp->recv_pkt; + + /* + * If the length is less than required for the header, or + * it is a response or a fragment, ignore this. + */ + if (rbufp->recv_length < CTL_HEADER_LEN + || pkt->r_m_e_op & (CTL_RESPONSE|CTL_MORE|CTL_ERROR) + || pkt->offset != 0) { +#ifdef DEBUG + if (debug) + printf("invalid format in control packet\n"); +#endif + if (rbufp->recv_length < CTL_HEADER_LEN) + numctltooshort++; + if (pkt->r_m_e_op & CTL_RESPONSE) + numctlinputresp++; + if (pkt->r_m_e_op & CTL_MORE) + numctlinputfrag++; + if (pkt->r_m_e_op & CTL_ERROR) + numctlinputerr++; + if (pkt->offset != 0) + numctlbadoffset++; + return; + } + res_version = PKT_VERSION(pkt->li_vn_mode); + if (res_version > NTP_VERSION || res_version < NTP_OLDVERSION) { +#ifdef DEBUG + if (debug) + printf("unknown version %d in control packet\n", + res_version); +#endif + numctlbadversion++; + return; + } + + /* + * Pull enough data from the packet to make intelligent + * responses + */ + rpkt.li_vn_mode = PKT_LI_VN_MODE(sys_leap, res_version, + MODE_CONTROL); + res_opcode = pkt->r_m_e_op; + rpkt.sequence = pkt->sequence; + rpkt.associd = pkt->associd; + rpkt.status = 0; + res_offset = 0; + res_associd = htons(pkt->associd); + res_async = 0; + res_authenticate = 0; + res_keyid = 0; + res_authokay = 0; + req_count = (int)htons(pkt->count); + datanotbinflag = 0; + datalinelen = 0; + datapt = rpkt.data; + dataend = &(rpkt.data[CTL_MAX_DATA_LEN]); + + /* + * We're set up now. Make sure we've got at least enough + * incoming data space to match the count. + */ + req_data = rbufp->recv_length - CTL_HEADER_LEN; + if (req_data < req_count || rbufp->recv_length & 0x3) { + ctl_error(CERR_BADFMT); + numctldatatooshort++; + return; + } + + properlen = req_count + CTL_HEADER_LEN; +#ifdef DEBUG + if (debug > 2 && (rbufp->recv_length & 0x3) != 0) + printf("Packet length %d unrounded\n", + rbufp->recv_length); +#endif + /* round up proper len to a 8 octet boundary */ + + properlen = (properlen + 7) & ~7; + maclen = rbufp->recv_length - properlen; + if ((rbufp->recv_length & (sizeof(u_long) - 1)) == 0 && + maclen >= MIN_MAC_LEN && maclen <= MAX_MAC_LEN && + sys_authenticate) { + res_authenticate = 1; + res_keyid = ntohl(*(u_int32 *)((u_char *)pkt + + properlen)); + +#ifdef DEBUG + if (debug > 2) + printf( + "recv_len %d, properlen %d, wants auth with keyid %08x, MAC length=%d\n", + rbufp->recv_length, properlen, res_keyid, maclen); +#endif + if (!authistrusted(res_keyid)) { +#ifdef DEBUG + if (debug > 2) + printf("invalid keyid %08x\n", + res_keyid); +#endif + } else if (authdecrypt(res_keyid, (u_int32 *)pkt, + rbufp->recv_length - maclen, maclen)) { +#ifdef DEBUG + if (debug > 2) + printf("authenticated okay\n"); +#endif + res_authokay = 1; + } else { +#ifdef DEBUG + if (debug > 2) + printf("authentication failed\n"); +#endif + res_keyid = 0; + } + } + + /* + * Set up translate pointers + */ + reqpt = (char *)pkt->data; + reqend = reqpt + req_count; + + /* + * Look for the opcode processor + */ + for (cc = control_codes; cc->control_code != NO_REQUEST; cc++) { + if (cc->control_code == res_opcode) { +#ifdef DEBUG + if (debug > 2) + printf("opcode %d, found command handler\n", + res_opcode); +#endif + if (cc->flags == AUTH && (!res_authokay || + res_keyid != ctl_auth_keyid)) { + ctl_error(CERR_PERMISSION); + return; + } + (cc->handler)(rbufp, restrict_mask); + return; + } + } + + /* + * Can't find this one, return an error. + */ + numctlbadop++; + ctl_error(CERR_BADOP); + return; +} + + +/* + * ctlpeerstatus - return a status word for this peer + */ +u_short +ctlpeerstatus( + register struct peer *peer + ) +{ + register u_short status; + + status = peer->status; + if (peer->flags & FLAG_CONFIG) + status |= CTL_PST_CONFIG; + if (peer->flags & FLAG_AUTHENABLE) + status |= CTL_PST_AUTHENABLE; + if (peer->flags & FLAG_AUTHENTIC) + status |= CTL_PST_AUTHENTIC; + if (peer->reach != 0) + status |= CTL_PST_REACH; + return (u_short)CTL_PEER_STATUS(status, peer->num_events, + peer->last_event); +} + + +/* + * ctlclkstatus - return a status word for this clock + */ +#ifdef REFCLOCK +static u_short +ctlclkstatus( + struct refclockstat *this_clock + ) +{ + return ((u_short)(((this_clock->currentstatus) << 8) | + (this_clock->lastevent))); +} +#endif + + +/* + * ctlsysstatus - return the system status word + */ +u_short +ctlsysstatus(void) +{ + register u_char this_clock; + + this_clock = CTL_SST_TS_UNSPEC; + if (sys_peer != 0) { + if (sys_peer->sstclktype != CTL_SST_TS_UNSPEC) { + this_clock = sys_peer->sstclktype; + if (pps_control) + this_clock |= CTL_SST_TS_PPS; + } else { + if (sys_peer->refclktype < sizeof(clocktypes)) + this_clock = + clocktypes[sys_peer->refclktype]; + if (pps_control) + this_clock |= CTL_SST_TS_PPS; + } + } + return (u_short)CTL_SYS_STATUS(sys_leap, this_clock, + ctl_sys_num_events, ctl_sys_last_event); +} + + +/* + * ctl_flushpkt - write out the current packet and prepare + * another if necessary. + */ +static void +ctl_flushpkt( + int more + ) +{ + int dlen; + int sendlen; + + if (!more && datanotbinflag) { + /* + * Big hack, output a trailing \r\n + */ + *datapt++ = '\r'; + *datapt++ = '\n'; + } + dlen = datapt - (u_char *)rpkt.data; + sendlen = dlen + CTL_HEADER_LEN; + + /* + * Pad to a multiple of 32 bits + */ + while (sendlen & 0x3) { + *datapt++ = '\0'; + sendlen++; + } + + /* + * Fill in the packet with the current info + */ + rpkt.r_m_e_op = (u_char)(CTL_RESPONSE|more|(res_opcode & + CTL_OP_MASK)); + rpkt.count = htons((u_short) dlen); + rpkt.offset = htons( (u_short) res_offset); + if (res_async) { + register int i; + + for (i = 0; i < CTL_MAXTRAPS; i++) { + if (ctl_trap[i].tr_flags & TRAP_INUSE) { + rpkt.li_vn_mode = + PKT_LI_VN_MODE(sys_leap, + ctl_trap[i].tr_version, + MODE_CONTROL); + rpkt.sequence = + htons(ctl_trap[i].tr_sequence); + sendpkt(&ctl_trap[i].tr_addr, + ctl_trap[i].tr_localaddr, -4, + (struct pkt *)&rpkt, sendlen); + if (!more) + ctl_trap[i].tr_sequence++; + numasyncmsgs++; + } + } + } else { + if (res_authenticate && sys_authenticate) { + int maclen; + int totlen = sendlen; + keyid_t keyid = htonl(res_keyid); + + /* + * If we are going to authenticate, then there + * is an additional requirement that the MAC + * begin on a 64 bit boundary. + */ + while (totlen & 7) { + *datapt++ = '\0'; + totlen++; + } + memcpy(datapt, &keyid, sizeof keyid); + maclen = authencrypt(res_keyid, + (u_int32 *)&rpkt, totlen); + sendpkt(rmt_addr, lcl_inter, -5, + (struct pkt *)&rpkt, totlen + maclen); + } else { + sendpkt(rmt_addr, lcl_inter, -6, + (struct pkt *)&rpkt, sendlen); + } + if (more) + numctlfrags++; + else + numctlresponses++; + } + + /* + * Set us up for another go around. + */ + res_offset += dlen; + datapt = (u_char *)rpkt.data; +} + + +/* + * ctl_putdata - write data into the packet, fragmenting and starting + * another if this one is full. + */ +static void +ctl_putdata( + const char *dp, + unsigned int dlen, + int bin /* set to 1 when data is binary */ + ) +{ + int overhead; + + overhead = 0; + if (!bin) { + datanotbinflag = 1; + overhead = 3; + if (datapt != rpkt.data) { + *datapt++ = ','; + datalinelen++; + if ((dlen + datalinelen + 1) >= MAXDATALINELEN) + { + *datapt++ = '\r'; + *datapt++ = '\n'; + datalinelen = 0; + } else { + *datapt++ = ' '; + datalinelen++; + } + } + } + + /* + * Save room for trailing junk + */ + if (dlen + overhead + datapt > dataend) { + /* + * Not enough room in this one, flush it out. + */ + ctl_flushpkt(CTL_MORE); + } + memmove((char *)datapt, dp, (unsigned)dlen); + datapt += dlen; + datalinelen += dlen; +} + + +/* + * ctl_putstr - write a tagged string into the response packet + */ +static void +ctl_putstr( + const char *tag, + const char *data, + unsigned int len + ) +{ + register char *cp; + register const char *cq; + char buffer[400]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + if (len > 0) { + *cp++ = '='; + *cp++ = '"'; + if (len > (int) (sizeof(buffer) - (cp - buffer) - 1)) + len = sizeof(buffer) - (cp - buffer) - 1; + memmove(cp, data, (unsigned)len); + cp += len; + *cp++ = '"'; + } + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putdbl - write a tagged, signed double into the response packet + */ +static void +ctl_putdbl( + const char *tag, + double ts + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + *cp++ = '='; + (void)sprintf(cp, "%.3f", ts); + while (*cp != '\0') + cp++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + +/* + * ctl_putuint - write a tagged unsigned integer into the response + */ +static void +ctl_putuint( + const char *tag, + u_long uval + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + (void) sprintf(cp, "%lu", uval); + while (*cp != '\0') + cp++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_puthex - write a tagged unsigned integer, in hex, into the response + */ +static void +ctl_puthex( + const char *tag, + u_long uval + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + (void) sprintf(cp, "0x%lx", uval); + while (*cp != '\0') + cp++; + ctl_putdata(buffer,(unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putint - write a tagged signed integer into the response + */ +static void +ctl_putint( + const char *tag, + long ival + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + (void) sprintf(cp, "%ld", ival); + while (*cp != '\0') + cp++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putts - write a tagged timestamp, in hex, into the response + */ +static void +ctl_putts( + const char *tag, + l_fp *ts + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + (void) sprintf(cp, "0x%08lx.%08lx", ts->l_ui & 0xffffffffL, + ts->l_uf & 0xffffffffL); + while (*cp != '\0') + cp++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putadr - write an IP address into the response + */ +static void +ctl_putadr( + const char *tag, + u_int32 addr32, + struct sockaddr_storage* addr + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + if (addr == NULL) + cq = numtoa(addr32); + else + cq = stoa(addr); + while (*cq != '\0') + *cp++ = *cq++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putid - write a tagged clock ID into the response + */ +static void +ctl_putid( + const char *tag, + char *id + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + + *cp++ = '='; + cq = id; + while (*cq != '\0' && (cq - id) < 4) + *cp++ = *cq++; + ctl_putdata(buffer, (unsigned)( cp - buffer ), 0); +} + + +/* + * ctl_putarray - write a tagged eight element double array into the response + */ +static void +ctl_putarray( + const char *tag, + double *arr, + int start + ) +{ + register char *cp; + register const char *cq; + char buffer[200]; + int i; + cp = buffer; + cq = tag; + while (*cq != '\0') + *cp++ = *cq++; + i = start; + do { + if (i == 0) + i = NTP_SHIFT; + i--; + (void)sprintf(cp, " %.2f", arr[i] * 1e3); + while (*cp != '\0') + cp++; + } while(i != start); + ctl_putdata(buffer, (unsigned)(cp - buffer), 0); +} + + +/* + * ctl_putsys - output a system variable + */ +static void +ctl_putsys( + int varid + ) +{ + l_fp tmp; + char str[256]; +#ifdef OPENSSL + struct cert_info *cp; + char cbuf[256]; +#endif /* OPENSSL */ + + switch (varid) { + + case CS_LEAP: + ctl_putuint(sys_var[CS_LEAP].text, sys_leap); + break; + + case CS_STRATUM: + ctl_putuint(sys_var[CS_STRATUM].text, sys_stratum); + break; + + case CS_PRECISION: + ctl_putint(sys_var[CS_PRECISION].text, sys_precision); + break; + + case CS_ROOTDELAY: + ctl_putdbl(sys_var[CS_ROOTDELAY].text, sys_rootdelay * + 1e3); + break; + + case CS_ROOTDISPERSION: + ctl_putdbl(sys_var[CS_ROOTDISPERSION].text, + sys_rootdispersion * 1e3); + break; + + case CS_REFID: + if (sys_stratum > 1 && sys_stratum < STRATUM_UNSPEC) + ctl_putadr(sys_var[CS_REFID].text, sys_refid, NULL); + else + ctl_putid(sys_var[CS_REFID].text, + (char *)&sys_refid); + break; + + case CS_REFTIME: + ctl_putts(sys_var[CS_REFTIME].text, &sys_reftime); + break; + + case CS_POLL: + ctl_putuint(sys_var[CS_POLL].text, sys_poll); + break; + + case CS_PEERID: + if (sys_peer == NULL) + ctl_putuint(sys_var[CS_PEERID].text, 0); + else + ctl_putuint(sys_var[CS_PEERID].text, + sys_peer->associd); + break; + + case CS_STATE: + ctl_putuint(sys_var[CS_STATE].text, (unsigned)state); + break; + + case CS_OFFSET: + ctl_putdbl(sys_var[CS_OFFSET].text, last_offset * 1e3); + break; + + case CS_DRIFT: + ctl_putdbl(sys_var[CS_DRIFT].text, drift_comp * 1e6); + break; + + case CS_JITTER: + ctl_putdbl(sys_var[CS_JITTER].text, sys_jitter * 1e3); + break; + + case CS_CLOCK: + get_systime(&tmp); + ctl_putts(sys_var[CS_CLOCK].text, &tmp); + break; + + case CS_PROCESSOR: +#ifndef HAVE_UNAME + ctl_putstr(sys_var[CS_PROCESSOR].text, str_processor, + sizeof(str_processor) - 1); +#else + ctl_putstr(sys_var[CS_PROCESSOR].text, + utsnamebuf.machine, strlen(utsnamebuf.machine)); +#endif /* HAVE_UNAME */ + break; + + case CS_SYSTEM: +#ifndef HAVE_UNAME + ctl_putstr(sys_var[CS_SYSTEM].text, str_system, + sizeof(str_system) - 1); +#else + sprintf(str, "%s/%s", utsnamebuf.sysname, utsnamebuf.release); + ctl_putstr(sys_var[CS_SYSTEM].text, str, strlen(str)); +#endif /* HAVE_UNAME */ + break; + + case CS_VERSION: + ctl_putstr(sys_var[CS_VERSION].text, Version, + strlen(Version)); + break; + + case CS_STABIL: + ctl_putdbl(sys_var[CS_STABIL].text, clock_stability * + 1e6); + break; + + case CS_VARLIST: + { + char buf[CTL_MAX_DATA_LEN]; + register char *s, *t, *be; + register const char *ss; + register int i; + register struct ctl_var *k; + + s = buf; + be = buf + sizeof(buf) - + strlen(sys_var[CS_VARLIST].text) - 4; + if (s > be) + break; /* really long var name */ + + strcpy(s, sys_var[CS_VARLIST].text); + strcat(s, "=\""); + s += strlen(s); + t = s; + for (k = sys_var; !(k->flags &EOV); k++) { + if (k->flags & PADDING) + continue; + i = strlen(k->text); + if (s+i+1 >= be) + break; + + if (s != t) + *s++ = ','; + strcpy(s, k->text); + s += i; + } + + for (k = ext_sys_var; k && !(k->flags &EOV); + k++) { + if (k->flags & PADDING) + continue; + + ss = k->text; + if (!ss) + continue; + + while (*ss && *ss != '=') + ss++; + i = ss - k->text; + if (s + i + 1 >= be) + break; + + if (s != t) + *s++ = ','; + strncpy(s, k->text, + (unsigned)i); + s += i; + } + if (s+2 >= be) + break; + + *s++ = '"'; + *s = '\0'; + + ctl_putdata(buf, (unsigned)( s - buf ), + 0); + } + break; + +#ifdef OPENSSL + case CS_FLAGS: + if (crypto_flags) { + ctl_puthex(sys_var[CS_FLAGS].text, crypto_flags); + } + break; + + case CS_DIGEST: + if (crypto_flags) { + const EVP_MD *dp; + + dp = EVP_get_digestbynid(crypto_flags >> 16); + strcpy(str, OBJ_nid2ln(EVP_MD_pkey_type(dp))); + ctl_putstr(sys_var[CS_DIGEST].text, str, + strlen(str)); + } + break; + + case CS_HOST: + if (sys_hostname != NULL) + ctl_putstr(sys_var[CS_HOST].text, sys_hostname, + strlen(sys_hostname)); + break; + + case CS_CERTIF: + for (cp = cinfo; cp != NULL; cp = cp->link) { + sprintf(cbuf, "%s %s 0x%x %u", cp->subject, + cp->issuer, cp->flags, + ntohl(cp->cert.fstamp)); + ctl_putstr(sys_var[CS_CERTIF].text, cbuf, + strlen(cbuf)); + } + break; + + case CS_PUBLIC: + if (hostval.fstamp != 0) + ctl_putuint(sys_var[CS_PUBLIC].text, + ntohl(hostval.fstamp)); + break; + + case CS_REVTIME: + if (hostval.tstamp != 0) + ctl_putuint(sys_var[CS_REVTIME].text, + ntohl(hostval.tstamp)); + break; + + case CS_LEAPTAB: + if (tai_leap.fstamp != 0) + ctl_putuint(sys_var[CS_LEAPTAB].text, + ntohl(tai_leap.fstamp)); + if (sys_tai != 0) + ctl_putuint(sys_var[CS_TAI].text, sys_tai); + break; +#endif /* OPENSSL */ + } +} + + +/* + * ctl_putpeer - output a peer variable + */ +static void +ctl_putpeer( + int varid, + struct peer *peer + ) +{ +#ifdef OPENSSL + char str[256]; + struct autokey *ap; +#endif /* OPENSSL */ + + switch (varid) { + + case CP_CONFIG: + ctl_putuint(peer_var[CP_CONFIG].text, + (unsigned)((peer->flags & FLAG_CONFIG) != 0)); + break; + + case CP_AUTHENABLE: + ctl_putuint(peer_var[CP_AUTHENABLE].text, + (unsigned)((peer->flags & FLAG_AUTHENABLE) != 0)); + break; + + case CP_AUTHENTIC: + ctl_putuint(peer_var[CP_AUTHENTIC].text, + (unsigned)((peer->flags & FLAG_AUTHENTIC) != 0)); + break; + + case CP_SRCADR: + ctl_putadr(peer_var[CP_SRCADR].text, 0, + &peer->srcadr); + break; + + case CP_SRCPORT: + ctl_putuint(peer_var[CP_SRCPORT].text, + ntohs(((struct sockaddr_in*)&peer->srcadr)->sin_port)); + break; + + case CP_DSTADR: + ctl_putadr(peer_var[CP_DSTADR].text, 0, + &(peer->dstadr->sin)); + break; + + case CP_DSTPORT: + ctl_putuint(peer_var[CP_DSTPORT].text, + (u_long)(peer->dstadr ? + ntohs(((struct sockaddr_in*)&peer->dstadr->sin)->sin_port) : 0)); + break; + + case CP_LEAP: + ctl_putuint(peer_var[CP_LEAP].text, peer->leap); + break; + + case CP_HMODE: + ctl_putuint(peer_var[CP_HMODE].text, peer->hmode); + break; + + case CP_STRATUM: + ctl_putuint(peer_var[CP_STRATUM].text, peer->stratum); + break; + + case CP_PPOLL: + ctl_putuint(peer_var[CP_PPOLL].text, peer->ppoll); + break; + + case CP_HPOLL: + ctl_putuint(peer_var[CP_HPOLL].text, peer->hpoll); + break; + + case CP_PRECISION: + ctl_putint(peer_var[CP_PRECISION].text, + peer->precision); + break; + + case CP_ROOTDELAY: + ctl_putdbl(peer_var[CP_ROOTDELAY].text, + peer->rootdelay * 1e3); + break; + + case CP_ROOTDISPERSION: + ctl_putdbl(peer_var[CP_ROOTDISPERSION].text, + peer->rootdispersion * 1e3); + break; + + case CP_REFID: + if (peer->flags & FLAG_REFCLOCK) { + if (peer->stratum > 0 && peer->stratum < + STRATUM_UNSPEC) + ctl_putadr(peer_var[CP_REFID].text, + peer->refid, NULL); + else + ctl_putid(peer_var[CP_REFID].text, + (char *)&peer->refid); + } else { + if (peer->stratum > 1 && peer->stratum < + STRATUM_UNSPEC) + ctl_putadr(peer_var[CP_REFID].text, + peer->refid, NULL); + else + ctl_putid(peer_var[CP_REFID].text, + (char *)&peer->refid); + } + break; + + case CP_REFTIME: + ctl_putts(peer_var[CP_REFTIME].text, &peer->reftime); + break; + + case CP_ORG: + ctl_putts(peer_var[CP_ORG].text, &peer->org); + break; + + case CP_REC: + ctl_putts(peer_var[CP_REC].text, &peer->rec); + break; + + case CP_XMT: + ctl_putts(peer_var[CP_XMT].text, &peer->xmt); + break; + + case CP_REACH: + ctl_puthex(peer_var[CP_REACH].text, peer->reach); + break; + + case CP_FLASH: + ctl_puthex(peer_var[CP_FLASH].text, peer->flash); + break; + + case CP_TTL: + ctl_putint(peer_var[CP_TTL].text, sys_ttl[peer->ttl]); + break; + + case CP_VALID: + ctl_putuint(peer_var[CP_VALID].text, peer->unreach); + break; + + case CP_RANK: + ctl_putuint(peer_var[CP_RANK].text, peer->rank); + break; + + case CP_TIMER: + ctl_putuint(peer_var[CP_TIMER].text, + peer->nextdate - current_time); + break; + + case CP_DELAY: + ctl_putdbl(peer_var[CP_DELAY].text, peer->delay * 1e3); + break; + + case CP_OFFSET: + ctl_putdbl(peer_var[CP_OFFSET].text, peer->offset * + 1e3); + break; + + case CP_JITTER: + ctl_putdbl(peer_var[CP_JITTER].text, + SQRT(peer->jitter) * 1e3); + break; + + case CP_DISPERSION: + ctl_putdbl(peer_var[CP_DISPERSION].text, peer->disp * + 1e3); + break; + + case CP_KEYID: + ctl_putuint(peer_var[CP_KEYID].text, peer->keyid); + break; + + case CP_FILTDELAY: + ctl_putarray(peer_var[CP_FILTDELAY].text, + peer->filter_delay, (int)peer->filter_nextpt); + break; + + case CP_FILTOFFSET: + ctl_putarray(peer_var[CP_FILTOFFSET].text, + peer->filter_offset, (int)peer->filter_nextpt); + break; + + case CP_FILTERROR: + ctl_putarray(peer_var[CP_FILTERROR].text, + peer->filter_disp, (int)peer->filter_nextpt); + break; + + case CP_PMODE: + ctl_putuint(peer_var[CP_PMODE].text, peer->pmode); + break; + + case CP_RECEIVED: + ctl_putuint(peer_var[CP_RECEIVED].text, peer->received); + break; + + case CP_SENT: + ctl_putuint(peer_var[CP_SENT].text, peer->sent); + break; + + case CP_VARLIST: + { + char buf[CTL_MAX_DATA_LEN]; + register char *s, *t, *be; + register int i; + register struct ctl_var *k; + + s = buf; + be = buf + sizeof(buf) - + strlen(peer_var[CP_VARLIST].text) - 4; + if (s > be) + break; /* really long var name */ + + strcpy(s, peer_var[CP_VARLIST].text); + strcat(s, "=\""); + s += strlen(s); + t = s; + for (k = peer_var; !(k->flags &EOV); k++) { + if (k->flags & PADDING) + continue; + + i = strlen(k->text); + if (s + i + 1 >= be) + break; + + if (s != t) + *s++ = ','; + strcpy(s, k->text); + s += i; + } + if (s+2 >= be) + break; + + *s++ = '"'; + *s = '\0'; + ctl_putdata(buf, (unsigned)(s - buf), 0); + } + break; +#ifdef OPENSSL + case CP_FLAGS: + if (peer->crypto) + ctl_puthex(peer_var[CP_FLAGS].text, peer->crypto); + break; + + case CP_DIGEST: + if (peer->crypto) { + const EVP_MD *dp; + + dp = EVP_get_digestbynid(peer->crypto >> 16); + strcpy(str, OBJ_nid2ln(EVP_MD_pkey_type(dp))); + ctl_putstr(peer_var[CP_DIGEST].text, str, + strlen(str)); + } + break; + + case CP_HOST: + if (peer->subject != NULL) + ctl_putstr(peer_var[CP_HOST].text, peer->subject, + strlen(peer->subject)); + break; + + case CP_IDENT: + if (peer->issuer != NULL) + ctl_putstr(peer_var[CP_IDENT].text, peer->issuer, + strlen(peer->issuer)); + break; + + case CP_INITSEQ: + if ((ap = (struct autokey *)peer->recval.ptr) == NULL) + break; + ctl_putint(peer_var[CP_INITSEQ].text, ap->seq); + ctl_puthex(peer_var[CP_INITKEY].text, ap->key); + ctl_putuint(peer_var[CP_INITTSP].text, + ntohl(peer->recval.tstamp)); + break; +#endif /* OPENSSL */ + } +} + + +#ifdef REFCLOCK +/* + * ctl_putclock - output clock variables + */ +static void +ctl_putclock( + int varid, + struct refclockstat *clock_stat, + int mustput + ) +{ + switch(varid) { + + case CC_TYPE: + if (mustput || clock_stat->clockdesc == NULL + || *(clock_stat->clockdesc) == '\0') { + ctl_putuint(clock_var[CC_TYPE].text, clock_stat->type); + } + break; + case CC_TIMECODE: + ctl_putstr(clock_var[CC_TIMECODE].text, + clock_stat->p_lastcode, + (unsigned)clock_stat->lencode); + break; + + case CC_POLL: + ctl_putuint(clock_var[CC_POLL].text, clock_stat->polls); + break; + + case CC_NOREPLY: + ctl_putuint(clock_var[CC_NOREPLY].text, + clock_stat->noresponse); + break; + + case CC_BADFORMAT: + ctl_putuint(clock_var[CC_BADFORMAT].text, + clock_stat->badformat); + break; + + case CC_BADDATA: + ctl_putuint(clock_var[CC_BADDATA].text, + clock_stat->baddata); + break; + + case CC_FUDGETIME1: + if (mustput || (clock_stat->haveflags & CLK_HAVETIME1)) + ctl_putdbl(clock_var[CC_FUDGETIME1].text, + clock_stat->fudgetime1 * 1e3); + break; + + case CC_FUDGETIME2: + if (mustput || (clock_stat->haveflags & CLK_HAVETIME2)) ctl_putdbl(clock_var[CC_FUDGETIME2].text, + clock_stat->fudgetime2 * 1e3); + break; + + case CC_FUDGEVAL1: + if (mustput || (clock_stat->haveflags & CLK_HAVEVAL1)) + ctl_putint(clock_var[CC_FUDGEVAL1].text, + clock_stat->fudgeval1); + break; + + case CC_FUDGEVAL2: + if (mustput || (clock_stat->haveflags & CLK_HAVEVAL2)) { + if (clock_stat->fudgeval1 > 1) + ctl_putadr(clock_var[CC_FUDGEVAL2].text, + (u_int32)clock_stat->fudgeval2, NULL); + else + ctl_putid(clock_var[CC_FUDGEVAL2].text, + (char *)&clock_stat->fudgeval2); + } + break; + + case CC_FLAGS: + if (mustput || (clock_stat->haveflags & (CLK_HAVEFLAG1 | + CLK_HAVEFLAG2 | CLK_HAVEFLAG3 | CLK_HAVEFLAG4))) + ctl_putuint(clock_var[CC_FLAGS].text, + clock_stat->flags); + break; + + case CC_DEVICE: + if (clock_stat->clockdesc == NULL || + *(clock_stat->clockdesc) == '\0') { + if (mustput) + ctl_putstr(clock_var[CC_DEVICE].text, + "", 0); + } else { + ctl_putstr(clock_var[CC_DEVICE].text, + clock_stat->clockdesc, + strlen(clock_stat->clockdesc)); + } + break; + + case CC_VARLIST: + { + char buf[CTL_MAX_DATA_LEN]; + register char *s, *t, *be; + register const char *ss; + register int i; + register struct ctl_var *k; + + s = buf; + be = buf + sizeof(buf); + if (s + strlen(clock_var[CC_VARLIST].text) + 4 > + be) + break; /* really long var name */ + + strcpy(s, clock_var[CC_VARLIST].text); + strcat(s, "=\""); + s += strlen(s); + t = s; + + for (k = clock_var; !(k->flags &EOV); k++) { + if (k->flags & PADDING) + continue; + + i = strlen(k->text); + if (s + i + 1 >= be) + break; + + if (s != t) + *s++ = ','; + strcpy(s, k->text); + s += i; + } + + for (k = clock_stat->kv_list; k && !(k->flags & + EOV); k++) { + if (k->flags & PADDING) + continue; + + ss = k->text; + if (!ss) + continue; + + while (*ss && *ss != '=') + ss++; + i = ss - k->text; + if (s+i+1 >= be) + break; + + if (s != t) + *s++ = ','; + strncpy(s, k->text, (unsigned)i); + s += i; + *s = '\0'; + } + if (s+2 >= be) + break; + + *s++ = '"'; + *s = '\0'; + ctl_putdata(buf, (unsigned)( s - buf ), 0); + } + break; + } +} +#endif + + + +/* + * ctl_getitem - get the next data item from the incoming packet + */ +static struct ctl_var * +ctl_getitem( + struct ctl_var *var_list, + char **data + ) +{ + register struct ctl_var *v; + register char *cp; + register char *tp; + static struct ctl_var eol = { 0, EOV, }; + static char buf[128]; + + /* + * Delete leading commas and white space + */ + while (reqpt < reqend && (*reqpt == ',' || + isspace((int)*reqpt))) + reqpt++; + if (reqpt >= reqend) + return (0); + + if (var_list == (struct ctl_var *)0) + return (&eol); + + /* + * Look for a first character match on the tag. If we find + * one, see if it is a full match. + */ + v = var_list; + cp = reqpt; + while (!(v->flags & EOV)) { + if (!(v->flags & PADDING) && *cp == *(v->text)) { + tp = v->text; + while (*tp != '\0' && *tp != '=' && cp < + reqend && *cp == *tp) { + cp++; + tp++; + } + if ((*tp == '\0') || (*tp == '=')) { + while (cp < reqend && isspace((int)*cp)) + cp++; + if (cp == reqend || *cp == ',') { + buf[0] = '\0'; + *data = buf; + if (cp < reqend) + cp++; + reqpt = cp; + return v; + } + if (*cp == '=') { + cp++; + tp = buf; + while (cp < reqend && isspace((int)*cp)) + cp++; + while (cp < reqend && *cp != ',') { + *tp++ = *cp++; + if (tp >= buf + sizeof(buf)) { + ctl_error(CERR_BADFMT); + numctlbadpkts++; + msyslog(LOG_WARNING, + "Possible 'ntpdx' exploit from %s:%d (possibly spoofed)\n", + stoa(rmt_addr), SRCPORT(rmt_addr) + ); + return (0); + } + } + if (cp < reqend) + cp++; + *tp-- = '\0'; + while (tp >= buf) { + if (!isspace((int)(*tp))) + break; + *tp-- = '\0'; + } + reqpt = cp; + *data = buf; + return (v); + } + } + cp = reqpt; + } + v++; + } + return v; +} + + +/* + * control_unspec - response to an unspecified op-code + */ +/*ARGSUSED*/ +static void +control_unspec( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + struct peer *peer; + + /* + * What is an appropriate response to an unspecified op-code? + * I return no errors and no data, unless a specified assocation + * doesn't exist. + */ + if (res_associd != 0) { + if ((peer = findpeerbyassoc(res_associd)) == 0) { + ctl_error(CERR_BADASSOC); + return; + } + rpkt.status = htons(ctlpeerstatus(peer)); + } else { + rpkt.status = htons(ctlsysstatus()); + } + ctl_flushpkt(0); +} + + +/* + * read_status - return either a list of associd's, or a particular + * peer's status. + */ +/*ARGSUSED*/ +static void +read_status( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + register int i; + register struct peer *peer; + u_short ass_stat[CTL_MAX_DATA_LEN / sizeof(u_short)]; + +#ifdef DEBUG + if (debug > 2) + printf("read_status: ID %d\n", res_associd); +#endif + /* + * Two choices here. If the specified association ID is + * zero we return all known assocation ID's. Otherwise + * we return a bunch of stuff about the particular peer. + */ + if (res_associd == 0) { + register int n; + + n = 0; + rpkt.status = htons(ctlsysstatus()); + for (i = 0; i < HASH_SIZE; i++) { + for (peer = assoc_hash[i]; peer != 0; + peer = peer->ass_next) { + ass_stat[n++] = htons(peer->associd); + ass_stat[n++] = + htons(ctlpeerstatus(peer)); + if (n == + CTL_MAX_DATA_LEN/sizeof(u_short)) { + ctl_putdata((char *)ass_stat, + n * sizeof(u_short), 1); + n = 0; + } + } + } + + if (n != 0) + ctl_putdata((char *)ass_stat, n * + sizeof(u_short), 1); + ctl_flushpkt(0); + } else { + peer = findpeerbyassoc(res_associd); + if (peer == 0) { + ctl_error(CERR_BADASSOC); + } else { + register u_char *cp; + + rpkt.status = htons(ctlpeerstatus(peer)); + if (res_authokay) + peer->num_events = 0; + /* + * For now, output everything we know about the + * peer. May be more selective later. + */ + for (cp = def_peer_var; *cp != 0; cp++) + ctl_putpeer((int)*cp, peer); + ctl_flushpkt(0); + } + } +} + + +/* + * read_variables - return the variables the caller asks for + */ +/*ARGSUSED*/ +static void +read_variables( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + register struct ctl_var *v; + register int i; + char *valuep; + u_char *wants; + unsigned int gotvar = (CS_MAXCODE > CP_MAXCODE) ? (CS_MAXCODE + + 1) : (CP_MAXCODE + 1); + if (res_associd == 0) { + /* + * Wants system variables. Figure out which he wants + * and give them to him. + */ + rpkt.status = htons(ctlsysstatus()); + if (res_authokay) + ctl_sys_num_events = 0; + gotvar += count_var(ext_sys_var); + wants = (u_char *)emalloc(gotvar); + memset((char *)wants, 0, gotvar); + gotvar = 0; + while ((v = ctl_getitem(sys_var, &valuep)) != 0) { + if (v->flags & EOV) { + if ((v = ctl_getitem(ext_sys_var, + &valuep)) != 0) { + if (v->flags & EOV) { + ctl_error(CERR_UNKNOWNVAR); + free((char *)wants); + return; + } + wants[CS_MAXCODE + 1 + + v->code] = 1; + gotvar = 1; + continue; + } else { + break; /* shouldn't happen ! */ + } + } + wants[v->code] = 1; + gotvar = 1; + } + if (gotvar) { + for (i = 1; i <= CS_MAXCODE; i++) + if (wants[i]) + ctl_putsys(i); + for (i = 0; ext_sys_var && + !(ext_sys_var[i].flags & EOV); i++) + if (wants[i + CS_MAXCODE + 1]) + ctl_putdata(ext_sys_var[i].text, + strlen(ext_sys_var[i].text), + 0); + } else { + register u_char *cs; + register struct ctl_var *kv; + + for (cs = def_sys_var; *cs != 0; cs++) + ctl_putsys((int)*cs); + for (kv = ext_sys_var; kv && !(kv->flags & EOV); + kv++) + if (kv->flags & DEF) + ctl_putdata(kv->text, + strlen(kv->text), 0); + } + free((char *)wants); + } else { + register struct peer *peer; + + /* + * Wants info for a particular peer. See if we know + * the guy. + */ + peer = findpeerbyassoc(res_associd); + if (peer == 0) { + ctl_error(CERR_BADASSOC); + return; + } + rpkt.status = htons(ctlpeerstatus(peer)); + if (res_authokay) + peer->num_events = 0; + wants = (u_char *)emalloc(gotvar); + memset((char*)wants, 0, gotvar); + gotvar = 0; + while ((v = ctl_getitem(peer_var, &valuep)) != 0) { + if (v->flags & EOV) { + ctl_error(CERR_UNKNOWNVAR); + free((char *)wants); + return; + } + wants[v->code] = 1; + gotvar = 1; + } + if (gotvar) { + for (i = 1; i <= CP_MAXCODE; i++) + if (wants[i]) + ctl_putpeer(i, peer); + } else { + register u_char *cp; + + for (cp = def_peer_var; *cp != 0; cp++) + ctl_putpeer((int)*cp, peer); + } + free((char *)wants); + } + ctl_flushpkt(0); +} + + +/* + * write_variables - write into variables. We only allow leap bit + * writing this way. + */ +/*ARGSUSED*/ +static void +write_variables( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + register struct ctl_var *v; + register int ext_var; + char *valuep; + long val = 0; + + /* + * If he's trying to write into a peer tell him no way + */ + if (res_associd != 0) { + ctl_error(CERR_PERMISSION); + return; + } + + /* + * Set status + */ + rpkt.status = htons(ctlsysstatus()); + + /* + * Look through the variables. Dump out at the first sign of + * trouble. + */ + while ((v = ctl_getitem(sys_var, &valuep)) != 0) { + ext_var = 0; + if (v->flags & EOV) { + if ((v = ctl_getitem(ext_sys_var, &valuep)) != + 0) { + if (v->flags & EOV) { + ctl_error(CERR_UNKNOWNVAR); + return; + } + ext_var = 1; + } else { + break; + } + } + if (!(v->flags & CAN_WRITE)) { + ctl_error(CERR_PERMISSION); + return; + } + if (!ext_var && (*valuep == '\0' || !atoint(valuep, + &val))) { + ctl_error(CERR_BADFMT); + return; + } + if (!ext_var && (val & ~LEAP_NOTINSYNC) != 0) { + ctl_error(CERR_BADVALUE); + return; + } + + if (ext_var) { + char *s = (char *)emalloc(strlen(v->text) + + strlen(valuep) + 2); + const char *t; + char *tt = s; + + t = v->text; + while (*t && *t != '=') + *tt++ = *t++; + + *tt++ = '='; + strcat(tt, valuep); + set_sys_var(s, strlen(s)+1, v->flags); + free(s); + } else { + /* + * This one seems sane. Save it. + */ + switch(v->code) { + + case CS_LEAP: + default: + ctl_error(CERR_UNSPEC); /* really */ + return; + } + } + } + + /* + * If we got anything, do it. xxx nothing to do *** + */ + /* + if (leapind != ~0 || leapwarn != ~0) { + if (!leap_setleap((int)leapind, (int)leapwarn)) { + ctl_error(CERR_PERMISSION); + return; + } + } + */ + ctl_flushpkt(0); +} + + +/* + * read_clock_status - return clock radio status + */ +/*ARGSUSED*/ +static void +read_clock_status( + struct recvbuf *rbufp, + int restrict_mask + ) +{ +#ifndef REFCLOCK + /* + * If no refclock support, no data to return + */ + ctl_error(CERR_BADASSOC); +#else + register struct ctl_var *v; + register int i; + register struct peer *peer; + char *valuep; + u_char *wants; + unsigned int gotvar; + struct refclockstat clock_stat; + + if (res_associd == 0) { + + /* + * Find a clock for this jerk. If the system peer + * is a clock use it, else search the hash tables + * for one. + */ + if (sys_peer != 0 && (sys_peer->flags & FLAG_REFCLOCK)) + { + peer = sys_peer; + } else { + peer = 0; + for (i = 0; peer == 0 && i < HASH_SIZE; i++) { + for (peer = assoc_hash[i]; peer != 0; + peer = peer->ass_next) { + if (peer->flags & FLAG_REFCLOCK) + break; + } + } + if (peer == 0) { + ctl_error(CERR_BADASSOC); + return; + } + } + } else { + peer = findpeerbyassoc(res_associd); + if (peer == 0 || !(peer->flags & FLAG_REFCLOCK)) { + ctl_error(CERR_BADASSOC); + return; + } + } + + /* + * If we got here we have a peer which is a clock. Get his + * status. + */ + clock_stat.kv_list = (struct ctl_var *)0; + refclock_control(&peer->srcadr, (struct refclockstat *)0, + &clock_stat); + + /* + * Look for variables in the packet. + */ + rpkt.status = htons(ctlclkstatus(&clock_stat)); + gotvar = CC_MAXCODE + 1 + count_var(clock_stat.kv_list); + wants = (u_char *)emalloc(gotvar); + memset((char*)wants, 0, gotvar); + gotvar = 0; + while ((v = ctl_getitem(clock_var, &valuep)) != 0) { + if (v->flags & EOV) { + if ((v = ctl_getitem(clock_stat.kv_list, + &valuep)) != 0) { + if (v->flags & EOV) { + ctl_error(CERR_UNKNOWNVAR); + free((char*)wants); + free_varlist(clock_stat.kv_list); + return; + } + wants[CC_MAXCODE + 1 + v->code] = 1; + gotvar = 1; + continue; + } else { + break; /* shouldn't happen ! */ + } + } + wants[v->code] = 1; + gotvar = 1; + } + + if (gotvar) { + for (i = 1; i <= CC_MAXCODE; i++) + if (wants[i]) + ctl_putclock(i, &clock_stat, 1); + for (i = 0; clock_stat.kv_list && + !(clock_stat.kv_list[i].flags & EOV); i++) + if (wants[i + CC_MAXCODE + 1]) + ctl_putdata(clock_stat.kv_list[i].text, + strlen(clock_stat.kv_list[i].text), + 0); + } else { + register u_char *cc; + register struct ctl_var *kv; + + for (cc = def_clock_var; *cc != 0; cc++) + ctl_putclock((int)*cc, &clock_stat, 0); + for (kv = clock_stat.kv_list; kv && !(kv->flags & EOV); + kv++) + if (kv->flags & DEF) + ctl_putdata(kv->text, strlen(kv->text), + 0); + } + + free((char*)wants); + free_varlist(clock_stat.kv_list); + + ctl_flushpkt(0); +#endif +} + + +/* + * write_clock_status - we don't do this + */ +/*ARGSUSED*/ +static void +write_clock_status( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + ctl_error(CERR_PERMISSION); +} + +/* + * Trap support from here on down. We send async trap messages when the + * upper levels report trouble. Traps can by set either by control + * messages or by configuration. + */ +/* + * set_trap - set a trap in response to a control message + */ +static void +set_trap( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + int traptype; + + /* + * See if this guy is allowed + */ + if (restrict_mask & RES_NOTRAP) { + ctl_error(CERR_PERMISSION); + return; + } + + /* + * Determine his allowed trap type. + */ + traptype = TRAP_TYPE_PRIO; + if (restrict_mask & RES_LPTRAP) + traptype = TRAP_TYPE_NONPRIO; + + /* + * Call ctlsettrap() to do the work. Return + * an error if it can't assign the trap. + */ + if (!ctlsettrap(&rbufp->recv_srcadr, rbufp->dstadr, traptype, + (int)res_version)) + ctl_error(CERR_NORESOURCE); + ctl_flushpkt(0); +} + + +/* + * unset_trap - unset a trap in response to a control message + */ +static void +unset_trap( + struct recvbuf *rbufp, + int restrict_mask + ) +{ + int traptype; + + /* + * We don't prevent anyone from removing his own trap unless the + * trap is configured. Note we also must be aware of the + * possibility that restriction flags were changed since this + * guy last set his trap. Set the trap type based on this. + */ + traptype = TRAP_TYPE_PRIO; + if (restrict_mask & RES_LPTRAP) + traptype = TRAP_TYPE_NONPRIO; + + /* + * Call ctlclrtrap() to clear this out. + */ + if (!ctlclrtrap(&rbufp->recv_srcadr, rbufp->dstadr, traptype)) + ctl_error(CERR_BADASSOC); + ctl_flushpkt(0); +} + + +/* + * ctlsettrap - called to set a trap + */ +int +ctlsettrap( + struct sockaddr_storage *raddr, + struct interface *linter, + int traptype, + int version + ) +{ + register struct ctl_trap *tp; + register struct ctl_trap *tptouse; + + /* + * See if we can find this trap. If so, we only need update + * the flags and the time. + */ + if ((tp = ctlfindtrap(raddr, linter)) != NULL) { + switch (traptype) { + + case TRAP_TYPE_CONFIG: + tp->tr_flags = TRAP_INUSE|TRAP_CONFIGURED; + break; + + case TRAP_TYPE_PRIO: + if (tp->tr_flags & TRAP_CONFIGURED) + return (1); /* don't change anything */ + tp->tr_flags = TRAP_INUSE; + break; + + case TRAP_TYPE_NONPRIO: + if (tp->tr_flags & TRAP_CONFIGURED) + return (1); /* don't change anything */ + tp->tr_flags = TRAP_INUSE|TRAP_NONPRIO; + break; + } + tp->tr_settime = current_time; + tp->tr_resets++; + return (1); + } + + /* + * First we heard of this guy. Try to find a trap structure + * for him to use, clearing out lesser priority guys if we + * have to. Clear out anyone who's expired while we're at it. + */ + tptouse = NULL; + for (tp = ctl_trap; tp < &ctl_trap[CTL_MAXTRAPS]; tp++) { + if ((tp->tr_flags & TRAP_INUSE) && + !(tp->tr_flags & TRAP_CONFIGURED) && + ((tp->tr_settime + CTL_TRAPTIME) > current_time)) { + tp->tr_flags = 0; + num_ctl_traps--; + } + if (!(tp->tr_flags & TRAP_INUSE)) { + tptouse = tp; + } else if (!(tp->tr_flags & TRAP_CONFIGURED)) { + switch (traptype) { + + case TRAP_TYPE_CONFIG: + if (tptouse == NULL) { + tptouse = tp; + break; + } + if (tptouse->tr_flags & TRAP_NONPRIO && + !(tp->tr_flags & TRAP_NONPRIO)) + break; + + if (!(tptouse->tr_flags & TRAP_NONPRIO) + && tp->tr_flags & TRAP_NONPRIO) { + tptouse = tp; + break; + } + if (tptouse->tr_origtime < + tp->tr_origtime) + tptouse = tp; + break; + + case TRAP_TYPE_PRIO: + if (tp->tr_flags & TRAP_NONPRIO) { + if (tptouse == NULL || + (tptouse->tr_flags & + TRAP_INUSE && + tptouse->tr_origtime < + tp->tr_origtime)) + tptouse = tp; + } + break; + + case TRAP_TYPE_NONPRIO: + break; + } + } + } + + /* + * If we don't have room for him return an error. + */ + if (tptouse == NULL) + return (0); + + /* + * Set up this structure for him. + */ + tptouse->tr_settime = tptouse->tr_origtime = current_time; + tptouse->tr_count = tptouse->tr_resets = 0; + tptouse->tr_sequence = 1; + tptouse->tr_addr = *raddr; + tptouse->tr_localaddr = linter; + tptouse->tr_version = (u_char) version; + tptouse->tr_flags = TRAP_INUSE; + if (traptype == TRAP_TYPE_CONFIG) + tptouse->tr_flags |= TRAP_CONFIGURED; + else if (traptype == TRAP_TYPE_NONPRIO) + tptouse->tr_flags |= TRAP_NONPRIO; + num_ctl_traps++; + return (1); +} + + +/* + * ctlclrtrap - called to clear a trap + */ +int +ctlclrtrap( + struct sockaddr_storage *raddr, + struct interface *linter, + int traptype + ) +{ + register struct ctl_trap *tp; + + if ((tp = ctlfindtrap(raddr, linter)) == NULL) + return (0); + + if (tp->tr_flags & TRAP_CONFIGURED + && traptype != TRAP_TYPE_CONFIG) + return (0); + + tp->tr_flags = 0; + num_ctl_traps--; + return (1); +} + + +/* + * ctlfindtrap - find a trap given the remote and local addresses + */ +static struct ctl_trap * +ctlfindtrap( + struct sockaddr_storage *raddr, + struct interface *linter + ) +{ + register struct ctl_trap *tp; + + for (tp = ctl_trap; tp < &ctl_trap[CTL_MAXTRAPS]; tp++) { + if ((tp->tr_flags & TRAP_INUSE) + && (NSRCPORT(raddr) == NSRCPORT(&tp->tr_addr)) + && SOCKCMP(raddr, &tp->tr_addr) + && (linter == tp->tr_localaddr) ) + return (tp); + } + return (struct ctl_trap *)NULL; +} + + +/* + * report_event - report an event to the trappers + */ +void +report_event( + int err, + struct peer *peer + ) +{ + register int i; + + /* + * Record error code in proper spots, but have mercy on the + * log file. + */ + if (!(err & (PEER_EVENT | CRPT_EVENT))) { + if (ctl_sys_num_events < CTL_SYS_MAXEVENTS) + ctl_sys_num_events++; + if (ctl_sys_last_event != (u_char)err) { + NLOG(NLOG_SYSEVENT) + msyslog(LOG_INFO, "system event '%s' (0x%02x) status '%s' (0x%02x)", + eventstr(err), err, + sysstatstr(ctlsysstatus()), ctlsysstatus()); +#ifdef DEBUG + if (debug) + printf("report_event: system event '%s' (0x%02x) status '%s' (0x%02x)\n", + eventstr(err), err, + sysstatstr(ctlsysstatus()), + ctlsysstatus()); +#endif + ctl_sys_last_event = (u_char)err; + } + } else if (peer != 0) { + char *src; + +#ifdef REFCLOCK + if (ISREFCLOCKADR(&peer->srcadr)) + src = refnumtoa(&peer->srcadr); + else +#endif + src = stoa(&peer->srcadr); + + peer->last_event = (u_char)(err & ~PEER_EVENT); + if (peer->num_events < CTL_PEER_MAXEVENTS) + peer->num_events++; + NLOG(NLOG_PEEREVENT) + msyslog(LOG_INFO, "peer %s event '%s' (0x%02x) status '%s' (0x%02x)", + src, eventstr(err), err, + peerstatstr(ctlpeerstatus(peer)), + ctlpeerstatus(peer)); +#ifdef DEBUG + if (debug) + printf( "peer %s event '%s' (0x%02x) status '%s' (0x%02x)\n", + src, eventstr(err), err, + peerstatstr(ctlpeerstatus(peer)), + ctlpeerstatus(peer)); +#endif + } else { + msyslog(LOG_ERR, + "report_event: err '%s' (0x%02x), no peer", + eventstr(err), err); +#ifdef DEBUG + printf( + "report_event: peer event '%s' (0x%02x), no peer\n", + eventstr(err), err); +#endif + return; + } + + /* + * If no trappers, return. + */ + if (num_ctl_traps <= 0) + return; + + /* + * Set up the outgoing packet variables + */ + res_opcode = CTL_OP_ASYNCMSG; + res_offset = 0; + res_async = 1; + res_authenticate = 0; + datapt = rpkt.data; + dataend = &(rpkt.data[CTL_MAX_DATA_LEN]); + if (!(err & PEER_EVENT)) { + rpkt.associd = 0; + rpkt.status = htons(ctlsysstatus()); + + /* + * For now, put everything we know about system + * variables. Don't send crypto strings. + */ + for (i = 1; i <= CS_MAXCODE; i++) { +#ifdef OPENSSL + if (i > CS_VARLIST) + continue; +#endif /* OPENSSL */ + ctl_putsys(i); + } +#ifdef REFCLOCK + /* + * for clock exception events: add clock variables to + * reflect info on exception + */ + if (err == EVNT_CLOCKEXCPT) { + struct refclockstat clock_stat; + struct ctl_var *kv; + + clock_stat.kv_list = (struct ctl_var *)0; + refclock_control(&peer->srcadr, + (struct refclockstat *)0, &clock_stat); + ctl_puthex("refclockstatus", + ctlclkstatus(&clock_stat)); + for (i = 1; i <= CC_MAXCODE; i++) + ctl_putclock(i, &clock_stat, 0); + for (kv = clock_stat.kv_list; kv && + !(kv->flags & EOV); kv++) + if (kv->flags & DEF) + ctl_putdata(kv->text, + strlen(kv->text), 0); + free_varlist(clock_stat.kv_list); + } +#endif /* REFCLOCK */ + } else { + rpkt.associd = htons(peer->associd); + rpkt.status = htons(ctlpeerstatus(peer)); + + /* + * Dump it all. Later, maybe less. + */ + for (i = 1; i <= CP_MAXCODE; i++) { +#ifdef OPENSSL + if (i > CP_VARLIST) + continue; +#endif /* OPENSSL */ + ctl_putpeer(i, peer); + } +#ifdef REFCLOCK + /* + * for clock exception events: add clock variables to + * reflect info on exception + */ + if (err == EVNT_PEERCLOCK) { + struct refclockstat clock_stat; + struct ctl_var *kv; + + clock_stat.kv_list = (struct ctl_var *)0; + refclock_control(&peer->srcadr, + (struct refclockstat *)0, &clock_stat); + + ctl_puthex("refclockstatus", + ctlclkstatus(&clock_stat)); + + for (i = 1; i <= CC_MAXCODE; i++) + ctl_putclock(i, &clock_stat, 0); + for (kv = clock_stat.kv_list; kv && + !(kv->flags & EOV); kv++) + if (kv->flags & DEF) + ctl_putdata(kv->text, + strlen(kv->text), 0); + free_varlist(clock_stat.kv_list); + } +#endif /* REFCLOCK */ + } + + /* + * We're done, return. + */ + ctl_flushpkt(0); +} + + +/* + * ctl_clr_stats - clear stat counters + */ +void +ctl_clr_stats(void) +{ + ctltimereset = current_time; + numctlreq = 0; + numctlbadpkts = 0; + numctlresponses = 0; + numctlfrags = 0; + numctlerrors = 0; + numctlfrags = 0; + numctltooshort = 0; + numctlinputresp = 0; + numctlinputfrag = 0; + numctlinputerr = 0; + numctlbadoffset = 0; + numctlbadversion = 0; + numctldatatooshort = 0; + numctlbadop = 0; + numasyncmsgs = 0; +} + +static u_long +count_var( + struct ctl_var *k + ) +{ + register u_long c; + + if (!k) + return (0); + + c = 0; + while (!(k++->flags & EOV)) + c++; + return (c); +} + +char * +add_var( + struct ctl_var **kv, + u_long size, + u_short def + ) +{ + register u_long c; + register struct ctl_var *k; + + c = count_var(*kv); + + k = *kv; + *kv = (struct ctl_var *)emalloc((c+2)*sizeof(struct ctl_var)); + if (k) { + memmove((char *)*kv, (char *)k, + sizeof(struct ctl_var)*c); + free((char *)k); + } + (*kv)[c].code = (u_short) c; + (*kv)[c].text = (char *)emalloc(size); + (*kv)[c].flags = def; + (*kv)[c+1].code = 0; + (*kv)[c+1].text = (char *)0; + (*kv)[c+1].flags = EOV; + return (char *)(*kv)[c].text; +} + +void +set_var( + struct ctl_var **kv, + const char *data, + u_long size, + u_short def + ) +{ + register struct ctl_var *k; + register const char *s; + register const char *t; + char *td; + + if (!data || !size) + return; + + k = *kv; + if (k != NULL) { + while (!(k->flags & EOV)) { + s = data; + t = k->text; + if (t) { + while (*t != '=' && *s - *t == 0) { + s++; + t++; + } + if (*s == *t && ((*t == '=') || !*t)) { + free((void *)k->text); + td = (char *)emalloc(size); + memmove(td, data, size); + k->text =td; + k->flags = def; + return; + } + } else { + td = (char *)emalloc(size); + memmove(td, data, size); + k->text = td; + k->flags = def; + return; + } + k++; + } + } + td = add_var(kv, size, def); + memmove(td, data, size); +} + +void +set_sys_var( + char *data, + u_long size, + u_short def + ) +{ + set_var(&ext_sys_var, data, size, def); +} + +void +free_varlist( + struct ctl_var *kv + ) +{ + struct ctl_var *k; + if (kv) { + for (k = kv; !(k->flags & EOV); k++) + free((void *)k->text); + free((void *)kv); + } +} diff --git a/ntpd/ntp_crypto.c b/ntpd/ntp_crypto.c new file mode 100644 index 0000000..3e67703 --- /dev/null +++ b/ntpd/ntp_crypto.c @@ -0,0 +1,4031 @@ +/* + * ntp_crypto.c - NTP version 4 public key routines + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef OPENSSL +#include <stdio.h> +#include <sys/types.h> +#include <sys/param.h> +#include <unistd.h> +#include <fcntl.h> + +#include "ntpd.h" +#include "ntp_stdlib.h" +#include "ntp_unixtime.h" +#include "ntp_string.h" + +#include "openssl/asn1_mac.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "openssl/evp.h" +#include "openssl/pem.h" +#include "openssl/rand.h" +#include "openssl/x509v3.h" + +#ifdef KERNEL_PLL +#include "ntp_syscall.h" +#endif /* KERNEL_PLL */ + +/* + * Extension field message format + * + * These are always signed and saved before sending in network byte + * order. They must be converted to and from host byte order for + * processing. + * + * +-------+-------+ + * | op | len | <- extension pointer + * +-------+-------+ + * | assocID | + * +---------------+ + * | timestamp | <- value pointer + * +---------------+ + * | filestamp | + * +---------------+ + * | value len | + * +---------------+ + * | | + * = value = + * | | + * +---------------+ + * | signature len | + * +---------------+ + * | | + * = signature = + * | | + * +---------------+ + * + * The CRYPTO_RESP bit is set to 0 for requests, 1 for responses. + * Requests carry the association ID of the receiver; responses carry + * the association ID of the sender. Some messages include only the + * operation/length and association ID words and so have length 8 + * octets. Ohers include the value structure and associated value and + * signature fields. These messages include the timestamp, filestamp, + * value and signature words and so have length at least 24 octets. The + * signature and/or value fields can be empty, in which case the + * respective length words are zero. An empty value with nonempty + * signature is syntactically valid, but semantically questionable. + * + * The filestamp represents the time when a cryptographic data file such + * as a public/private key pair is created. It follows every reference + * depending on that file and serves as a means to obsolete earlier data + * of the same type. The timestamp represents the time when the + * cryptographic data of the message were last signed. Creation of a + * cryptographic data file or signing a message can occur only when the + * creator or signor is synchronized to an authoritative source and + * proventicated to a trusted authority. + * + * Note there are four conditions required for server trust. First, the + * public key on the certificate must be verified, which involves a + * number of format, content and consistency checks. Next, the server + * identity must be confirmed by one of four schemes: private + * certificate, IFF scheme, GQ scheme or certificate trail hike to a + * self signed trusted certificate. Finally, the server signature must + * be verified. + */ +/* + * Cryptodefines + */ +#define TAI_1972 10 /* initial TAI offset (s) */ +#define MAX_LEAP 100 /* max UTC leapseconds (s) */ +#define VALUE_LEN (6 * 4) /* min response field length */ +#define YEAR (60 * 60 * 24 * 365) /* seconds in year */ + +/* + * Global cryptodata in host byte order + */ +u_int32 crypto_flags = 0x0; /* status word */ +u_int sys_tai; /* current UTC offset from TAI */ + +/* + * Global cryptodata in network byte order + */ +struct cert_info *cinfo = NULL; /* certificate info/value */ +struct value hostval; /* host value */ +struct value pubkey; /* public key */ +struct value tai_leap; /* leapseconds table */ + +/* + * Private cryptodata in host byte order + */ +static char *passwd = NULL; /* private key password */ +static EVP_PKEY *host_pkey = NULL; /* host key */ +static EVP_PKEY *sign_pkey = NULL; /* sign key */ +static EVP_PKEY *iffpar_pkey = NULL; /* IFF parameters */ +static EVP_PKEY *gqpar_pkey = NULL; /* GQ parameters */ +static EVP_PKEY *mvpar_pkey = NULL; /* MV parameters */ +static const EVP_MD *sign_digest = NULL; /* sign digest */ +static u_int sign_siglen; /* sign key length */ +static char *rand_file = NULL; /* random seed file */ +static char *host_file = NULL; /* host key file */ +static char *sign_file = NULL; /* sign key file */ +static char *iffpar_file = NULL; /* IFF parameters file */ +static char *gqpar_file = NULL; /* GQ parameters file */ +static char *mvpar_file = NULL; /* MV parameters file */ +static char *cert_file = NULL; /* certificate file */ +static char *leap_file = NULL; /* leapseconds file */ +static tstamp_t if_fstamp = 0; /* IFF file stamp */ +static tstamp_t gq_fstamp = 0; /* GQ file stamp */ +static tstamp_t mv_fstamp = 0; /* MV file stamp */ + +/* + * Cryptotypes + */ +static int crypto_verify P((struct exten *, struct value *, + struct peer *)); +static int crypto_encrypt P((struct exten *, struct value *, + keyid_t *)); +static int crypto_alice P((struct peer *, struct value *)); +static int crypto_alice2 P((struct peer *, struct value *)); +static int crypto_alice3 P((struct peer *, struct value *)); +static int crypto_bob P((struct exten *, struct value *)); +static int crypto_bob2 P((struct exten *, struct value *)); +static int crypto_bob3 P((struct exten *, struct value *)); +static int crypto_iff P((struct exten *, struct peer *)); +static int crypto_gq P((struct exten *, struct peer *)); +static int crypto_mv P((struct exten *, struct peer *)); +static u_int crypto_send P((struct exten *, struct value *)); +static tstamp_t crypto_time P((void)); +static u_long asn2ntp P((ASN1_TIME *)); +static struct cert_info *cert_parse P((u_char *, u_int, tstamp_t)); +static int cert_sign P((struct exten *, struct value *)); +static int cert_valid P((struct cert_info *, EVP_PKEY *)); +static int cert_install P((struct exten *, struct peer *)); +static void cert_free P((struct cert_info *)); +static EVP_PKEY *crypto_key P((char *, tstamp_t *)); +static int bighash P((BIGNUM *, BIGNUM *)); +static struct cert_info *crypto_cert P((char *)); +static void crypto_tai P((char *)); + +#ifdef SYS_WINNT +int +readlink(char * link, char * file, int len) { + return (-1); +} +#endif + +/* + * session_key - generate session key + * + * This routine generates a session key from the source address, + * destination address, key ID and private value. The value of the + * session key is the MD5 hash of these values, while the next key ID is + * the first four octets of the hash. + * + * Returns the next key ID + */ +keyid_t +session_key( + struct sockaddr_storage *srcadr, /* source address */ + struct sockaddr_storage *dstadr, /* destination address */ + keyid_t keyno, /* key ID */ + keyid_t private, /* private value */ + u_long lifetime /* key lifetime */ + ) +{ + EVP_MD_CTX ctx; /* message digest context */ + u_char dgst[EVP_MAX_MD_SIZE]; /* message digest */ + keyid_t keyid; /* key identifer */ + u_int32 header[10]; /* data in network byte order */ + u_int hdlen, len; + + /* + * Generate the session key and key ID. If the lifetime is + * greater than zero, install the key and call it trusted. + */ + hdlen = 0; + switch(srcadr->ss_family) { + case AF_INET: + header[0] = ((struct sockaddr_in *)srcadr)->sin_addr.s_addr; + header[1] = ((struct sockaddr_in *)dstadr)->sin_addr.s_addr; + header[2] = htonl(keyno); + header[3] = htonl(private); + hdlen = 4 * sizeof(u_int32); + break; + case AF_INET6: + memcpy(&header[0], &GET_INADDR6(*srcadr), + sizeof(struct in6_addr)); + memcpy(&header[4], &GET_INADDR6(*dstadr), + sizeof(struct in6_addr)); + header[8] = htonl(keyno); + header[9] = htonl(private); + hdlen = 10 * sizeof(u_int32); + break; + } + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, (u_char *)header, hdlen); + EVP_DigestFinal(&ctx, dgst, &len); + memcpy(&keyid, dgst, 4); + keyid = ntohl(keyid); + if (lifetime != 0) { + MD5auth_setkey(keyno, dgst, len); + authtrust(keyno, lifetime); + } +#ifdef DEBUG + if (debug > 1) + printf( + "session_key: %s > %s %08x %08x hash %08x life %lu\n", + stoa(srcadr), stoa(dstadr), keyno, + private, keyid, lifetime); +#endif + return (keyid); +} + + +/* + * make_keylist - generate key list + * + * This routine constructs a pseudo-random sequence by repeatedly + * hashing the session key starting from a given source address, + * destination address, private value and the next key ID of the + * preceeding session key. The last entry on the list is saved along + * with its sequence number and public signature. + */ +void +make_keylist( + struct peer *peer, /* peer structure pointer */ + struct interface *dstadr /* interface */ + ) +{ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + struct autokey *ap; /* autokey pointer */ + struct value *vp; /* value pointer */ + keyid_t keyid = 0; /* next key ID */ + keyid_t cookie; /* private value */ + u_long lifetime; + u_int len; + int i; + + /* + * Allocate the key list if necessary. + */ + tstamp = crypto_time(); + if (peer->keylist == NULL) + peer->keylist = emalloc(sizeof(keyid_t) * + NTP_MAXSESSION); + + /* + * Generate an initial key ID which is unique and greater than + * NTP_MAXKEY. + */ + while (1) { + keyid = (u_long)RANDOM & 0xffffffff; + if (keyid <= NTP_MAXKEY) + continue; + if (authhavekey(keyid)) + continue; + break; + } + + /* + * Generate up to NTP_MAXSESSION session keys. Stop if the + * next one would not be unique or not a session key ID or if + * it would expire before the next poll. The private value + * included in the hash is zero if broadcast mode, the peer + * cookie if client mode or the host cookie if symmetric modes. + */ + lifetime = min(sys_automax, (unsigned long) NTP_MAXSESSION * (1 <<(peer->kpoll))); + if (peer->hmode == MODE_BROADCAST) + cookie = 0; + else + cookie = peer->pcookie; + for (i = 0; i < NTP_MAXSESSION; i++) { + peer->keylist[i] = keyid; + peer->keynumber = i; + keyid = session_key(&dstadr->sin, &peer->srcadr, keyid, + cookie, lifetime); + lifetime -= 1 << peer->kpoll; + if (auth_havekey(keyid) || keyid <= NTP_MAXKEY || + lifetime <= (unsigned long)(1 << (peer->kpoll))) + break; + } + + /* + * Save the last session key ID, sequence number and timestamp, + * then sign these values for later retrieval by the clients. Be + * careful not to use invalid key media. Use the public values + * timestamp as filestamp. + */ + vp = &peer->sndval; + if (vp->ptr == NULL) + vp->ptr = emalloc(sizeof(struct autokey)); + ap = (struct autokey *)vp->ptr; + ap->seq = htonl(peer->keynumber); + ap->key = htonl(keyid); + vp->tstamp = htonl(tstamp); + vp->fstamp = hostval.tstamp; + vp->vallen = htonl(sizeof(struct autokey)); + vp->siglen = 0; + if (vp->tstamp != 0) { + if (vp->sig == NULL) + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)vp, 12); + EVP_SignUpdate(&ctx, vp->ptr, sizeof(struct autokey)); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + else + msyslog(LOG_ERR, "make_keys %s\n", + ERR_error_string(ERR_get_error(), NULL)); + peer->flags |= FLAG_ASSOC; + } +#ifdef DEBUG + if (debug) + printf("make_keys: %d %08x %08x ts %u fs %u poll %d\n", + ntohl(ap->seq), ntohl(ap->key), cookie, + ntohl(vp->tstamp), ntohl(vp->fstamp), peer->kpoll); +#endif +} + + +/* + * crypto_recv - parse extension fields + * + * This routine is called when the packet has been matched to an + * association and passed sanity, format and MAC checks. We believe the + * extension field values only if the field has proper format and + * length, the timestamp and filestamp are valid and the signature has + * valid length and is verified. There are a few cases where some values + * are believed even if the signature fails, but only if the proventic + * bit is not set. + */ +int +crypto_recv( + struct peer *peer, /* peer structure pointer */ + struct recvbuf *rbufp /* packet buffer pointer */ + ) +{ + const EVP_MD *dp; /* message digest algorithm */ + u_int32 *pkt; /* receive packet pointer */ + struct autokey *ap, *bp; /* autokey pointer */ + struct exten *ep, *fp; /* extension pointers */ + int has_mac; /* length of MAC field */ + int authlen; /* offset of MAC field */ + associd_t associd; /* association ID */ + tstamp_t tstamp = 0; /* timestamp */ + tstamp_t fstamp = 0; /* filestamp */ + u_int len; /* extension field length */ + u_int code; /* extension field opcode */ + u_int vallen = 0; /* value length */ + X509 *cert; /* X509 certificate */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + keyid_t cookie; /* crumbles */ + int rval = XEVNT_OK; + u_char *ptr; + u_int32 temp32; +#ifdef KERNEL_PLL +#if NTP_API > 3 + struct timex ntv; /* kernel interface structure */ +#endif /* NTP_API */ +#endif /* KERNEL_PLL */ + + /* + * Initialize. Note that the packet has already been checked for + * valid format and extension field lengths. First extract the + * field length, command code and association ID in host byte + * order. These are used with all commands and modes. Then check + * the version number, which must be 2, and length, which must + * be at least 8 for requests and VALUE_LEN (24) for responses. + * Packets that fail either test sink without a trace. The + * association ID is saved only if nonzero. + */ + authlen = LEN_PKT_NOMAC; + while ((has_mac = rbufp->recv_length - authlen) > MAX_MAC_LEN) { + pkt = (u_int32 *)&rbufp->recv_pkt + authlen / 4; + ep = (struct exten *)pkt; + code = ntohl(ep->opcode) & 0xffff0000; + len = ntohl(ep->opcode) & 0x0000ffff; + associd = (associd_t) ntohl(pkt[1]); + rval = XEVNT_OK; +#ifdef DEBUG + if (debug) + printf( + "crypto_recv: flags 0x%x ext offset %d len %u code %x assocID %d\n", + peer->crypto, authlen, len, code >> 16, + associd); +#endif + + /* + * Check version number and field length. If bad, + * quietly ignore the packet. + */ + if (((code >> 24) & 0x3f) != CRYPTO_VN || len < 8 || + (len < VALUE_LEN && (code & CRYPTO_RESP))) { + sys_unknownversion++; + code |= CRYPTO_ERROR; + } + + /* + * Little vulnerability bandage here. If a perp tosses a + * fake association ID over the fence, we better toss it + * out. Only the first one counts. + */ + if (code & CRYPTO_RESP) { + if (peer->assoc == 0) + peer->assoc = associd; + else if (peer->assoc != associd) + code |= CRYPTO_ERROR; + } + if (len >= VALUE_LEN) { + tstamp = ntohl(ep->tstamp); + fstamp = ntohl(ep->fstamp); + vallen = ntohl(ep->vallen); + } + switch (code) { + + /* + * Install status word, host name, signature scheme and + * association ID. In OpenSSL the signature algorithm is + * bound to the digest algorithm, so the NID completely + * defines the signature scheme. Note the request and + * response are identical, but neither is validated by + * signature. The request is processed here only in + * symmetric modes. The server name field would be + * useful to implement access controls in future. + */ + case CRYPTO_ASSOC: + + /* + * Pass the extension field to the transmit + * side. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + /* fall through */ + + case CRYPTO_ASSOC | CRYPTO_RESP: + + /* + * Discard the message if it has already been + * stored or the server is not synchronized. + */ + if (peer->crypto || !fstamp) + break; + + if (len < VALUE_LEN + vallen) { + rval = XEVNT_LEN; + break; + } + + /* + * Check the identity schemes are compatible. If + * the client has PC, the server must have PC, + * in which case the server public key and + * identity are presumed valid, so we skip the + * certificate and identity exchanges and move + * immediately to the cookie exchange which + * confirms the server signature. If the client + * has IFF or GC or both, the server must have + * the same one or both. Otherwise, the default + * TC scheme is used. + */ + if (crypto_flags & CRYPTO_FLAG_PRIV) { + if (!(fstamp & CRYPTO_FLAG_PRIV)) + rval = XEVNT_KEY; + else + fstamp |= CRYPTO_FLAG_VALID | + CRYPTO_FLAG_VRFY; + } else if (crypto_flags & CRYPTO_FLAG_MASK && + !(crypto_flags & fstamp & + CRYPTO_FLAG_MASK)) { + rval = XEVNT_KEY; + } + + /* + * Discard the message if identity error. + */ + if (rval != XEVNT_OK) + break; + + /* + * Discard the message if the host name length + * is unreasonable or the signature digest NID + * is not supported. + */ + temp32 = (fstamp >> 16) & 0xffff; + dp = + (const EVP_MD *)EVP_get_digestbynid(temp32); + if (vallen == 0 || vallen > MAXHOSTNAME) + rval = XEVNT_LEN; + else if (dp == NULL) + rval = XEVNT_MD; + if (rval != XEVNT_OK) + break; + + /* + * Save status word, host name and message + * digest/signature type. If PC identity, be + * sure not to sign the certificate. + */ + if (crypto_flags & CRYPTO_FLAG_PRIV) + fstamp |= CRYPTO_FLAG_SIGN; + peer->crypto = fstamp; + peer->digest = dp; + peer->subject = emalloc(vallen + 1); + memcpy(peer->subject, ep->pkt, vallen); + peer->subject[vallen] = '\0'; + peer->issuer = emalloc(vallen + 1); + strcpy(peer->issuer, peer->subject); + temp32 = (fstamp >> 16) & 0xffff; + sprintf(statstr, + "flags 0x%x host %s signature %s", fstamp, + peer->subject, OBJ_nid2ln(temp32)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Decode X509 certificate in ASN.1 format and extract + * the data containing, among other things, subject + * name and public key. In the default identification + * scheme, the certificate trail is followed to a self + * signed trusted certificate. + */ + case CRYPTO_CERT | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Scan the certificate list to delete old + * versions and link the newest version first on + * the list. + */ + if ((rval = cert_install(ep, peer)) != XEVNT_OK) + break; + + /* + * If we snatch the certificate before the + * server certificate has been signed by its + * server, it will be self signed. When it is, + * we chase the certificate issuer, which the + * server has, and keep going until a self + * signed trusted certificate is found. Be sure + * to update the issuer field, since it may + * change. + */ + if (peer->issuer != NULL) + free(peer->issuer); + peer->issuer = emalloc(strlen(cinfo->issuer) + + 1); + strcpy(peer->issuer, cinfo->issuer); + + /* + * We plug in the public key and group key in + * the first certificate received. However, note + * that this certificate might not be signed by + * the server, so we can't check the + * signature/digest NID. + */ + if (peer->pkey == NULL) { + ptr = (u_char *)cinfo->cert.ptr; + cert = d2i_X509(NULL, &ptr, + ntohl(cinfo->cert.vallen)); + peer->pkey = X509_get_pubkey(cert); + X509_free(cert); + } + peer->flash &= ~TEST10; + temp32 = cinfo->nid; + sprintf(statstr, "cert %s 0x%x %s (%u) fs %u", + cinfo->subject, cinfo->flags, + OBJ_nid2ln(temp32), temp32, + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Schnorr (IFF)identity scheme. This scheme is designed + * for use with shared secret group keys and where the + * certificate may be generated by a third party. The + * client sends a challenge to the server, which + * performs a calculation and returns the result. A + * positive result is possible only if both client and + * server contain the same secret group key. + */ + case CRYPTO_IFF | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. + */ + if ((rval = crypto_iff(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "iff fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Guillou-Quisquater (GQ) identity scheme. This scheme + * is designed for use with public certificates carrying + * the GQ public key in an extension field. The client + * sends a challenge to the server, which performs a + * calculation and returns the result. A positive result + * is possible only if both client and server contain + * the same group key and the server has the matching GQ + * private key. + */ + case CRYPTO_GQ | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. + */ + if ((rval = crypto_gq(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "gq fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * MV + */ + case CRYPTO_MV | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. + */ + if ((rval = crypto_mv(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "mv fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * X509 certificate sign response. Validate the + * certificate signed by the server and install. Later + * this can be provided to clients of this server in + * lieu of the self signed certificate in order to + * validate the public key. + */ + case CRYPTO_SIGN | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * not confirmed. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Scan the certificate list to delete old + * versions and link the newest version first on + * the list. + */ + if ((rval = cert_install(ep, peer)) != XEVNT_OK) break; + + peer->crypto |= CRYPTO_FLAG_SIGN; + peer->flash &= ~TEST10; + temp32 = cinfo->nid; + sprintf(statstr, "sign %s 0x%x %s (%u) fs %u", + cinfo->issuer, cinfo->flags, + OBJ_nid2ln(temp32), temp32, + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Cookie request in symmetric modes. Roll a random + * cookie and install in symmetric mode. Encrypt for the + * response, which is transmitted later. + */ + case CRYPTO_COOK: + + /* + * Discard the message if invalid or identity + * not confirmed. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Pass the extension field to the transmit + * side. If already agreed, walk away. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + if (peer->crypto & CRYPTO_FLAG_AGREE) { + peer->flash &= ~TEST10; + break; + } + + /* + * Install cookie values and light the cookie + * bit. The transmit side will pick up and + * encrypt it for the response. + */ + key_expire(peer); + peer->cookval.tstamp = ep->tstamp; + peer->cookval.fstamp = ep->fstamp; + RAND_bytes((u_char *)&peer->pcookie, 4); + peer->crypto &= ~CRYPTO_FLAG_AUTO; + peer->crypto |= CRYPTO_FLAG_AGREE; + peer->flash &= ~TEST10; + sprintf(statstr, "cook %x ts %u fs %u", + peer->pcookie, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Cookie response in client and symmetric modes. If the + * cookie bit is set, the working cookie is the EXOR of + * the current and new values. + */ + case CRYPTO_COOK | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the cookie values. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, &peer->cookval, + peer)) != XEVNT_OK) + break; + + /* + * Decrypt the cookie, hunting all the time for + * errors. + */ + if (vallen == (u_int) EVP_PKEY_size(host_pkey)) { + RSA_private_decrypt(vallen, + (u_char *)ep->pkt, + (u_char *)&temp32, + host_pkey->pkey.rsa, + RSA_PKCS1_OAEP_PADDING); + cookie = ntohl(temp32); + } else { + rval = XEVNT_CKY; + break; + } + + /* + * Install cookie values and light the cookie + * bit. If this is not broadcast client mode, we + * are done here. + */ + key_expire(peer); + peer->cookval.tstamp = ep->tstamp; + peer->cookval.fstamp = ep->fstamp; + if (peer->crypto & CRYPTO_FLAG_AGREE) + peer->pcookie ^= cookie; + else + peer->pcookie = cookie; + if (peer->hmode == MODE_CLIENT && + !(peer->cast_flags & MDF_BCLNT)) + peer->crypto |= CRYPTO_FLAG_AUTO; + else + peer->crypto &= ~CRYPTO_FLAG_AUTO; + peer->crypto |= CRYPTO_FLAG_AGREE; + peer->flash &= ~TEST10; + sprintf(statstr, "cook %x ts %u fs %u", + peer->pcookie, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Install autokey values in broadcast client and + * symmetric modes. We have to do this every time the + * sever/peer cookie changes or a new keylist is + * rolled. Ordinarily, this is automatic as this message + * is piggybacked on the first NTP packet sent upon + * either of these events. Note that a broadcast client + * or symmetric peer can receive this response without a + * matching request. + */ + case CRYPTO_AUTO | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the receive autokey values. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, &peer->recval, + peer)) != XEVNT_OK) + break; + + /* + * Install autokey values and light the + * autokey bit. This is not hard. + */ + if (peer->recval.ptr == NULL) + peer->recval.ptr = + emalloc(sizeof(struct autokey)); + bp = (struct autokey *)peer->recval.ptr; + peer->recval.tstamp = ep->tstamp; + peer->recval.fstamp = ep->fstamp; + ap = (struct autokey *)ep->pkt; + bp->seq = ntohl(ap->seq); + bp->key = ntohl(ap->key); + peer->pkeyid = bp->key; + peer->crypto |= CRYPTO_FLAG_AUTO; + peer->flash &= ~TEST10; + sprintf(statstr, + "auto seq %d key %x ts %u fs %u", bp->seq, + bp->key, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * Install leapseconds table in symmetric modes. This + * table is proventicated to the NIST primary servers, + * either by copying the file containing the table from + * a NIST server to a trusted server or directly using + * this protocol. While the entire table is installed at + * the server, presently only the current TAI offset is + * provided via the kernel to other applications. + */ + case CRYPTO_TAI: + + /* + * Discard the message if invalid or identity + * not confirmed. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Pass the extension field to the transmit + * side. Continue below if a leapseconds table + * accompanies the message. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + if (len <= VALUE_LEN) { + peer->flash &= ~TEST10; + break; + } + /* fall through */ + + case CRYPTO_TAI | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the leapsecond table values. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, &peer->tai_leap, + peer)) != XEVNT_OK) + break; + + /* + * Initialize peer variables, leapseconds + * structure and extension field in network byte + * order. Since a filestamp may have changed, + * recompute the signatures. + */ + peer->tai_leap.tstamp = ep->tstamp; + peer->tai_leap.fstamp = ep->fstamp; + peer->tai_leap.vallen = ep->vallen; + + /* + * Install the new table if there is no stored + * table or the new table is more recent than + * the stored table. Since a filestamp may have + * changed, recompute the signatures. + */ + if (ntohl(peer->tai_leap.fstamp) > + ntohl(tai_leap.fstamp)) { + tai_leap.fstamp = ep->fstamp; + tai_leap.vallen = ep->vallen; + if (tai_leap.ptr != NULL) + free(tai_leap.ptr); + tai_leap.ptr = emalloc(vallen); + memcpy(tai_leap.ptr, ep->pkt, vallen); + crypto_update(); + sys_tai = vallen / 4 + TAI_1972 - 1; + } + crypto_flags |= CRYPTO_FLAG_TAI; + peer->crypto |= CRYPTO_FLAG_LEAP; + peer->flash &= ~TEST10; +#ifdef KERNEL_PLL +#if NTP_API > 3 + /* + * If the kernel cooperates, initialize the + * current TAI offset. + */ + ntv.modes = MOD_TAI; + ntv.constant = sys_tai; + (void)ntp_adjtime(&ntv); +#endif /* NTP_API */ +#endif /* KERNEL_PLL */ + sprintf(statstr, "leap %u ts %u fs %u", + vallen, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + + /* + * We come here in symmetric modes for miscellaneous + * commands that have value fields but are processed on + * the transmit side. All we need do here is check for + * valid field length. Remaining checks are below and on + * the transmit side. + */ + case CRYPTO_IFF: + case CRYPTO_GQ: + case CRYPTO_MV: + case CRYPTO_SIGN: + if (len < VALUE_LEN) { + rval = XEVNT_LEN; + break; + } + + /* fall through */ + + /* + * We come here for miscellaneous requests and unknown + * requests and responses. If an unknown response or + * error, forget it. If a request, save the extension + * field for later. Unknown requests will be caught on + * the transmit side. + */ + default: + if (code & (CRYPTO_RESP | CRYPTO_ERROR)) { + rval = XEVNT_LEN; + } else if ((rval = crypto_verify(ep, NULL, + peer)) == XEVNT_OK) { + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + } + } + + /* + * We log everything except length/format errors and + * duplicates, which are log clogging vulnerabilities. + * The first error found terminates the extension field + * scan and we return the laundry to the caller. + */ + if (rval != XEVNT_OK) { + sprintf(statstr, + "error %x opcode %x ts %u fs %u", rval, + code, tstamp, fstamp); + if (rval > XEVNT_TSP) + record_crypto_stats(&peer->srcadr, + statstr); + report_event(rval, peer); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; + } + authlen += len; + } + return (rval); +} + + +/* + * crypto_xmit - construct extension fields + * + * This routine is called both when an association is configured and + * when one is not. The only case where this matters is to retrieve the + * autokey information, in which case the caller has to provide the + * association ID to match the association. + * + * Returns length of extension field. + */ +int +crypto_xmit( + struct pkt *xpkt, /* transmit packet pointer */ + struct sockaddr_storage *srcadr_sin, /* active runway */ + int start, /* offset to extension field */ + struct exten *ep, /* extension pointer */ + keyid_t cookie /* session cookie */ + ) +{ + u_int32 *pkt; /* packet pointer */ + struct peer *peer; /* peer structure pointer */ + u_int opcode; /* extension field opcode */ + struct exten *fp; /* extension pointers */ + struct cert_info *cp; /* certificate info/value pointer */ + char certname[MAXHOSTNAME + 1]; /* subject name buffer */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + u_int vallen; + u_int len; + struct value vtemp; + associd_t associd; + int rval; + keyid_t tcookie; + + /* + * Generate the requested extension field request code, length + * and association ID. If this is a response and the host is not + * synchronized, light the error bit and go home. + */ + pkt = (u_int32 *)xpkt + start / 4; + fp = (struct exten *)pkt; + opcode = ntohl(ep->opcode); + associd = (associd_t) ntohl(ep->associd); + fp->associd = htonl(associd); + len = 8; + rval = XEVNT_OK; + switch (opcode & 0xffff0000) { + + /* + * Send association request and response with status word and + * host name. Note, this message is not signed and the filestamp + * contains only the status word. We check at this point whether + * the identity schemes are compatible to save tears later on. + */ + case CRYPTO_ASSOC | CRYPTO_RESP: + case CRYPTO_ASSOC: + len += crypto_send(fp, &hostval); + if (crypto_time() == 0) + fp->fstamp = 0; + else + fp->fstamp = htonl(crypto_flags); + break; + + /* + * Send certificate request. Use the values from the extension + * field. + */ + case CRYPTO_CERT: + memset(&vtemp, 0, sizeof(vtemp)); + vtemp.tstamp = ep->tstamp; + vtemp.fstamp = ep->fstamp; + vtemp.vallen = ep->vallen; + vtemp.ptr = (unsigned char *)ep->pkt; + len += crypto_send(fp, &vtemp); + break; + + /* + * Send certificate response or sign request. Use the values + * from the certificate. If the request contains no subject + * name, assume the name of this host. This is for backwards + * compatibility. Light the error bit if no certificate with + * the given subject name is found. Of course, private + * certificates are never sent. + */ + case CRYPTO_SIGN: + case CRYPTO_CERT | CRYPTO_RESP: + vallen = ntohl(ep->vallen); + if (vallen == 8) { + strcpy(certname, sys_hostname); + } else if (vallen == 0 || vallen > MAXHOSTNAME) { + opcode |= CRYPTO_ERROR; + break; + + } else { + memcpy(certname, ep->pkt, vallen); + certname[vallen] = '\0'; + } + for (cp = cinfo; cp != NULL; cp = cp->link) { + if (cp->flags & CERT_PRIV) + continue; + if (strcmp(certname, cp->subject) == 0) { + len += crypto_send(fp, &cp->cert); + break; + } + } + if (cp == NULL) + opcode |= CRYPTO_ERROR; + break; + + /* + * Send challenge in Schnorr (IFF) identity scheme. + */ + case CRYPTO_IFF: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + if ((rval = crypto_alice(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send response in Schnorr (IFF) identity scheme. + */ + case CRYPTO_IFF | CRYPTO_RESP: + if ((rval = crypto_bob(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send challenge in Guillou-Quisquater (GQ) identity scheme. + */ + case CRYPTO_GQ: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + if ((rval = crypto_alice2(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send response in Guillou-Quisquater (GQ) identity scheme. + */ + case CRYPTO_GQ | CRYPTO_RESP: + if ((rval = crypto_bob2(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send challenge in MV identity scheme. + */ + case CRYPTO_MV: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + if ((rval = crypto_alice3(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send response in MV identity scheme. + */ + case CRYPTO_MV | CRYPTO_RESP: + if ((rval = crypto_bob3(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send certificate sign response. The integrity of the request + * certificate has already been verified on the receive side. + * Sign the response using the local server key. Use the + * filestamp from the request and use the timestamp as the + * current time. Light the error bit if the certificate is + * invalid or contains an unverified signature. + */ + case CRYPTO_SIGN | CRYPTO_RESP: + if ((rval = cert_sign(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send public key and signature. Use the values from the public + * key. + */ + case CRYPTO_COOK: + len += crypto_send(fp, &pubkey); + break; + + /* + * Encrypt and send cookie and signature. Light the error bit if + * anything goes wrong. + */ + case CRYPTO_COOK | CRYPTO_RESP: + if ((opcode & 0xffff) < VALUE_LEN) { + opcode |= CRYPTO_ERROR; + break; + } + if (PKT_MODE(xpkt->li_vn_mode) == MODE_SERVER) { + tcookie = cookie; + } else { + if ((peer = findpeerbyassoc(associd)) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + tcookie = peer->pcookie; + } + if ((rval = crypto_encrypt(ep, &vtemp, &tcookie)) == + XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Find peer and send autokey data and signature in broadcast + * server and symmetric modes. Use the values in the autokey + * structure. If no association is found, either the server has + * restarted with new associations or some perp has replayed an + * old message, in which case light the error bit. + */ + case CRYPTO_AUTO | CRYPTO_RESP: + if ((peer = findpeerbyassoc(associd)) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + peer->flags &= ~FLAG_ASSOC; + len += crypto_send(fp, &peer->sndval); + break; + + /* + * Send leapseconds table and signature. Use the values from the + * tai structure. If no table has been loaded, just send a + * request. + */ + case CRYPTO_TAI: + case CRYPTO_TAI | CRYPTO_RESP: + if (crypto_flags & CRYPTO_FLAG_TAI) + len += crypto_send(fp, &tai_leap); + break; + + /* + * Default - Fall through for requests; for unknown responses, + * flag as error. + */ + default: + if (opcode & CRYPTO_RESP) + opcode |= CRYPTO_ERROR; + } + + /* + * We ignore length/format errors and duplicates. Other errors + * are reported to the log and deny further service. To really + * persistent rascals we toss back a kiss-of-death grenade. + */ + if (rval > XEVNT_TSP) { + opcode |= CRYPTO_ERROR; + sprintf(statstr, "error %x opcode %x", rval, opcode); + record_crypto_stats(srcadr_sin, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_xmit: %s\n", statstr); +#endif + } + + /* + * Round up the field length to a multiple of 8 bytes and save + * the request code and length. + */ + len = ((len + 7) / 8) * 8; + fp->opcode = htonl((opcode & 0xffff0000) | len); +#ifdef DEBUG + if (debug) + printf( + "crypto_xmit: ext offset %d len %u code %x assocID %d\n", + start, len, opcode>> 16, associd); +#endif + return (len); +} + + +/* + * crypto_verify - parse and verify the extension field and value + * + * Returns + * XEVNT_OK success + * XEVNT_LEN bad field format or length + * XEVNT_TSP bad timestamp + * XEVNT_FSP bad filestamp + * XEVNT_PUB bad or missing public key + * XEVNT_SGL bad signature length + * XEVNT_SIG signature not verified + */ +static int +crypto_verify( + struct exten *ep, /* extension pointer */ + struct value *vp, /* value pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + EVP_PKEY *pkey; /* server public key */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* timestamp */ + tstamp_t fstamp; /* filestamp */ + u_int vallen; /* value length */ + u_int siglen; /* signature length */ + u_int opcode, len; + int rval; + int i; + + /* + * We require valid opcode and field length, timestamp, + * filestamp, public key, digest, signature length and + * signature, where relevant. Note that preliminary length + * checks are done in the main loop. + */ + len = ntohl(ep->opcode) & 0x0000ffff; + opcode = ntohl(ep->opcode) & 0xffff0000; + + /* + * Check for valid operation code and protocol. The opcode must + * not have the error bit set. If a response, it must have a + * value header. If a request and does not contain a value + * header, no need for further checking. + */ + if (opcode & CRYPTO_ERROR) + return (XEVNT_LEN); + if (opcode & CRYPTO_RESP) { + if (len < VALUE_LEN) + return (XEVNT_LEN); + } else { + if (len < VALUE_LEN) + return (XEVNT_OK); + } + /* + * We have a value header. Check for valid field lengths. The + * field length must be long enough to contain the value header, + * value and signature. If a request and a previous request of + * the same type is pending, discard the previous request. If a + * request but no signature, there is no need for further + * checking. + */ + vallen = ntohl(ep->vallen); + if (len < ((VALUE_LEN + vallen + 3) / 4) * 4) + return (XEVNT_LEN); + + i = (vallen + 3) / 4; + siglen = ntohl(ep->pkt[i++]); + if (len < VALUE_LEN + vallen + siglen) + return (XEVNT_LEN); + + if (!(opcode & CRYPTO_RESP)) { + if (peer->cmmd != NULL) { + if ((opcode | CRYPTO_RESP) == + (ntohl(peer->cmmd->opcode) & 0xffff0000)) { + free(peer->cmmd); + peer->cmmd = NULL; + } else { + return (XEVNT_LEN); + } + } + if (siglen == 0) + return (XEVNT_OK); + } + + /* + * We have a signature. Check for valid timestamp and filestamp. + * The timestamp must not precede the filestamp. The timestamp + * and filestamp must not precede the corresponding values in + * the value structure. Once the autokey values have been + * installed, the timestamp must always be later than the + * corresponding value in the value structure. Duplicate + * timestamps are illegal once the cookie has been validated. + */ + rval = XEVNT_OK; + if (crypto_flags & peer->crypto & CRYPTO_FLAG_PRIV) + pkey = sign_pkey; + else + pkey = peer->pkey; + tstamp = ntohl(ep->tstamp); + fstamp = ntohl(ep->fstamp); + if (tstamp == 0 || tstamp < fstamp) { + rval = XEVNT_TSP; + } else if (vp != NULL && (tstamp < ntohl(vp->tstamp) || + (tstamp == ntohl(vp->tstamp) && (peer->crypto & + CRYPTO_FLAG_AUTO)))) { + rval = XEVNT_TSP; + } else if (vp != NULL && (tstamp < ntohl(vp->fstamp) || fstamp < + ntohl(vp->fstamp))) { + rval = XEVNT_FSP; + + /* + * If a public key and digest is present, and if valid key + * length, check for valid signature. Note that the first valid + * signature lights the proventic bit. + */ + } else if (pkey == NULL || peer->digest == NULL) { + /* fall through */ + } else if (siglen != (u_int) EVP_PKEY_size(pkey)) { + rval = XEVNT_SGL; + } else { + EVP_VerifyInit(&ctx, peer->digest); + EVP_VerifyUpdate(&ctx, (u_char *)&ep->tstamp, vallen + + 12); + if (EVP_VerifyFinal(&ctx, (u_char *)&ep->pkt[i], siglen, + pkey)) { + if (peer->crypto & CRYPTO_FLAG_VRFY) + peer->crypto |= CRYPTO_FLAG_PROV; + } else { + rval = XEVNT_SIG; + } + } +#ifdef DEBUG + if (debug > 1) + printf( + "crypto_recv: verify %x vallen %u siglen %u ts %u fs %u\n", + rval, vallen, siglen, tstamp, fstamp); +#endif + return (rval); +} + + +/* + * crypto_encrypt - construct encrypted cookie and signature from + * extension field and cookie + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_CKY bad or missing cookie + */ +static int +crypto_encrypt( + struct exten *ep, /* extension pointer */ + struct value *vp, /* value pointer */ + keyid_t *cookie /* server cookie */ + ) +{ + EVP_PKEY *pkey; /* public key */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + u_int32 temp32; + u_int len; + u_char *ptr; + + /* + * Extract the public key from the request. + */ + len = ntohl(ep->vallen); + ptr = (u_char *)ep->pkt; + pkey = d2i_PublicKey(EVP_PKEY_RSA, NULL, &ptr, len); + if (pkey == NULL) { + msyslog(LOG_ERR, "crypto_encrypt %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Encrypt the cookie, encode in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = hostval.tstamp; + len = EVP_PKEY_size(pkey); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + temp32 = htonl(*cookie); + if (!RSA_public_encrypt(4, (u_char *)&temp32, vp->ptr, + pkey->pkey.rsa, RSA_PKCS1_OAEP_PADDING)) { + msyslog(LOG_ERR, "crypto_encrypt %s\n", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return (XEVNT_CKY); + } + EVP_PKEY_free(pkey); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_ident - construct extension field for identity scheme + * + * This routine determines which identity scheme is in use and + * constructs an extension field for that scheme. + */ +u_int +crypto_ident( + struct peer *peer /* peer structure pointer */ + ) +{ + char filename[MAXFILENAME + 1]; + + /* + * If the server identity has already been verified, no further + * action is necessary. Otherwise, try to load the identity file + * of the certificate issuer. If the issuer file is not found, + * try the host file. If nothing found, declare a cryptobust. + * Note we can't get here unless the trusted certificate has + * been found and the CRYPTO_FLAG_VALID bit is set, so the + * certificate issuer is valid. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + return (0); + + if (peer->ident_pkey != NULL) + EVP_PKEY_free(peer->ident_pkey); + if (peer->crypto & CRYPTO_FLAG_GQ) { + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_GQ); + + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + sys_hostname); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_GQ); + } + if (peer->crypto & CRYPTO_FLAG_IFF) { + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_IFF); + + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + sys_hostname); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_IFF); + } + if (peer->crypto & CRYPTO_FLAG_MV) { + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_MV); + + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", + sys_hostname); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_MV); + } + + /* + * No compatible identity scheme is available. Use the default + * TC scheme. + */ + msyslog(LOG_INFO, + "crypto_ident: no compatible identity scheme found"); + return (0); +} + + +/* + * crypto_args - construct extension field from arguments + * + * This routine creates an extension field with current timestamps and + * specified opcode, association ID and optional string. Note that the + * extension field is created here, but freed after the crypto_xmit() + * call in the protocol module. + * + * Returns extension field pointer (no errors). + */ +struct exten * +crypto_args( + struct peer *peer, /* peer structure pointer */ + u_int opcode, /* operation code */ + char *str /* argument string */ + ) +{ + tstamp_t tstamp; /* NTP timestamp */ + struct exten *ep; /* extension field pointer */ + u_int len; /* extension field length */ + + tstamp = crypto_time(); + len = sizeof(struct exten); + if (str != NULL) + len += strlen(str); + ep = emalloc(len); + memset(ep, 0, len); + ep->opcode = htonl(opcode + len); + + /* + * If a response, send our ID; if a request, send the + * responder's ID. + */ + if (opcode & CRYPTO_RESP) + ep->associd = htonl(peer->associd); + else + ep->associd = htonl(peer->assoc); + ep->tstamp = htonl(tstamp); + ep->fstamp = hostval.tstamp; + ep->vallen = 0; + if (str != NULL) { + ep->vallen = htonl(strlen(str)); + memcpy((char *)ep->pkt, str, strlen(str)); + } else { + ep->pkt[0] = peer->associd; + } + return (ep); +} + + +/* + * crypto_send - construct extension field from value components + * + * Returns extension field length. Note: it is not polite to send a + * nonempty signature with zero timestamp or a nonzero timestamp with + * empty signature, but these rules are not enforced here. + */ +u_int +crypto_send( + struct exten *ep, /* extension field pointer */ + struct value *vp /* value pointer */ + ) +{ + u_int len, temp32; + int i; + + /* + * Copy data. If the data field is empty or zero length, encode + * an empty value with length zero. + */ + ep->tstamp = vp->tstamp; + ep->fstamp = vp->fstamp; + ep->vallen = vp->vallen; + len = 12; + temp32 = ntohl(vp->vallen); + if (temp32 > 0 && vp->ptr != NULL) + memcpy(ep->pkt, vp->ptr, temp32); + + /* + * Copy signature. If the signature field is empty or zero + * length, encode an empty signature with length zero. + */ + i = (temp32 + 3) / 4; + len += i * 4 + 4; + ep->pkt[i++] = vp->siglen; + temp32 = ntohl(vp->siglen); + if (temp32 > 0 && vp->sig != NULL) + memcpy(&ep->pkt[i], vp->sig, temp32); + len += temp32; + return (len); +} + + +/* + * crypto_update - compute new public value and sign extension fields + * + * This routine runs periodically, like once a day, and when something + * changes. It updates the timestamps on three value structures and one + * value structure list, then signs all the structures: + * + * hostval host name (not signed) + * pubkey public key + * cinfo certificate info/value list + * tai_leap leapseconds file + * + * Filestamps are proventicated data, so this routine is run only when + * the host has been synchronized to a proventicated source. Thus, the + * timestamp is proventicated, too, and can be used to deflect + * clogging attacks and even cook breakfast. + * + * Returns void (no errors) + */ +void +crypto_update(void) +{ + EVP_MD_CTX ctx; /* message digest context */ + struct cert_info *cp, *cpn, **zp; /* certificate info/value */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t tstamp; /* NTP timestamp */ + u_int len; + + if ((tstamp = crypto_time()) == 0) + return; + hostval.tstamp = htonl(tstamp); + + /* + * Sign public key and timestamps. The filestamp is derived from + * the host key file extension from wherever the file was + * generated. + */ + if (pubkey.vallen != 0) { + pubkey.tstamp = hostval.tstamp; + pubkey.siglen = 0; + if (pubkey.sig == NULL) + pubkey.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&pubkey, 12); + EVP_SignUpdate(&ctx, pubkey.ptr, ntohl(pubkey.vallen)); + if (EVP_SignFinal(&ctx, pubkey.sig, &len, sign_pkey)) + pubkey.siglen = htonl(len); + } + + /* + * Sign certificates and timestamps. The filestamp is derived + * from the certificate file extension from wherever the file + * was generated. At the same time expired certificates are + * expunged. + */ + zp = &cinfo; + for (cp = cinfo; cp != NULL; cp = cpn) { + cpn = cp->link; + if (tstamp > cp->last) { + *zp = cpn; + cert_free(cp); + } else { + cp->cert.tstamp = hostval.tstamp; + cp->cert.siglen = 0; + if (cp->cert.sig == NULL) + cp->cert.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&cp->cert, 12); + EVP_SignUpdate(&ctx, cp->cert.ptr, + ntohl(cp->cert.vallen)); + if (EVP_SignFinal(&ctx, cp->cert.sig, &len, + sign_pkey)) + cp->cert.siglen = htonl(len); + zp = &cp->link; + } + } + + /* + * Sign leapseconds table and timestamps. The filestamp is + * derived from the leapsecond file extension from wherever the + * file was generated. + */ + if (tai_leap.vallen != 0) { + tai_leap.tstamp = hostval.tstamp; + tai_leap.siglen = 0; + if (tai_leap.sig == NULL) + tai_leap.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&tai_leap, 12); + EVP_SignUpdate(&ctx, tai_leap.ptr, + ntohl(tai_leap.vallen)); + if (EVP_SignFinal(&ctx, tai_leap.sig, &len, sign_pkey)) + tai_leap.siglen = htonl(len); + } + sprintf(statstr, "update ts %u", ntohl(hostval.tstamp)); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_update: %s\n", statstr); +#endif +} + + +/* + * value_free - free value structure components. + * + * Returns void (no errors) + */ +void +value_free( + struct value *vp /* value structure */ + ) +{ + if (vp->ptr != NULL) + free(vp->ptr); + if (vp->sig != NULL) + free(vp->sig); + memset(vp, 0, sizeof(struct value)); +} + + +/* + * crypto_time - returns current NTP time in seconds. + */ +tstamp_t +crypto_time() +{ + l_fp tstamp; /* NTP time */ L_CLR(&tstamp); + + L_CLR(&tstamp); + if (sys_leap != LEAP_NOTINSYNC) + get_systime(&tstamp); + return (tstamp.l_ui); +} + + +/* + * asn2ntp - convert ASN1_TIME time structure to NTP time in seconds. + */ +u_long +asn2ntp ( + ASN1_TIME *asn1time /* pointer to ASN1_TIME structure */ + ) +{ + char *v; /* pointer to ASN1_TIME string */ + struct tm tm; /* used to convert to NTP time */ + + /* + * Extract time string YYMMDDHHMMSSZ from ASN1 time structure. + * Note that the YY, MM, DD fields start with one, the HH, MM, + * SS fiels start with zero and the Z character should be 'Z' + * for UTC. Also note that years less than 50 map to years + * greater than 100. Dontcha love ASN.1? Better than MIL-188. + */ + if (asn1time->length > 13) + return ((u_long)(~0)); /* We can't use -1 here. It's invalid */ + v = (char *)asn1time->data; + tm.tm_year = (v[0] - '0') * 10 + v[1] - '0'; + if (tm.tm_year < 50) + tm.tm_year += 100; + tm.tm_mon = (v[2] - '0') * 10 + v[3] - '0' - 1; + tm.tm_mday = (v[4] - '0') * 10 + v[5] - '0'; + tm.tm_hour = (v[6] - '0') * 10 + v[7] - '0'; + tm.tm_min = (v[8] - '0') * 10 + v[9] - '0'; + tm.tm_sec = (v[10] - '0') * 10 + v[11] - '0'; + tm.tm_wday = 0; + tm.tm_yday = 0; + tm.tm_isdst = 0; + return (timegm(&tm) + JAN_1970); +} + + +/* + * bigdig() - compute a BIGNUM MD5 hash of a BIGNUM number. + */ +static int +bighash( + BIGNUM *bn, /* BIGNUM * from */ + BIGNUM *bk /* BIGNUM * to */ + ) +{ + EVP_MD_CTX ctx; /* message digest context */ + u_char dgst[EVP_MAX_MD_SIZE]; /* message digest */ + u_char *ptr; /* a BIGNUM as binary string */ + u_int len; + + len = BN_num_bytes(bn); + ptr = emalloc(len); + BN_bn2bin(bn, ptr); + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, ptr, len); + EVP_DigestFinal(&ctx, dgst, &len); + BN_bin2bn(dgst, len, bk); + return (1); +} + + +/* + *********************************************************************** + * * + * The following routines implement the Schnorr (IFF) identity scheme * + * * + *********************************************************************** + * + * The Schnorr (IFF) identity scheme is intended for use when + * the ntp-genkeys program does not generate the certificates used in + * the protocol and the group key cannot be conveyed in the certificate + * itself. For this purpose, new generations of IFF values must be + * securely transmitted to all members of the group before use. The + * scheme is self contained and independent of new generations of host + * keys, sign keys and certificates. + * + * The IFF identity scheme is based on DSA cryptography and algorithms + * described in Stinson p. 285. The IFF values hide in a DSA cuckoo + * structure, but only the primes and generator are used. The p is a + * 512-bit prime, q a 160-bit prime that divides p - 1 and is a qth root + * of 1 mod p; that is, g^q = 1 mod p. The TA rolls primvate random + * group key b disguised as a DSA structure member, then computes public + * key g^(q - b). These values are shared only among group members and + * never revealed in messages. Alice challenges Bob to confirm identity + * using the protocol described below. + * + * How it works + * + * The scheme goes like this. Both Alice and Bob have the public primes + * p, q and generator g. The TA gives private key b to Bob and public + * key v = g^(q - a) mod p to Alice. + * + * Alice rolls new random challenge r and sends to Bob in the IFF + * request message. Bob rolls new random k, then computes y = k + b r + * mod q and x = g^k mod p and sends (y, hash(x)) to Alice in the + * response message. Besides making the response shorter, the hash makes + * it effectivey impossible for an intruder to solve for b by observing + * a number of these messages. + * + * Alice receives the response and computes g^y v^r mod p. After a bit + * of algebra, this simplifies to g^k. If the hash of this result + * matches hash(x), Alice knows that Bob has the group key b. The signed + * response binds this knowledge to Bob's private key and the public key + * previously received in his certificate. + * + * crypto_alice - construct Alice's challenge in IFF scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; + + /* + * The identity parameters must have correct format and content. + */ + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice: defective key"); + return (XEVNT_PUB); + } + + /* + * Roll new random r (0 < r < q). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. + */ + bctx = BN_CTX_new(); + len = BN_num_bytes(dsa->q); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r */ + BN_mod(peer->iffval, peer->iffval, dsa->q, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + DSA_SIG *sdsa; /* DSA signature context fake */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *bn, *bk, *r; + u_char *ptr; + u_int len; + + /* + * If the IFF parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_IFF)) { + msyslog(LOG_INFO, "crypto_bob: scheme unavailable"); + return (XEVNT_PUB); + } + dsa = iffpar_pkey->pkey.dsa; + + /* + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < q), computes y = k + b r mod q + * and x = g^k mod p, then sends (y, hash(x)) to Alice. + */ + bctx = BN_CTX_new(); bk = BN_new(); bn = BN_new(); + sdsa = DSA_SIG_new(); + BN_rand(bk, len * 8, -1, 1); /* k */ + BN_mod_mul(bn, dsa->priv_key, r, dsa->q, bctx); /* b r mod q */ + BN_add(bn, bn, bk); + BN_mod(bn, bn, dsa->q, bctx); /* k + b r mod q */ + sdsa->r = BN_dup(bn); + BN_mod_exp(bk, dsa->g, bk, dsa->p, bctx); /* g^k mod p */ + bighash(bk, bk); + sdsa->s = BN_dup(bk); + BN_CTX_free(bctx); + BN_free(r); BN_free(bn); BN_free(bk); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(if_fstamp); + len = i2d_DSA_SIG(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_SIG_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSA_SIG(sdsa, &ptr); + DSA_SIG_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_iff - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_iff( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + BN_CTX *bctx; /* BIGNUM context */ + DSA_SIG *sdsa; /* DSA parameters */ + BIGNUM *bn, *bk; + u_int len; + const u_char *ptr; + int temp; + + /* + * If the IFF parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_iff: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_iff: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_iff: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_iff: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the k + b r and g^k values from the response. + */ + bctx = BN_CTX_new(); bk = BN_new(); bn = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSA_SIG(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_iff %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Compute g^(k + b r) g^(q - b)r mod p. + */ + BN_mod_exp(bn, dsa->pub_key, peer->iffval, dsa->p, bctx); + BN_mod_exp(bk, dsa->g, sdsa->r, dsa->p, bctx); + BN_mod_mul(bn, bn, bk, dsa->p, bctx); + + /* + * Verify the hash of the result matches hash(x). + */ + bighash(bn, bn); + temp = BN_cmp(bn, sdsa->s); + BN_free(bn); BN_free(bk); BN_CTX_free(bctx); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_SIG_free(sdsa); + if (temp == 0) + return (XEVNT_OK); + else + return (XEVNT_ID); +} + + +/* + *********************************************************************** + * * + * The following routines implement the Guillou-Quisquater (GQ) * + * identity scheme * + * * + *********************************************************************** + * + * The Guillou-Quisquater (GQ) identity scheme is intended for use when + * the ntp-genkeys program generates the certificates used in the + * protocol and the group key can be conveyed in a certificate extension + * field. The scheme is self contained and independent of new + * generations of host keys, sign keys and certificates. + * + * The GQ identity scheme is based on RSA cryptography and algorithms + * described in Stinson p. 300 (with errors). The GQ values hide in a + * RSA cuckoo structure, but only the modulus is used. The 512-bit + * public modulus is n = p q, where p and q are secret large primes. The + * TA rolls random group key b disguised as a RSA structure member. + * Except for the public key, these values are shared only among group + * members and never revealed in messages. + * + * When rolling new certificates, Bob recomputes the private and + * public keys. The private key u is a random roll, while the public key + * is the inverse obscured by the group key v = (u^-1)^b. These values + * replace the private and public keys normally generated by the RSA + * scheme. Alice challenges Bob to confirm identity using the protocol + * described below. + * + * How it works + * + * The scheme goes like this. Both Alice and Bob have the same modulus n + * and some random b as the group key. These values are computed and + * distributed in advance via secret means, although only the group key + * b is truly secret. Each has a private random private key u and public + * key (u^-1)^b, although not necessarily the same ones. Bob and Alice + * can regenerate the key pair from time to time without affecting + * operations. The public key is conveyed on the certificate in an + * extension field; the private key is never revealed. + * + * Alice rolls new random challenge r and sends to Bob in the GQ + * request message. Bob rolls new random k, then computes y = k u^r mod + * n and x = k^b mod n and sends (y, hash(x)) to Alice in the response + * message. Besides making the response shorter, the hash makes it + * effectivey impossible for an intruder to solve for b by observing + * a number of these messages. + * + * Alice receives the response and computes y^b v^r mod n. After a bit + * of algebra, this simplifies to k^b. If the hash of this result + * matches hash(x), Alice knows that Bob has the group key b. The signed + * response binds this knowledge to Bob's private key and the public key + * previously received in his certificate. + * + * crypto_alice2 - construct Alice's challenge in GQ scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice2( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; + + /* + * The identity parameters must have correct format and content. + */ + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((rsa = peer->ident_pkey->pkey.rsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice2: defective key"); + return (XEVNT_PUB); + } + + /* + * Roll new random r (0 < r < n). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. + */ + bctx = BN_CTX_new(); + len = BN_num_bytes(rsa->n); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r mod n */ + BN_mod(peer->iffval, peer->iffval, rsa->n, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob2 - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob2( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + DSA_SIG *sdsa; /* DSA parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *r, *k, *g, *y; + u_char *ptr; + u_int len; + + /* + * If the GQ parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_GQ)) { + msyslog(LOG_INFO, "crypto_bob2: scheme unavailable"); + return (XEVNT_PUB); + } + rsa = gqpar_pkey->pkey.rsa; + + /* + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob2 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < n), computes y = k u^r mod n and + * x = k^b mod n, then sends (y, hash(x)) to Alice. + */ + bctx = BN_CTX_new(); k = BN_new(); g = BN_new(); y = BN_new(); + sdsa = DSA_SIG_new(); + BN_rand(k, len * 8, -1, 1); /* k */ + BN_mod(k, k, rsa->n, bctx); + BN_mod_exp(y, rsa->p, r, rsa->n, bctx); /* u^r mod n */ + BN_mod_mul(y, k, y, rsa->n, bctx); /* k u^r mod n */ + sdsa->r = BN_dup(y); + BN_mod_exp(g, k, rsa->e, rsa->n, bctx); /* k^b mod n */ + bighash(g, g); + sdsa->s = BN_dup(g); + BN_CTX_free(bctx); + BN_free(r); BN_free(k); BN_free(g); BN_free(y); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(gq_fstamp); + len = i2d_DSA_SIG(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob2 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_SIG_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSA_SIG(sdsa, &ptr); + DSA_SIG_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_gq - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_gq( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + BN_CTX *bctx; /* BIGNUM context */ + DSA_SIG *sdsa; /* RSA signature context fake */ + BIGNUM *y, *v; + const u_char *ptr; + u_int len; + int temp; + + /* + * If the GQ parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_gq: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_gq: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((rsa = peer->ident_pkey->pkey.rsa) == NULL) { + msyslog(LOG_INFO, "crypto_gq: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_gq: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the y = k u^r and hash(x = k^b) values from the + * response. + */ + bctx = BN_CTX_new(); y = BN_new(); v = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSA_SIG(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_gq %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Compute v^r y^b mod n. + */ + BN_mod_exp(v, peer->grpkey, peer->iffval, rsa->n, bctx); + /* v^r mod n */ + BN_mod_exp(y, sdsa->r, rsa->e, rsa->n, bctx); /* y^b mod n */ + BN_mod_mul(y, v, y, rsa->n, bctx); /* v^r y^b mod n */ + + /* + * Verify the hash of the result matches hash(x). + */ + bighash(y, y); + temp = BN_cmp(y, sdsa->s); + BN_CTX_free(bctx); BN_free(y); BN_free(v); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_SIG_free(sdsa); + if (temp == 0) + return (XEVNT_OK); + else + return (XEVNT_ID); +} + + +/* + *********************************************************************** + * * + * The following routines implement the Mu-Varadharajan (MV) identity * + * scheme * + * * + *********************************************************************** + */ +/* + * The Mu-Varadharajan (MV) cryptosystem was originally intended when + * servers broadcast messages to clients, but clients never send + * messages to servers. There is one encryption key for the server and a + * separate decryption key for each client. It operated something like a + * pay-per-view satellite broadcasting system where the session key is + * encrypted by the broadcaster and the decryption keys are held in a + * tamperproof set-top box. + * + * The MV parameters and private encryption key hide in a DSA cuckoo + * structure which uses the same parameters, but generated in a + * different way. The values are used in an encryption scheme similar to + * El Gamal cryptography and a polynomial formed from the expansion of + * product terms (x - x[j]), as described in Mu, Y., and V. + * Varadharajan: Robust and Secure Broadcasting, Proc. Indocrypt 2001, + * 223-231. The paper has significant errors and serious omissions. + * + * Let q be the product of n distinct primes s'[j] (j = 1...n), where + * each s'[j] has m significant bits. Let p be a prime p = 2 * q + 1, so + * that q and each s'[j] divide p - 1 and p has M = n * m + 1 + * significant bits. The elements x mod q of Zq with the elements 2 and + * the primes removed form a field Zq* valid for polynomial arithetic. + * Let g be a generator of Zp; that is, gcd(g, p - 1) = 1 and g^q = 1 + * mod p. We expect M to be in the 500-bit range and n relatively small, + * like 25, so the likelihood of a randomly generated element of x mod q + * of Zq colliding with a factor of p - 1 is very small and can be + * avoided. Associated with each s'[j] is an element s[j] such that s[j] + * s'[j] = s'[j] mod q. We find s[j] as the quotient (q + s'[j]) / + * s'[j]. These are the parameters of the scheme and they are expensive + * to compute. + * + * We set up an instance of the scheme as follows. A set of random + * values x[j] mod q (j = 1...n), are generated as the zeros of a + * polynomial of order n. The product terms (x - x[j]) are expanded to + * form coefficients a[i] mod q (i = 0...n) in powers of x. These are + * used as exponents of the generator g mod p to generate the private + * encryption key A. The pair (gbar, ghat) of public server keys and the + * pairs (xbar[j], xhat[j]) (j = 1...n) of private client keys are used + * to construct the decryption keys. The devil is in the details. + * + * The distinguishing characteristic of this scheme is the capability to + * revoke keys. Included in the calculation of E, gbar and ghat is the + * product s = prod(s'[j]) (j = 1...n) above. If the factor s'[j] is + * subsequently removed from the product and E, gbar and ghat + * recomputed, the jth client will no longer be able to compute E^-1 and + * thus unable to decrypt the block. + * + * How it works + * + * The scheme goes like this. Bob has the server values (p, A, q, gbar, + * ghat) and Alice the client values (p, xbar, xhat). + * + * Alice rolls new random challenge r (0 < r < p) and sends to Bob in + * the MV request message. Bob rolls new random k (0 < k < q), encrypts + * y = A^k mod p (a permutation) and sends (hash(y), gbar^k, ghat^k) to + * Alice. + * + * Alice receives the response and computes the decryption key (the + * inverse permutation) from previously obtained (xbar, xhat) and + * (gbar^k, ghat^k) in the message. She computes the inverse, which is + * unique by reasons explained in the ntp-keygen.c program sources. If + * the hash of this result matches hash(y), Alice knows that Bob has the + * group key b. The signed response binds this knowledge to Bob's + * private key and the public key previously received in his + * certificate. + * + * crypto_alice3 - construct Alice's challenge in MV scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice3( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* MV parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; + + /* + * The identity parameters must have correct format and content. + */ + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice3: defective key"); + return (XEVNT_PUB); + } + + /* + * Roll new random r (0 < r < q). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. + */ + bctx = BN_CTX_new(); + len = BN_num_bytes(dsa->p); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r */ + BN_mod(peer->iffval, peer->iffval, dsa->p, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob3 - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob3( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* MV parameters */ + DSA *sdsa; /* DSA signature context fake */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *r, *k, *u; + u_char *ptr; + u_int len; + + /* + * If the MV parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_MV)) { + msyslog(LOG_INFO, "crypto_bob3: scheme unavailable"); + return (XEVNT_PUB); + } + dsa = mvpar_pkey->pkey.dsa; + + /* + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob3 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < q), making sure it is not a + * factor of q. He then computes y = A^k r and sends (hash(y), + * gbar^k, ghat^k) to Alice. + */ + bctx = BN_CTX_new(); k = BN_new(); u = BN_new(); + sdsa = DSA_new(); + sdsa->p = BN_new(); sdsa->q = BN_new(); sdsa->g = BN_new(); + while (1) { + BN_rand(k, BN_num_bits(dsa->q), 0, 0); + BN_mod(k, k, dsa->q, bctx); + BN_gcd(u, k, dsa->q, bctx); + if (BN_is_one(u)) + break; + } + BN_mod_exp(u, dsa->g, k, dsa->p, bctx); /* A r */ + BN_mod_mul(u, u, r, dsa->p, bctx); + bighash(u, sdsa->p); + BN_mod_exp(sdsa->q, dsa->priv_key, k, dsa->p, bctx); /* gbar */ + BN_mod_exp(sdsa->g, dsa->pub_key, k, dsa->p, bctx); /* ghat */ + BN_CTX_free(bctx); BN_free(k); BN_free(r); BN_free(u); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(mv_fstamp); + len = i2d_DSAparams(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob3 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSAparams(sdsa, &ptr); + DSA_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_mv - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_mv( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + DSA *dsa; /* MV parameters */ + DSA *sdsa; /* DSA parameters */ + BN_CTX *bctx; /* BIGNUM context */ + BIGNUM *k, *u, *v; + u_int len; + const u_char *ptr; + int temp; + + /* + * If the MV parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_mv: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_mv: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_mv: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_mv: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the (hash(y), gbar, ghat) values from the response. + */ + bctx = BN_CTX_new(); k = BN_new(); u = BN_new(); v = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSAparams(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_mv %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Compute (gbar^xhat ghat^xbar)^-1 mod p. + */ + BN_mod_exp(u, sdsa->q, dsa->pub_key, dsa->p, bctx); + BN_mod_exp(v, sdsa->g, dsa->priv_key, dsa->p, bctx); + BN_mod_mul(u, u, v, dsa->p, bctx); + BN_mod_inverse(u, u, dsa->p, bctx); + BN_mod_mul(v, u, peer->iffval, dsa->p, bctx); + + /* + * The result should match the hash of r mod p. + */ + bighash(v, v); + temp = BN_cmp(v, sdsa->p); + BN_CTX_free(bctx); BN_free(k); BN_free(u); BN_free(v); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_free(sdsa); + if (temp == 0) + return (XEVNT_OK); + else + return (XEVNT_ID); +} + + +/* + *********************************************************************** + * * + * The following routines are used to manipulate certificates * + * * + *********************************************************************** + */ +/* + * cert_parse - parse x509 certificate and create info/value structures. + * + * The server certificate includes the version number, issuer name, + * subject name, public key and valid date interval. If the issuer name + * is the same as the subject name, the certificate is self signed and + * valid only if the server is configured as trustable. If the names are + * different, another issuer has signed the server certificate and + * vouched for it. In this case the server certificate is valid if + * verified by the issuer public key. + * + * Returns certificate info/value pointer if valid, NULL if not. + */ +struct cert_info * /* certificate information structure */ +cert_parse( + u_char *asn1cert, /* X509 certificate */ + u_int len, /* certificate length */ + tstamp_t fstamp /* filestamp */ + ) +{ + X509 *cert; /* X509 certificate */ + X509_EXTENSION *ext; /* X509v3 extension */ + struct cert_info *ret; /* certificate info/value */ + BIO *bp; + X509V3_EXT_METHOD *method; + char pathbuf[MAXFILENAME]; + u_char *uptr; + char *ptr; + int temp, cnt, i; + + /* + * Decode ASN.1 objects and construct certificate structure. + */ + uptr = asn1cert; + if ((cert = d2i_X509(NULL, &uptr, len)) == NULL) { + msyslog(LOG_ERR, "cert_parse %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } + + /* + * Extract version, subject name and public key. + */ + ret = emalloc(sizeof(struct cert_info)); + memset(ret, 0, sizeof(struct cert_info)); + if ((ret->pkey = X509_get_pubkey(cert)) == NULL) { + msyslog(LOG_ERR, "cert_parse %s\n", + ERR_error_string(ERR_get_error(), NULL)); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->version = X509_get_version(cert); + X509_NAME_oneline(X509_get_subject_name(cert), pathbuf, + MAXFILENAME - 1); + ptr = strstr(pathbuf, "CN="); + if (ptr == NULL) { + msyslog(LOG_INFO, "cert_parse: invalid subject %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->subject = emalloc(strlen(ptr) + 1); + strcpy(ret->subject, ptr + 3); + + /* + * Extract remaining objects. Note that the NTP serial number is + * the NTP seconds at the time of signing, but this might not be + * the case for other authority. We don't bother to check the + * objects at this time, since the real crunch can happen only + * when the time is valid but not yet certificated. + */ + ret->nid = OBJ_obj2nid(cert->cert_info->signature->algorithm); + ret->digest = (const EVP_MD *)EVP_get_digestbynid(ret->nid); + ret->serial = + (u_long)ASN1_INTEGER_get(X509_get_serialNumber(cert)); + X509_NAME_oneline(X509_get_issuer_name(cert), pathbuf, + MAXFILENAME); + if ((ptr = strstr(pathbuf, "CN=")) == NULL) { + msyslog(LOG_INFO, "cert_parse: invalid issuer %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->issuer = emalloc(strlen(ptr) + 1); + strcpy(ret->issuer, ptr + 3); + ret->first = asn2ntp(X509_get_notBefore(cert)); + ret->last = asn2ntp(X509_get_notAfter(cert)); + + /* + * Extract extension fields. These are ad hoc ripoffs of + * currently assigned functions and will certainly be changed + * before prime time. + */ + cnt = X509_get_ext_count(cert); + for (i = 0; i < cnt; i++) { + ext = X509_get_ext(cert, i); + method = X509V3_EXT_get(ext); + temp = OBJ_obj2nid(ext->object); + switch (temp) { + + /* + * If a key_usage field is present, we decode whether + * this is a trusted or private certificate. This is + * dorky; all we want is to compare NIDs, but OpenSSL + * insists on BIO text strings. + */ + case NID_ext_key_usage: + bp = BIO_new(BIO_s_mem()); + X509V3_EXT_print(bp, ext, 0, 0); + BIO_gets(bp, pathbuf, MAXFILENAME); + BIO_free(bp); +#if DEBUG + if (debug) + printf("cert_parse: %s: %s\n", + OBJ_nid2ln(temp), pathbuf); +#endif + if (strcmp(pathbuf, "Trust Root") == 0) + ret->flags |= CERT_TRUST; + else if (strcmp(pathbuf, "Private") == 0) + ret->flags |= CERT_PRIV; + break; + + /* + * If a NID_subject_key_identifier field is present, it + * contains the GQ public key. + */ + case NID_subject_key_identifier: + ret->grplen = ext->value->length - 2; + ret->grpkey = emalloc(ret->grplen); + memcpy(ret->grpkey, &ext->value->data[2], + ret->grplen); + break; + } + } + + /* + * If certificate is self signed, verify signature. + */ + if (strcmp(ret->subject, ret->issuer) == 0) { + if (!X509_verify(cert, ret->pkey)) { + msyslog(LOG_INFO, + "cert_parse: invalid signature not verified %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + } + + /* + * Verify certificate valid times. Note that certificates cannot + * be retroactive. + */ + if (ret->first > ret->last || ret->first < fstamp) { + msyslog(LOG_INFO, + "cert_parse: expired %s", + ret->subject); + cert_free(ret); + X509_free(cert); + return (NULL); + } + + /* + * Build the value structure to sign and send later. + */ + ret->cert.fstamp = htonl(fstamp); + ret->cert.vallen = htonl(len); + ret->cert.ptr = emalloc(len); + memcpy(ret->cert.ptr, asn1cert, len); +#ifdef DEBUG + if (debug > 1) + X509_print_fp(stdout, cert); +#endif + X509_free(cert); + return (ret); +} + + +/* + * cert_sign - sign x509 certificate and update value structure. + * + * The certificate request is a copy of the client certificate, which + * includes the version number, subject name and public key of the + * client. The resulting certificate includes these values plus the + * serial number, issuer name and validity interval of the server. The + * validity interval extends from the current time to the same time one + * year hence. For NTP purposes, it is convenient to use the NTP seconds + * of the current time as the serial number. + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_CRT bad or missing certificate + * XEVNT_VFY certificate not verified + */ +static int +cert_sign( + struct exten *ep, /* extension field pointer */ + struct value *vp /* value pointer */ + ) +{ + X509 *req; /* X509 certificate request */ + X509 *cert; /* X509 certificate */ + X509_EXTENSION *ext; /* certificate extension */ + ASN1_INTEGER *serial; /* serial number */ + X509_NAME *subj; /* distinguished (common) name */ + EVP_PKEY *pkey; /* public key */ + EVP_MD_CTX ctx; /* message digest context */ + tstamp_t tstamp; /* NTP timestamp */ + u_int len; + u_char *ptr; + int i, temp; + + /* + * Decode ASN.1 objects and construct certificate structure. + */ + tstamp = crypto_time(); + if (tstamp == 0) + return (XEVNT_TSP); + + ptr = (u_char *)ep->pkt; + if ((req = d2i_X509(NULL, &ptr, ntohl(ep->vallen))) == NULL) { + msyslog(LOG_ERR, "cert_sign %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_CRT); + } + /* + * Extract public key and check for errors. + */ + if ((pkey = X509_get_pubkey(req)) == NULL) { + msyslog(LOG_ERR, "cert_sign %s\n", + ERR_error_string(ERR_get_error(), NULL)); + X509_free(req); + return (XEVNT_PUB); + } + + /* + * Generate X509 certificate signed by this server. For this + * prupose the issuer name is the server name. Also copy any + * extensions that might be present. + */ + cert = X509_new(); + X509_set_version(cert, X509_get_version(req)); + serial = ASN1_INTEGER_new(); + ASN1_INTEGER_set(serial, tstamp); + X509_set_serialNumber(cert, serial); + X509_gmtime_adj(X509_get_notBefore(cert), 0L); + X509_gmtime_adj(X509_get_notAfter(cert), YEAR); + subj = X509_get_issuer_name(cert); + X509_NAME_add_entry_by_txt(subj, "commonName", MBSTRING_ASC, + (unsigned char *) sys_hostname, strlen(sys_hostname), -1, 0); + subj = X509_get_subject_name(req); + X509_set_subject_name(cert, subj); + X509_set_pubkey(cert, pkey); + ext = X509_get_ext(req, 0); + temp = X509_get_ext_count(req); + for (i = 0; i < temp; i++) { + ext = X509_get_ext(req, i); + X509_add_ext(cert, ext, -1); + } + X509_free(req); + + /* + * Sign and verify the certificate. + */ + X509_sign(cert, sign_pkey, sign_digest); + if (!X509_verify(cert, sign_pkey)) { + printf("cert_sign\n%s\n", + ERR_error_string(ERR_get_error(), NULL)); + X509_free(cert); + return (XEVNT_VFY); + } + len = i2d_X509(cert, NULL); + + /* + * Build and sign the value structure. We have to sign it here, + * since the response has to be returned right away. This is a + * clogging hazard. + */ + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = ep->fstamp; + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + ptr = vp->ptr; + i2d_X509(cert, &ptr); + vp->siglen = 0; + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)vp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); +#ifdef DEBUG + if (debug > 1) + X509_print_fp(stdout, cert); +#endif + X509_free(cert); + return (XEVNT_OK); +} + + +/* + * cert_valid - verify certificate with given public key + * + * This is pretty ugly, as the certificate has to be verified in the + * OpenSSL X509 structure, not in the DER format in the info/value + * structure. + * + * Returns + * XEVNT_OK success + * XEVNT_VFY certificate not verified + */ +int +cert_valid( + struct cert_info *cinf, /* certificate information structure */ + EVP_PKEY *pkey /* public key */ + ) +{ + X509 *cert; /* X509 certificate */ + u_char *ptr; + + if (cinf->flags & CERT_SIGN) + return (XEVNT_OK); + ptr = (u_char *)cinf->cert.ptr; + cert = d2i_X509(NULL, &ptr, ntohl(cinf->cert.vallen)); + if (!X509_verify(cert, pkey)) + return (XEVNT_VFY); + cinf->flags |= CERT_SIGN; + X509_free(cert); + return (XEVNT_OK); +} + + +/* + * cert - install certificate in certificate list + * + * This routine encodes an extension field into a certificate info/value + * structure. It searches the certificate list for duplicates and + * expunges whichever is older. It then searches the list for other + * certificates that might be verified by this latest one. Finally, it + * inserts this certificate first on the list. + * + * Returns + * XEVNT_OK success + * XEVNT_PER certificate expired + * XEVNT_CRT bad or missing certificate + */ +int +cert_install( + struct exten *ep, /* cert info/value */ + struct peer *peer /* peer structure */ + ) +{ + struct cert_info *cp, *xp, *yp, **zp; + int rval; + tstamp_t tstamp; + + /* + * Parse and validate the signed certificate. If valid, + * construct the info/value structure; otherwise, scamper home. + * Note this allows a certificate not-before time to be in the + * future, but not a not-after time to be in the past. + */ + if ((cp = cert_parse((u_char *)ep->pkt, ntohl(ep->vallen), + ntohl(ep->fstamp))) == NULL) + return (XEVNT_CRT); + + tstamp = crypto_time(); + if (tstamp > cp->last) { + cert_free(cp); + return (XEVNT_PER); + } + + /* + * Scan certificate list looking for another certificate with + * the same subject and issuer. If another is found with the + * same or older filestamp, unlink it and return the goodies to + * the heap. If another is found with a later filetsamp, discard + * the new one and leave the building. + */ + rval = XEVNT_OK; + yp = cp; + zp = &cinfo; + for (xp = cinfo; xp != NULL; xp = xp->link) { + if (strcmp(cp->subject, xp->subject) == 0 && + strcmp(cp->issuer, xp->issuer) == 0) { + if (ntohl(cp->cert.fstamp) <= + ntohl(xp->cert.fstamp)) { + *zp = xp->link;; + cert_free(xp); + } else { + cert_free(cp); + return (XEVNT_TSP); + } + break; + } + zp = &xp->link; + } + yp->link = cinfo; + cinfo = yp; + + /* + * Scan the certificate list to see if Y is signed by X. + */ + for (yp = cinfo; yp != NULL; yp = yp->link) { + for (xp = cinfo; xp != NULL; xp = xp->link) { + if (yp->flags & CERT_ERROR) + continue; + + /* + * If issuer Y matches subject X and signature Y + * is valid using public key X, then Y is valid. + */ + if (strcmp(yp->issuer, xp->subject) != 0) + continue; + + if (cert_valid(yp, xp->pkey) != XEVNT_OK) { + yp->flags |= CERT_ERROR; + continue; + } + xp->flags |= CERT_SIGN; + + /* + * If X is trusted, then Y is trusted. Note that + * we might stumble over a self signed + * certificate that is not trusted, at least + * temporarily. This can happen when a dude + * first comes up, but has not synchronized the + * clock and had its certificate signed by its + * server. In case of broken certificate trail, + * this might result in a loop that could + * persist until timeout. + */ + if (!(xp->flags & CERT_TRUST)) + continue; + + yp->flags |= CERT_TRUST; + + /* + * If subject Y matches the server subject name, + * then Y has completed the certificate trail. + * Save the group key and light the valid bit. + */ + if (strcmp(yp->subject, peer->subject) != 0) + continue; + + if (yp->grpkey != NULL) { + if (peer->grpkey != NULL) + BN_free(peer->grpkey); + peer->grpkey = BN_bin2bn(yp->grpkey, + yp->grplen, NULL); + } + peer->crypto |= CRYPTO_FLAG_VALID; + + /* + * If the server has an an identity scheme, + * fetch the identity credentials. If not, the + * identity is verified only by the trusted + * certificate. The next signature will set the + * server proventic. + */ + if (peer->crypto & (CRYPTO_FLAG_GQ | + CRYPTO_FLAG_IFF | CRYPTO_FLAG_MV)) + continue; + + peer->crypto |= CRYPTO_FLAG_VRFY; + } + } + + /* + * That was awesome. Now update the timestamps and signatures. + */ + crypto_update(); + return (rval); +} + + +/* + * cert_free - free certificate information structure + */ +void +cert_free( + struct cert_info *cinf /* certificate info/value structure */ + ) +{ + if (cinf->pkey != NULL) + EVP_PKEY_free(cinf->pkey); + if (cinf->subject != NULL) + free(cinf->subject); + if (cinf->issuer != NULL) + free(cinf->issuer); + if (cinf->grpkey != NULL) + free(cinf->grpkey); + value_free(&cinf->cert); + free(cinf); +} + + +/* + *********************************************************************** + * * + * The following routines are used only at initialization time * + * * + *********************************************************************** + */ +/* + * crypto_key - load cryptographic parameters and keys from files + * + * This routine loads a PEM-encoded public/private key pair and extracts + * the filestamp from the file name. + * + * Returns public key pointer if valid, NULL if not. Side effect updates + * the filestamp if valid. + */ +static EVP_PKEY * +crypto_key( + char *cp, /* file name */ + tstamp_t *fstamp /* filestamp */ + ) +{ + FILE *str; /* file handle */ + EVP_PKEY *pkey = NULL; /* public/private key */ + char filename[MAXFILENAME]; /* name of key file */ + char linkname[MAXFILENAME]; /* filestamp buffer) */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + char *ptr; + + /* + * Open the key file. If the first character of the file name is + * not '/', prepend the keys directory string. If something goes + * wrong, abandon ship. + */ + if (*cp == '/') + strcpy(filename, cp); + else + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); + str = fopen(filename, "r"); + if (str == NULL) + return (NULL); + + /* + * Read the filestamp, which is contained in the first line. + */ + if ((ptr = fgets(linkname, MAXFILENAME, str)) == NULL) { + msyslog(LOG_ERR, "crypto_key: no data %s\n", + filename); + return (NULL); + } + if ((ptr = strrchr(ptr, '.')) == NULL) { + msyslog(LOG_ERR, "crypto_key: no filestamp %s\n", + filename); + return (NULL); + } + if (sscanf(++ptr, "%u", fstamp) != 1) { + msyslog(LOG_ERR, "crypto_key: invalid timestamp %s\n", + filename); + return (NULL); + } + + /* + * Read and decrypt PEM-encoded private key. + */ + pkey = PEM_read_PrivateKey(str, NULL, NULL, passwd); + fclose(str); + if (pkey == NULL) { + msyslog(LOG_ERR, "crypto_key %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } + + /* + * Leave tracks in the cryptostats. + */ + if ((ptr = strrchr(linkname, '\n')) != NULL) + *ptr = '\0'; + sprintf(statstr, "%s mod %d", &linkname[2], + EVP_PKEY_size(pkey) * 8); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_key: %s\n", statstr); + if (debug > 1) { + if (EVP_MD_type(pkey) == EVP_PKEY_DSA) + DSA_print_fp(stdout, pkey->pkey.dsa, 0); + else + RSA_print_fp(stdout, pkey->pkey.rsa, 0); + } +#endif + return (pkey); +} + + +/* + * crypto_cert - load certificate from file + * + * This routine loads a X.509 RSA or DSA certificate from a file and + * constructs a info/cert value structure for this machine. The + * structure includes a filestamp extracted from the file name. Later + * the certificate can be sent to another machine by request. + * + * Returns certificate info/value pointer if valid, NULL if not. + */ +static struct cert_info * /* certificate information */ +crypto_cert( + char *cp /* file name */ + ) +{ + struct cert_info *ret; /* certificate information */ + FILE *str; /* file handle */ + char filename[MAXFILENAME]; /* name of certificate file */ + char linkname[MAXFILENAME]; /* filestamp buffer */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t fstamp; /* filestamp */ + long len; + char *ptr; + char *name, *header; + u_char *data; + + /* + * Open the certificate file. If the first character of the file + * name is not '/', prepend the keys directory string. If + * something goes wrong, abandon ship. + */ + if (*cp == '/') + strcpy(filename, cp); + else + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); + str = fopen(filename, "r"); + if (str == NULL) + return (NULL); + + /* + * Read the filestamp, which is contained in the first line. + */ + if ((ptr = fgets(linkname, MAXFILENAME, str)) == NULL) { + msyslog(LOG_ERR, "crypto_cert: no data %s\n", + filename); + return (NULL); + } + if ((ptr = strrchr(ptr, '.')) == NULL) { + msyslog(LOG_ERR, "crypto_cert: no filestamp %s\n", + filename); + return (NULL); + } + if (sscanf(++ptr, "%u", &fstamp) != 1) { + msyslog(LOG_ERR, "crypto_cert: invalid filestamp %s\n", + filename); + return (NULL); + } + + /* + * Read PEM-encoded certificate and install. + */ + if (!PEM_read(str, &name, &header, &data, &len)) { + msyslog(LOG_ERR, "crypto_cert %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } + free(header); + if (strcmp(name, "CERTIFICATE") !=0) { + msyslog(LOG_INFO, "crypto_cert: wrong PEM type %s", + name); + free(name); + free(data); + return (NULL); + } + free(name); + + /* + * Parse certificate and generate info/value structure. + */ + ret = cert_parse(data, len, fstamp); + free(data); + if (ret == NULL) + return (NULL); + if ((ptr = strrchr(linkname, '\n')) != NULL) + *ptr = '\0'; + sprintf(statstr, "%s 0x%x len %lu", &linkname[2], ret->flags, + len); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_cert: %s\n", statstr); +#endif + return (ret); +} + + +/* + * crypto_tai - load leapseconds table from file + * + * This routine loads the ERTS leapsecond file in NIST text format, + * converts to a value structure and extracts a filestamp from the file + * name. The data are used to establish the TAI offset from UTC, which + * is provided to the kernel if supported. Later the data can be sent to + * another machine on request. + */ +static void +crypto_tai( + char *cp /* file name */ + ) +{ + FILE *str; /* file handle */ + char buf[NTP_MAXSTRLEN]; /* file line buffer */ + u_int leapsec[MAX_LEAP]; /* NTP time at leaps */ + u_int offset; /* offset at leap (s) */ + char filename[MAXFILENAME]; /* name of leapseconds file */ + char linkname[MAXFILENAME]; /* file link (for filestamp) */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t fstamp; /* filestamp */ + u_int len; + char *ptr; + int rval, i; +#ifdef KERNEL_PLL +#if NTP_API > 3 + struct timex ntv; /* kernel interface structure */ +#endif /* NTP_API */ +#endif /* KERNEL_PLL */ + + /* + * Open the file and discard comment lines. If the first + * character of the file name is not '/', prepend the keys + * directory string. If the file is not found, not to worry; it + * can be retrieved over the net. But, if it is found with + * errors, we crash and burn. + */ + if (*cp == '/') + strcpy(filename, cp); + else + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); + if ((str = fopen(filename, "r")) == NULL) + return; + + /* + * Extract filestamp if present. + */ + rval = readlink(filename, linkname, MAXFILENAME - 1); + if (rval > 0) { + linkname[rval] = '\0'; + ptr = strrchr(linkname, '.'); + } else { + ptr = strrchr(filename, '.'); + } + if (ptr != NULL) + sscanf(++ptr, "%u", &fstamp); + else + fstamp = 0; + tai_leap.fstamp = htonl(fstamp); + + /* + * We are rather paranoid here, since an intruder might cause a + * coredump by infiltrating naughty values. Empty lines and + * comments are ignored. Other lines must begin with two + * integers followed by junk or comments. The first integer is + * the NTP seconds of leap insertion, the second is the offset + * of TAI relative to UTC after that insertion. The second word + * must equal the initial insertion of ten seconds on 1 January + * 1972 plus one second for each succeeding insertion. + */ + i = 0; + while (i < MAX_LEAP) { + ptr = fgets(buf, NTP_MAXSTRLEN - 1, str); + if (ptr == NULL) + break; + if (strlen(buf) < 1) + continue; + if (*buf == '#') + continue; + if (sscanf(buf, "%u %u", &leapsec[i], &offset) != 2) + continue; + if (i != (int)(offset - TAI_1972)) { + break; + } + i++; + } + fclose(str); + if (ptr != NULL) { + msyslog(LOG_INFO, + "crypto_tai: leapseconds file %s error %d", cp, + rval); + exit (-1); + } + + /* + * The extension field table entries consists of the NTP seconds + * of leap insertion in reverse order, so that the most recent + * insertion is the first entry in the table. + */ + len = i * 4; + tai_leap.vallen = htonl(len); + ptr = emalloc(len); + tai_leap.ptr = (unsigned char *) ptr; + for (; i >= 0; i--) { + *ptr++ = (char) htonl(leapsec[i]); + } + crypto_flags |= CRYPTO_FLAG_TAI; + sys_tai = len / 4 + TAI_1972 - 1; +#ifdef KERNEL_PLL +#if NTP_API > 3 + ntv.modes = MOD_TAI; + ntv.constant = sys_tai; + if (ntp_adjtime(&ntv) == TIME_ERROR) + msyslog(LOG_INFO, + "crypto_tai: kernel TAI update failed"); +#endif /* NTP_API */ +#endif /* KERNEL_PLL */ + sprintf(statstr, "%s link %d fs %u offset %u", cp, rval, fstamp, + ntohl(tai_leap.vallen) / 4 + TAI_1972 - 1); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_tai: %s\n", statstr); +#endif +} + + +/* + * crypto_setup - load keys, certificate and leapseconds table + * + * This routine loads the public/private host key and certificate. If + * available, it loads the public/private sign key, which defaults to + * the host key, and leapseconds table. The host key must be RSA, but + * the sign key can be either RSA or DSA. In either case, the public key + * on the certificate must agree with the sign key. + */ +void +crypto_setup(void) +{ + EVP_PKEY *pkey; /* private/public key pair */ + char filename[MAXFILENAME]; /* file name buffer */ + l_fp seed; /* crypto PRNG seed as NTP timestamp */ + tstamp_t fstamp; /* filestamp */ + tstamp_t sstamp; /* sign filestamp */ + u_int len, bytes; + u_char *ptr; + + /* + * Initialize structures. + */ + if (!crypto_flags) + return; + gethostname(filename, MAXFILENAME); + bytes = strlen(filename) + 1; + sys_hostname = emalloc(bytes); + memcpy(sys_hostname, filename, bytes); + if (passwd == NULL) + passwd = sys_hostname; + memset(&hostval, 0, sizeof(hostval)); + memset(&pubkey, 0, sizeof(pubkey)); + memset(&tai_leap, 0, sizeof(tai_leap)); + + /* + * Load required random seed file and seed the random number + * generator. Be default, it is found in the user home + * directory. The root home directory may be / or /root, + * depending on the system. Wiggle the contents a bit and write + * it back so the sequence does not repeat when we next restart. + */ + ERR_load_crypto_strings(); + if (rand_file == NULL) { + if ((RAND_file_name(filename, MAXFILENAME)) != NULL) { + rand_file = emalloc(strlen(filename) + 1); + strcpy(rand_file, filename); + } + } else if (*rand_file != '/') { + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, + rand_file); + free(rand_file); + rand_file = emalloc(strlen(filename) + 1); + strcpy(rand_file, filename); + } + if (rand_file == NULL) { + msyslog(LOG_ERR, + "crypto_setup: random seed file not specified"); + exit (-1); + } + if ((bytes = RAND_load_file(rand_file, -1)) == 0) { + msyslog(LOG_ERR, + "crypto_setup: random seed file %s not found\n", + rand_file); + exit (-1); + } + get_systime(&seed); + RAND_seed(&seed, sizeof(l_fp)); + RAND_write_file(rand_file); + OpenSSL_add_all_algorithms(); +#ifdef DEBUG + if (debug) + printf( + "crypto_setup: OpenSSL version %lx random seed file %s bytes read %d\n", + SSLeay(), rand_file, bytes); +#endif + + /* + * Load required host key from file "ntpkey_host_<hostname>". It + * also becomes the default sign key. + */ + if (host_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_host_%s", + sys_hostname); + host_file = emalloc(strlen(filename) + 1); + strcpy(host_file, filename); + } + pkey = crypto_key(host_file, &fstamp); + if (pkey == NULL) { + msyslog(LOG_ERR, + "crypto_setup: host key file %s not found or corrupt", + host_file); + exit (-1); + } + host_pkey = pkey; + sign_pkey = pkey; + sstamp = fstamp; + hostval.fstamp = htonl(fstamp); + if (EVP_MD_type(host_pkey) != EVP_PKEY_RSA) { + msyslog(LOG_ERR, + "crypto_setup: host key is not RSA key type"); + exit (-1); + } + hostval.vallen = htonl(strlen(sys_hostname)); + hostval.ptr = (unsigned char *) sys_hostname; + + /* + * Construct public key extension field for agreement scheme. + */ + len = i2d_PublicKey(host_pkey, NULL); + ptr = emalloc(len); + pubkey.ptr = ptr; + i2d_PublicKey(host_pkey, &ptr); + pubkey.vallen = htonl(len); + pubkey.fstamp = hostval.fstamp; + + /* + * Load optional sign key from file "ntpkey_sign_<hostname>". If + * loaded, it becomes the sign key. + */ + if (sign_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_sign_%s", + sys_hostname); + sign_file = emalloc(strlen(filename) + 1); + strcpy(sign_file, filename); + } + pkey = crypto_key(sign_file, &fstamp); + if (pkey != NULL) { + sign_pkey = pkey; + sstamp = fstamp; + } + sign_siglen = EVP_PKEY_size(sign_pkey); + + /* + * Load optional IFF parameters from file + * "ntpkey_iff_<hostname>". + */ + if (iffpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + sys_hostname); + iffpar_file = emalloc(strlen(filename) + 1); + strcpy(iffpar_file, filename); + } + iffpar_pkey = crypto_key(iffpar_file, &if_fstamp); + if (iffpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_IFF; + + /* + * Load optional GQ parameters from file "ntpkey_gq_<hostname>". + */ + if (gqpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + sys_hostname); + gqpar_file = emalloc(strlen(filename) + 1); + strcpy(gqpar_file, filename); + } + gqpar_pkey = crypto_key(gqpar_file, &gq_fstamp); + if (gqpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_GQ; + + /* + * Load optional MV parameters from file "ntpkey_mv_<hostname>". + */ + if (mvpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", + sys_hostname); + mvpar_file = emalloc(strlen(filename) + 1); + strcpy(mvpar_file, filename); + } + mvpar_pkey = crypto_key(mvpar_file, &mv_fstamp); + if (mvpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_MV; + + /* + * Load required certificate from file "ntpkey_cert_<hostname>". + */ + if (cert_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_cert_%s", + sys_hostname); + cert_file = emalloc(strlen(filename) + 1); + strcpy(cert_file, filename); + } + if ((cinfo = crypto_cert(cert_file)) == NULL) { + msyslog(LOG_ERR, + "certificate file %s not found or corrupt", + cert_file); + exit (-1); + } + + /* + * The subject name must be the same as the host name, unless + * the certificate is private, in which case it may have come + * from another host. + */ + if (!(cinfo->flags & CERT_PRIV) && strcmp(cinfo->subject, + sys_hostname) != 0) { + msyslog(LOG_ERR, + "crypto_setup: certificate %s not for this host", + cert_file); + cert_free(cinfo); + exit (-1); + } + + /* + * It the certificate is trusted, the subject must be the same + * as the issuer, in other words it must be self signed. + */ + if (cinfo->flags & CERT_PRIV && strcmp(cinfo->subject, + cinfo->issuer) != 0) { + if (cert_valid(cinfo, sign_pkey) != XEVNT_OK) { + msyslog(LOG_ERR, + "crypto_setup: certificate %s is trusted, but not self signed.", + cert_file); + cert_free(cinfo); + exit (-1); + } + } + sign_digest = cinfo->digest; + if (cinfo->flags & CERT_PRIV) + crypto_flags |= CRYPTO_FLAG_PRIV; + crypto_flags |= cinfo->nid << 16; + + /* + * Load optional leapseconds table from file "ntpkey_leap". If + * the file is missing or defective, the values can later be + * retrieved from a server. + */ + if (leap_file == NULL) + leap_file = "ntpkey_leap"; + crypto_tai(leap_file); +#ifdef DEBUG + if (debug) + printf( + "crypto_setup: flags 0x%x host %s signature %s\n", + crypto_flags, sys_hostname, OBJ_nid2ln(cinfo->nid)); +#endif +} + + +/* + * crypto_config - configure data from crypto configuration command. + */ +void +crypto_config( + int item, /* configuration item */ + char *cp /* file name */ + ) +{ + switch (item) { + + /* + * Set random seed file name. + */ + case CRYPTO_CONF_RAND: + rand_file = emalloc(strlen(cp) + 1); + strcpy(rand_file, cp); + break; + + /* + * Set private key password. + */ + case CRYPTO_CONF_PW: + passwd = emalloc(strlen(cp) + 1); + strcpy(passwd, cp); + break; + + /* + * Set host file name. + */ + case CRYPTO_CONF_PRIV: + host_file = emalloc(strlen(cp) + 1); + strcpy(host_file, cp); + break; + + /* + * Set sign key file name. + */ + case CRYPTO_CONF_SIGN: + sign_file = emalloc(strlen(cp) + 1); + strcpy(sign_file, cp); + break; + + /* + * Set iff parameters file name. + */ + case CRYPTO_CONF_IFFPAR: + iffpar_file = emalloc(strlen(cp) + 1); + strcpy(iffpar_file, cp); + break; + + /* + * Set gq parameters file name. + */ + case CRYPTO_CONF_GQPAR: + gqpar_file = emalloc(strlen(cp) + 1); + strcpy(gqpar_file, cp); + break; + + /* + * Set mv parameters file name. + */ + case CRYPTO_CONF_MVPAR: + mvpar_file = emalloc(strlen(cp) + 1); + strcpy(mvpar_file, cp); + break; + + /* + * Set certificate file name. + */ + case CRYPTO_CONF_CERT: + cert_file = emalloc(strlen(cp) + 1); + strcpy(cert_file, cp); + break; + + /* + * Set leapseconds file name. + */ + case CRYPTO_CONF_LEAP: + leap_file = emalloc(strlen(cp) + 1); + strcpy(leap_file, cp); + break; + } + crypto_flags |= CRYPTO_FLAG_ENAB; +} +# else +int ntp_crypto_bs_pubkey; +# endif /* OPENSSL */ diff --git a/ntpd/ntp_filegen.c b/ntpd/ntp_filegen.c new file mode 100644 index 0000000..59a1d91 --- /dev/null +++ b/ntpd/ntp_filegen.c @@ -0,0 +1,547 @@ +/* + * ntp_filegen.c,v 3.12 1994/01/25 19:06:11 kardel Exp + * + * implements file generations support for NTP + * logfiles and statistic files + * + * + * Copyright (C) 1992, 1996 by Rainer Pruy + * Friedrich-Alexander Universität Erlangen-Nürnberg, Germany + * + * This code may be modified and used freely + * provided credits remain intact. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_string.h" +#include "ntp_calendar.h" +#include "ntp_filegen.h" +#include "ntp_stdlib.h" + +/* + * NTP is intended to run long periods of time without restart. + * Thus log and statistic files generated by NTP will grow large. + * + * this set of routines provides a central interface + * to generating files using file generations + * + * the generation of a file is changed according to file generation type + */ + + +/* + * redefine this if your system dislikes filename suffixes like + * X.19910101 or X.1992W50 or .... + */ +#define SUFFIX_SEP '.' + +/* + * other constants + */ +#define FGEN_AGE_SECS (24*60*60) /* life time of FILEGEN_AGE in seconds */ + +static void filegen_open P((FILEGEN *, u_long)); +static int valid_fileref P((char *, char *)); +#ifdef UNUSED +static FILEGEN *filegen_unregister P((char *)); +#endif /* UNUSED */ + +/* + * open a file generation according to the current settings of gen + * will also provide a link to basename if requested to do so + */ + +static void +filegen_open( + FILEGEN *gen, + u_long newid + ) +{ + char *filename; + char *basename; + u_int len; + FILE *fp; + struct calendar cal; + + len = strlen(gen->prefix) + strlen(gen->basename) + 1; + basename = (char*)emalloc(len); + sprintf(basename, "%s%s", gen->prefix, gen->basename); + + switch(gen->type) { + default: + msyslog(LOG_ERR, "unsupported file generations type %d for \"%s\" - reverting to FILEGEN_NONE", + gen->type, basename); + gen->type = FILEGEN_NONE; + + /*FALLTHROUGH*/ + case FILEGEN_NONE: + filename = (char*)emalloc(len); + sprintf(filename,"%s", basename); + break; + + case FILEGEN_PID: + filename = (char*)emalloc(len + 1 + 1 + 10); + sprintf(filename,"%s%c#%ld", basename, SUFFIX_SEP, newid); + break; + + case FILEGEN_DAY: + /* You can argue here in favor of using MJD, but + * I would assume it to be easier for humans to interpret dates + * in a format they are used to in everyday life. + */ + caljulian(newid,&cal); + filename = (char*)emalloc(len + 1 + 4 + 2 + 2); + sprintf(filename, "%s%c%04d%02d%02d", + basename, SUFFIX_SEP, cal.year, cal.month, cal.monthday); + break; + + case FILEGEN_WEEK: + /* + * This is still a hack + * - the term week is not correlated to week as it is used + * normally - it just refers to a period of 7 days + * starting at Jan 1 - 'weeks' are counted starting from zero + */ + caljulian(newid,&cal); + filename = (char*)emalloc(len + 1 + 4 + 1 + 2); + sprintf(filename, "%s%c%04dw%02d", + basename, SUFFIX_SEP, cal.year, cal.yearday / 7); + break; + + case FILEGEN_MONTH: + caljulian(newid,&cal); + filename = (char*)emalloc(len + 1 + 4 + 2); + sprintf(filename, "%s%c%04d%02d", + basename, SUFFIX_SEP, cal.year, cal.month); + break; + + case FILEGEN_YEAR: + caljulian(newid,&cal); + filename = (char*)emalloc(len + 1 + 4); + sprintf(filename, "%s%c%04d", basename, SUFFIX_SEP, cal.year); + break; + + case FILEGEN_AGE: + filename = (char*)emalloc(len + 1 + 2 + 10); + sprintf(filename, "%s%ca%08ld", basename, SUFFIX_SEP, newid); + break; + } + + if (gen->type != FILEGEN_NONE) { + /* + * check for existence of a file with name 'basename' + * as we disallow such a file + * if FGEN_FLAG_LINK is set create a link + */ + struct stat stats; + /* + * try to resolve name collisions + */ + static u_long conflicts = 0; + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFREG) == S_IFREG) +#endif + if (stat(basename, &stats) == 0) { + /* Hm, file exists... */ + if (S_ISREG(stats.st_mode)) { + if (stats.st_nlink <= 1) { + /* + * Oh, it is not linked - try to save it + */ + char *savename = (char*)emalloc(len + 1 + 1 + 10 + 10); + sprintf(savename, "%s%c%dC%lu", + basename, + SUFFIX_SEP, + (int) getpid(), + (u_long)conflicts++); + if (rename(basename, savename) != 0) + msyslog(LOG_ERR," couldn't save %s: %m", basename); + free(savename); + } else { + /* + * there is at least a second link tpo this file + * just remove the conflicting one + */ + if ( +#if !defined(VMS) + unlink(basename) != 0 +#else + delete(basename) != 0 +#endif + ) + msyslog(LOG_ERR, "couldn't unlink %s: %m", basename); + } + } else { + /* + * Ehh? Not a regular file ?? strange !!!! + */ + msyslog(LOG_ERR, "expected regular file for %s (found mode 0%lo)", + basename, (unsigned long)stats.st_mode); + } + } else { + /* + * stat(..) failed, but it is absolutely correct for + * 'basename' not to exist + */ + if (errno != ENOENT) + msyslog(LOG_ERR,"stat(%s) failed: %m", basename); + } + } + + /* + * now, try to open new file generation... + */ + fp = fopen(filename, "a"); + +#ifdef DEBUG + if (debug > 3) + printf("opening filegen (type=%d/id=%lu) \"%s\"\n", + gen->type, (u_long)newid, filename); +#endif + + if (fp == NULL) { + /* open failed -- keep previous state + * + * If the file was open before keep the previous generation. + * This will cause output to end up in the 'wrong' file, + * but I think this is still better than losing output + * + * ignore errors due to missing directories + */ + + if (errno != ENOENT) + msyslog(LOG_ERR, "can't open %s: %m", filename); + } else { + if (gen->fp != NULL) { + fclose(gen->fp); + } + gen->fp = fp; + gen->id = newid; + + if (gen->flag & FGEN_FLAG_LINK) { + /* + * need to link file to basename + * have to use hardlink for now as I want to allow + * gen->basename spanning directory levels + * this would make it more complex to get the correct + * filename for symlink + * + * Ok, it would just mean taking the part following + * the last '/' in the name.... Should add it later.... + */ + + /* Windows NT does not support file links -Greg Schueman 1/18/97 */ + +#if defined SYS_WINNT || defined SYS_VXWORKS + SetLastError(0); /* On WinNT, don't support FGEN_FLAG_LINK */ +#elif defined(VMS) + errno = 0; /* On VMS, don't support FGEN_FLAG_LINK */ +#else /* not (VMS) / VXWORKS / WINNT ; DO THE LINK) */ + if (link(filename, basename) != 0) + if (errno != EEXIST) + msyslog(LOG_ERR, "can't link(%s, %s): %m", filename, basename); +#endif /* SYS_WINNT || VXWORKS */ + } /* flags & FGEN_FLAG_LINK */ + } /* else fp == NULL */ + + free(basename); + free(filename); + return; +} + +/* + * this function sets up gen->fp to point to the correct + * generation of the file for the time specified by 'now' + * + * 'now' usually is interpreted as second part of a l_fp as is in the cal... + * library routines + */ + +void +filegen_setup( + FILEGEN *gen, + u_long now + ) +{ + u_long new_gen = ~ (u_long) 0; + struct calendar cal; + + if (!(gen->flag & FGEN_FLAG_ENABLED)) { + if (gen->fp != NULL) + fclose(gen->fp); + return; + } + + switch (gen->type) { + case FILEGEN_NONE: + if (gen->fp != NULL) return; /* file already open */ + break; + + case FILEGEN_PID: + new_gen = getpid(); + break; + + case FILEGEN_DAY: + caljulian(now, &cal); + cal.hour = cal.minute = cal.second = 0; + new_gen = caltontp(&cal); + break; + + case FILEGEN_WEEK: + /* Would be nice to have a calweekstart() routine */ + /* so just use a hack ... */ + /* just round time to integral 7 day period for actual year */ + new_gen = now - (now - calyearstart(now)) % TIMES7(SECSPERDAY) + + 60; + /* + * just to be sure - + * the computation above would fail in the presence of leap seconds + * so at least carry the date to the next day (+60 (seconds)) + * and go back to the start of the day via calendar computations + */ + caljulian(new_gen, &cal); + cal.hour = cal.minute = cal.second = 0; + new_gen = caltontp(&cal); + break; + + case FILEGEN_MONTH: + caljulian(now, &cal); + cal.yearday = (u_short) (cal.yearday - cal.monthday + 1); + cal.monthday = 1; + cal.hour = cal.minute = cal.second = 0; + new_gen = caltontp(&cal); + break; + + case FILEGEN_YEAR: + new_gen = calyearstart(now); + break; + + case FILEGEN_AGE: + new_gen = current_time - (current_time % FGEN_AGE_SECS); + break; + } + /* + * try to open file if not yet open + * reopen new file generation file on change of generation id + */ + if (gen->fp == NULL || gen->id != new_gen) { + filegen_open(gen, new_gen); + } +} + + +/* + * change settings for filegen files + */ +void +filegen_config( + FILEGEN *gen, + char *basename, + u_int type, + u_int flag + ) +{ + /* + * if nothing would be changed... + */ + if ((basename == gen->basename || strcmp(basename,gen->basename) == 0) && + type == gen->type && + flag == gen->flag) + return; + + /* + * validate parameters + */ + if (!valid_fileref(gen->prefix,basename)) + return; + + if (gen->fp != NULL) + fclose(gen->fp); + +#ifdef DEBUG + if (debug > 2) + printf("configuring filegen:\n\tprefix:\t%s\n\tbasename:\t%s -> %s\n\ttype:\t%d -> %d\n\tflag: %x -> %x\n", + gen->prefix, gen->basename, basename, gen->type, type, gen->flag, flag); +#endif + if (gen->basename != basename || strcmp(gen->basename, basename) != 0) { + free(gen->basename); + gen->basename = (char*)emalloc(strlen(basename) + 1); + strcpy(gen->basename, basename); + } + gen->type = (u_char) type; + gen->flag = (u_char) flag; + + /* + * make filegen use the new settings + * special action is only required when a generation file + * is currently open + * otherwise the new settings will be used anyway at the next open + */ + if (gen->fp != NULL) { + l_fp now; + + get_systime(&now); + filegen_setup(gen, now.l_ui); + } +} + + +/* + * check whether concatenating prefix and basename + * yields a legal filename + */ +static int +valid_fileref( + char *prefix, + char *basename + ) +{ + /* + * prefix cannot be changed dynamically + * (within the context of filegen) + * so just reject basenames containing '..' + * + * ASSUMPTION: + * file system parts 'below' prefix may be + * specified without infringement of security + * + * restricing prefix to legal values + * has to be ensured by other means + * (however, it would be possible to perform some checks here...) + */ + register char *p = basename; + + /* + * Just to catch, dumb errors opening up the world... + */ + if (prefix == NULL || *prefix == '\0') + return 0; + + if (basename == NULL) + return 0; + + for (p = basename; p; p = strchr(p, '/')) { + if (*p == '.' && *(p+1) == '.' && (*(p+2) == '\0' || *(p+2) == '/')) + return 0; + } + + return 1; +} + + +/* + * filegen registry + */ + +static struct filegen_entry { + char *name; + FILEGEN *filegen; + struct filegen_entry *next; +} *filegen_registry = NULL; + + +FILEGEN * +filegen_get( + char *name + ) +{ + struct filegen_entry *f = filegen_registry; + + while(f) { + if (f->name == name || strcmp(name, f->name) == 0) { +#ifdef XXX /* this gives the Alpha compiler fits */ + if (debug > 3) + printf("filegen_get(\"%s\") = %x\n", name, + (u_int)f->filegen); +#endif + return f->filegen; + } + f = f->next; + } +#ifdef DEBUG + if (debug > 3) + printf("filegen_get(\"%s\") = NULL\n", name); +#endif + return NULL; +} + +void +filegen_register( + const char *name, + FILEGEN *filegen + ) +{ + struct filegen_entry **f = &filegen_registry; + +#ifdef XXX /* this gives the Alpha compiler fits */ + if (debug > 3) + printf("filegen_register(\"%s\",%x)\n", name, (u_int)filegen); +#endif + while (*f) { + if ((*f)->name == name || strcmp(name, (*f)->name) == 0) { +#ifdef XXX /* this gives the Alpha compiler fits */ + if (debug > 4) { + printf("replacing filegen %x\n", (u_int)(*f)->filegen); + } +#endif + (*f)->filegen = filegen; + return; + } + f = &((*f)->next); + } + + *f = (struct filegen_entry *) emalloc(sizeof(struct filegen_entry)); + if (*f) { + (*f)->next = NULL; + (*f)->name = (char*)emalloc(strlen(name) + 1); + strcpy((*f)->name, name); + (*f)->filegen = filegen; +#ifdef DEBUG + if (debug > 5) { + printf("adding new filegen\n"); + } +#endif + } + + return; +} + +#ifdef UNUSED +static FILEGEN * +filegen_unregister( + char *name + ) +{ + struct filegen_entry **f = &filegen_registry; + +#ifdef DEBUG + if (debug > 3) + printf("filegen_unregister(\"%s\")\n", name); +#endif + + while (*f) { + if (strcmp((*f)->name,name) == 0) { + struct filegen_entry *ff = *f; + FILEGEN *fg; + + *f = (*f)->next; + fg = ff->filegen; + free(ff->name); + free(ff); + return fg; + } + f = &((*f)->next); + } + return NULL; +} +#endif /* UNUSED */ diff --git a/ntpd/ntp_intres.c b/ntpd/ntp_intres.c new file mode 100644 index 0000000..7f27f21 --- /dev/null +++ b/ntpd/ntp_intres.c @@ -0,0 +1,1065 @@ +/* + * ripped off from ../ntpres/ntpres.c by Greg Troxel 4/2/92 + * routine callable from ntpd, rather than separate program + * also, key info passed in via a global, so no key file needed. + */ + +/* + * ntpres - process configuration entries which require use of the resolver + * + * This is meant to be run by ntpd on the fly. It is not guaranteed + * to work properly if run by hand. This is actually a quick hack to + * stave off violence from people who hate using numbers in the + * configuration file (at least I hope the rest of the daemon is + * better than this). Also might provide some ideas about how one + * might go about autoconfiguring an NTP distribution network. + * + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntp_machine.h" +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_request.h" +#include "ntp_stdlib.h" +#include "ntp_syslog.h" + +#include <stdio.h> +#include <ctype.h> +#include <netdb.h> +#include <signal.h> + +/**/ +#include <netinet/in.h> +#include <arpa/inet.h> +/**/ +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> /* MAXHOSTNAMELEN (often) */ +#endif + +#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) + +/* + * Each item we are to resolve and configure gets one of these + * structures defined for it. + */ +struct conf_entry { + struct conf_entry *ce_next; + char *ce_name; /* name we are trying to resolve */ + struct conf_peer ce_config; /* configuration info for peer */ + struct sockaddr_storage peer_store; /* address info for both fams */ +}; +#define ce_peeraddr ce_config.peeraddr +#define ce_peeraddr6 ce_config.peeraddr6 +#define ce_hmode ce_config.hmode +#define ce_version ce_config.version +#define ce_minpoll ce_config.minpoll +#define ce_maxpoll ce_config.maxpoll +#define ce_flags ce_config.flags +#define ce_ttl ce_config.ttl +#define ce_keyid ce_config.keyid +#define ce_keystr ce_config.keystr + +/* + * confentries is a pointer to the list of configuration entries + * we have left to do. + */ +static struct conf_entry *confentries = NULL; + +/* + * We take an interrupt every thirty seconds, at which time we decrement + * config_timer and resolve_timer. The former is set to 2, so we retry + * unsucessful reconfigurations every minute. The latter is set to + * an exponentially increasing value which starts at 2 and increases to + * 32. When this expires we retry failed name resolutions. + * + * We sleep SLEEPTIME seconds before doing anything, to give the server + * time to arrange itself. + */ +#define MINRESOLVE 2 +#define MAXRESOLVE 32 +#define CONFIG_TIME 2 +#define ALARM_TIME 30 +#define SLEEPTIME 2 + +static volatile int config_timer = 0; +static volatile int resolve_timer = 0; + +static int resolve_value; /* next value of resolve timer */ + +/* + * Big hack attack + */ +#define LOCALHOST 0x7f000001 /* 127.0.0.1, in hex, of course */ +#define SKEWTIME 0x08000000 /* 0.03125 seconds as a l_fp fraction */ + +/* + * Select time out. Set to 2 seconds. The server is on the local machine, + * after all. + */ +#define TIMEOUT_SEC 2 +#define TIMEOUT_USEC 0 + + +/* + * Input processing. The data on each line in the configuration file + * is supposed to consist of entries in the following order + */ +#define TOK_HOSTNAME 0 +#define TOK_HMODE 1 +#define TOK_VERSION 2 +#define TOK_MINPOLL 3 +#define TOK_MAXPOLL 4 +#define TOK_FLAGS 5 +#define TOK_TTL 6 +#define TOK_KEYID 7 +#define TOK_KEYSTR 8 +#define NUMTOK 9 + +#define MAXLINESIZE 512 + + +/* + * File descriptor for ntp request code. + */ +static SOCKET sockfd = INVALID_SOCKET; /* NT uses SOCKET */ + +/* stuff to be filled in by caller */ + +keyid_t req_keyid; /* request keyid */ +char *req_file; /* name of the file with configuration info */ + +/* end stuff to be filled in */ + + +static RETSIGTYPE bong P((int)); +static void checkparent P((void)); +static void removeentry P((struct conf_entry *)); +static void addentry P((char *, int, int, int, int, u_int, + int, keyid_t, char *)); +static int findhostaddr P((struct conf_entry *)); +static void openntp P((void)); +static int request P((struct conf_peer *)); +static char * nexttoken P((char **)); +static void readconf P((FILE *, char *)); +static void doconfigure P((int)); + +struct ntp_res_t_pkt { /* Tagged packet: */ + void *tag; /* For the caller */ + u_int32 paddr; /* IP to look up, or 0 */ + char name[MAXHOSTNAMELEN]; /* Name to look up (if 1st byte is not 0) */ +}; + +struct ntp_res_c_pkt { /* Control packet: */ + char name[MAXHOSTNAMELEN]; + u_int32 paddr; + int mode; + int version; + int minpoll; + int maxpoll; + u_int flags; + int ttl; + keyid_t keyid; + u_char keystr[MAXFILENAME]; +}; + + +/* + * ntp_res_recv: Process an answer from the resolver + */ + +void +ntp_res_recv(void) +{ + /* + We have data ready on our descriptor. + It may be an EOF, meaning the resolver process went away. + Otherwise, it will be an "answer". + */ +} + + +/* + * ntp_intres needs; + * + * req_key(???), req_keyid, req_file valid + * syslog still open + */ + +void +ntp_intres(void) +{ + FILE *in; +#ifdef HAVE_SIGSUSPEND + sigset_t set; + + sigemptyset(&set); +#endif /* HAVE_SIGSUSPEND */ + +#ifdef DEBUG + if (debug > 1) { + msyslog(LOG_INFO, "NTP_INTRES running"); + } +#endif + + /* check out auth stuff */ + if (sys_authenticate) { + if (!authistrusted(req_keyid)) { + msyslog(LOG_ERR, "invalid request keyid %08x", + req_keyid ); + exit(1); + } + } + + /* + * Read the configuration info + * {this is bogus, since we are forked, but it is easier + * to keep this code - gdt} + */ + if ((in = fopen(req_file, "r")) == NULL) { + msyslog(LOG_ERR, "can't open configuration file %s: %m", + req_file); + exit(1); + } + readconf(in, req_file); + (void) fclose(in); + + if (!debug ) + (void) unlink(req_file); + + /* + * Sleep a little to make sure the server is completely up + */ + + sleep(SLEEPTIME); + + /* + * Make a first cut at resolving the bunch + */ + doconfigure(1); + if (confentries == NULL) { +#if defined SYS_WINNT + ExitThread(0); /* Don't want to kill whole NT process */ +#else + exit(0); /* done that quick */ +#endif + } + + /* + * Here we've got some problem children. Set up the timer + * and wait for it. + */ + resolve_value = resolve_timer = MINRESOLVE; + config_timer = CONFIG_TIME; +#ifndef SYS_WINNT + (void) signal_no_reset(SIGALRM, bong); + alarm(ALARM_TIME); +#endif /* SYS_WINNT */ + + for (;;) { + if (confentries == NULL) + exit(0); + + checkparent(); + + if (resolve_timer == 0) { + if (resolve_value < MAXRESOLVE) + resolve_value <<= 1; + resolve_timer = resolve_value; +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "resolve_timer: 0->%d", resolve_timer); +#endif + config_timer = CONFIG_TIME; + doconfigure(1); + continue; + } else if (config_timer == 0) { + config_timer = CONFIG_TIME; +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "config_timer: 0->%d", config_timer); +#endif + doconfigure(0); + continue; + } +#ifndef SYS_WINNT + /* + * There is a race in here. Is okay, though, since + * all it does is delay things by 30 seconds. + */ +# ifdef HAVE_SIGSUSPEND + sigsuspend(&set); +# else + sigpause(0); +# endif /* HAVE_SIGSUSPEND */ +#else + if (config_timer > 0) + config_timer--; + if (resolve_timer > 0) + resolve_timer--; + sleep(ALARM_TIME); +#endif /* SYS_WINNT */ + } +} + + +#ifndef SYS_WINNT +/* + * bong - service and reschedule an alarm() interrupt + */ +static RETSIGTYPE +bong( + int sig + ) +{ + if (config_timer > 0) + config_timer--; + if (resolve_timer > 0) + resolve_timer--; + alarm(ALARM_TIME); +} +#endif /* SYS_WINNT */ + +/* + * checkparent - see if our parent process is still running + * + * No need to worry in the Windows NT environment whether the + * main thread is still running, because if it goes + * down it takes the whole process down with it (in + * which case we won't be running this thread either) + * Turn function into NOP; + */ + +static void +checkparent(void) +{ +#if !defined (SYS_WINNT) && !defined (SYS_VXWORKS) + + /* + * If our parent (the server) has died we will have been + * inherited by init. If so, exit. + */ + if (getppid() == 1) { + msyslog(LOG_INFO, "parent died before we finished, exiting"); + exit(0); + } +#endif /* SYS_WINNT && SYS_VXWORKS*/ +} + + + +/* + * removeentry - we are done with an entry, remove it from the list + */ +static void +removeentry( + struct conf_entry *entry + ) +{ + register struct conf_entry *ce; + + ce = confentries; + if (ce == entry) { + confentries = ce->ce_next; + return; + } + + while (ce != NULL) { + if (ce->ce_next == entry) { + ce->ce_next = entry->ce_next; + return; + } + ce = ce->ce_next; + } +} + + +/* + * addentry - add an entry to the configuration list + */ +static void +addentry( + char *name, + int mode, + int version, + int minpoll, + int maxpoll, + u_int flags, + int ttl, + keyid_t keyid, + char *keystr + ) +{ + register char *cp; + register struct conf_entry *ce; + unsigned int len; + +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "intres: <%s> %d %d %d %d %x %d %x %s\n", name, + mode, version, minpoll, maxpoll, flags, ttl, keyid, + keystr); +#endif + len = strlen(name) + 1; + cp = (char *)emalloc(len); + memmove(cp, name, len); + + ce = (struct conf_entry *)emalloc(sizeof(struct conf_entry)); + ce->ce_name = cp; + ce->ce_peeraddr = 0; + ce->ce_peeraddr6 = in6addr_any; + ANYSOCK(&ce->peer_store); + ce->ce_hmode = (u_char)mode; + ce->ce_version = (u_char)version; + ce->ce_minpoll = (u_char)minpoll; + ce->ce_maxpoll = (u_char)maxpoll; + ce->ce_flags = (u_char)flags; + ce->ce_ttl = (u_char)ttl; + ce->ce_keyid = keyid; + strncpy((char *)ce->ce_keystr, keystr, MAXFILENAME); + ce->ce_next = NULL; + + if (confentries == NULL) { + confentries = ce; + } else { + register struct conf_entry *cep; + + for (cep = confentries; cep->ce_next != NULL; + cep = cep->ce_next) + /* nothing */; + cep->ce_next = ce; + } +} + + +/* + * findhostaddr - resolve a host name into an address (Or vice-versa) + * + * Given one of {ce_peeraddr,ce_name}, find the other one. + * It returns 1 for "success" and 0 for an uncorrectable failure. + * Note that "success" includes try again errors. You can tell that you + * got a "try again" since {ce_peeraddr,ce_name} will still be zero. + */ +static int +findhostaddr( + struct conf_entry *entry + ) +{ + struct addrinfo *addr; + int error; + + checkparent(); /* make sure our guy is still running */ + + if (entry->ce_name != NULL && SOCKNUL(&entry->peer_store)) { + /* HMS: Squawk? */ + msyslog(LOG_ERR, "findhostaddr: both ce_name and ce_peeraddr are defined..."); + return 1; + } + + if (entry->ce_name == NULL && !SOCKNUL(&entry->peer_store)) { + msyslog(LOG_ERR, "findhostaddr: both ce_name and ce_peeraddr are undefined!"); + return 0; + } + + if (entry->ce_name) { +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "findhostaddr: Resolving <%s>", + entry->ce_name); +#endif /* DEBUG */ + error = getaddrinfo(entry->ce_name, NULL, NULL, &addr); + if (error == 0) { + entry->peer_store = *((struct sockaddr_storage*)(addr->ai_addr)); + if (entry->peer_store.ss_family == AF_INET) { + entry->ce_peeraddr = + GET_INADDR(entry->peer_store); + entry->ce_config.v6_flag = 0; + } else { + entry->ce_peeraddr6 = + GET_INADDR6(entry->peer_store); + entry->ce_config.v6_flag = 1; + } + } + } else { +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "findhostaddr: Resolving %s>", + stoa(&entry->peer_store)); +#endif + entry->ce_name = emalloc(MAXHOSTNAMELEN); + error = getnameinfo((const struct sockaddr *)&entry->peer_store, + SOCKLEN(&entry->peer_store), + (char *)&entry->ce_name, MAXHOSTNAMELEN, + NULL, 0, 0); + } + + if (error != 0) { + /* + * If the resolver is in use, see if the failure is + * temporary. If so, return success. + */ + if (h_errno == TRY_AGAIN) + return (1); + return (0); + } + + if (entry->ce_name) { +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "findhostaddr: name resolved."); +#endif + +#ifdef DEBUG + if (debug > 2) + msyslog(LOG_INFO, "findhostaddr: address resolved."); +#endif + } + + return (1); +} + + +/* + * openntp - open a socket to the ntp server + */ +static void +openntp(void) +{ + struct addrinfo hints; + struct addrinfo *addrResult; + + if (sockfd >= 0) + return; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + if (getaddrinfo(NULL, "ntp", &hints, &addrResult)!=0) { + msyslog(LOG_ERR, "getaddrinfo failed: %m"); + exit(1); + } + sockfd = socket(addrResult->ai_family, addrResult->ai_socktype, 0); + + if (sockfd == -1) { + msyslog(LOG_ERR, "socket() failed: %m"); + exit(1); + } + + /* + * Make the socket non-blocking. We'll wait with select() + */ +#ifndef SYS_WINNT +#if defined(O_NONBLOCK) + if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) { + msyslog(LOG_ERR, "fcntl(O_NONBLOCK) failed: %m"); + exit(1); + } +#else +#if defined(FNDELAY) + if (fcntl(sockfd, F_SETFL, FNDELAY) == -1) { + msyslog(LOG_ERR, "fcntl(FNDELAY) failed: %m"); + exit(1); + } +#else +# include "Bletch: NEED NON BLOCKING IO" +#endif /* FNDDELAY */ +#endif /* O_NONBLOCK */ +#else /* SYS_WINNT */ + { + int on = 1; + if (ioctlsocket(sockfd,FIONBIO,(u_long *) &on) == SOCKET_ERROR) { + msyslog(LOG_ERR, "ioctlsocket(FIONBIO) fails: %m"); + exit(1); /* Windows NT - set socket in non-blocking mode */ + } + } +#endif /* SYS_WINNT */ + if (connect(sockfd, addrResult->ai_addr, addrResult->ai_addrlen) == -1) { + msyslog(LOG_ERR, "openntp: connect() failed: %m"); + exit(1); + } + freeaddrinfo(addrResult); +} + + +/* + * request - send a configuration request to the server, wait for a response + */ +static int +request( + struct conf_peer *conf + ) +{ + fd_set fdset; + struct timeval tvout; + struct req_pkt reqpkt; + l_fp ts; + int n; +#ifdef SYS_WINNT + HANDLE hReadWriteEvent = NULL; + BOOL ret; + DWORD NumberOfBytesWritten, NumberOfBytesRead, dwWait; + OVERLAPPED overlap; +#endif /* SYS_WINNT */ + + checkparent(); /* make sure our guy is still running */ + + if (sockfd < 0) + openntp(); + +#ifdef SYS_WINNT + hReadWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL); +#endif /* SYS_WINNT */ + + /* + * Try to clear out any previously received traffic so it + * doesn't fool us. Note the socket is nonblocking. + */ + tvout.tv_sec = 0; + tvout.tv_usec = 0; + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + while (select(sockfd + 1, &fdset, (fd_set *)0, (fd_set *)0, &tvout) > + 0) { + recv(sockfd, (char *)&reqpkt, REQ_LEN_MAC, 0); + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + } + + /* + * Make up a request packet with the configuration info + */ + memset((char *)&reqpkt, 0, sizeof(reqpkt)); + + reqpkt.rm_vn_mode = RM_VN_MODE(0, 0, 0); + reqpkt.auth_seq = AUTH_SEQ(1, 0); /* authenticated, no seq */ + reqpkt.implementation = IMPL_XNTPD; /* local implementation */ + reqpkt.request = REQ_CONFIG; /* configure a new peer */ + reqpkt.err_nitems = ERR_NITEMS(0, 1); /* one item */ + reqpkt.mbz_itemsize = MBZ_ITEMSIZE(sizeof(struct conf_peer)); + /* Make sure mbz_itemsize <= sizeof reqpkt.data */ + if (sizeof(struct conf_peer) > sizeof (reqpkt.data)) { + msyslog(LOG_ERR, "Bletch: conf_peer is too big for reqpkt.data!"); + exit(1); + } + memmove(reqpkt.data, (char *)conf, sizeof(struct conf_peer)); + reqpkt.keyid = htonl(req_keyid); + + get_systime(&ts); + L_ADDUF(&ts, SKEWTIME); + HTONL_FP(&ts, &reqpkt.tstamp); + n = 0; + if (sys_authenticate) + n = authencrypt(req_keyid, (u_int32 *)&reqpkt, REQ_LEN_NOMAC); + + /* + * Done. Send it. + */ +#ifndef SYS_WINNT + n = send(sockfd, (char *)&reqpkt, (unsigned)(REQ_LEN_NOMAC + n), 0); + if (n < 0) { + msyslog(LOG_ERR, "send to NTP server failed: %m"); + return 0; /* maybe should exit */ + } +#else + /* In the NT world, documentation seems to indicate that there + * exist _write and _read routines that can be used to do blocking + * I/O on sockets. Problem is these routines require a socket + * handle obtained through the _open_osf_handle C run-time API + * of which there is no explanation in the documentation. We need + * nonblocking write's and read's anyway for our purpose here. + * We're therefore forced to deviate a little bit from the Unix + * model here and use the ReadFile and WriteFile Win32 I/O API's + * on the socket + */ + overlap.Offset = overlap.OffsetHigh = (DWORD)0; + overlap.hEvent = hReadWriteEvent; + ret = WriteFile((HANDLE)sockfd, (char *)&reqpkt, REQ_LEN_NOMAC + n, + (LPDWORD)&NumberOfBytesWritten, (LPOVERLAPPED)&overlap); + if ((ret == FALSE) && (GetLastError() != ERROR_IO_PENDING)) { + msyslog(LOG_ERR, "send to NTP server failed: %m"); + return 0; + } + dwWait = WaitForSingleObject(hReadWriteEvent, (DWORD) TIMEOUT_SEC * 1000); + if ((dwWait == WAIT_FAILED) || (dwWait == WAIT_TIMEOUT)) { + if (dwWait == WAIT_FAILED) + msyslog(LOG_ERR, "WaitForSingleObject failed: %m"); + return 0; + } +#endif /* SYS_WINNT */ + + + /* + * Wait for a response. A weakness of the mode 7 protocol used + * is that there is no way to associate a response with a + * particular request, i.e. the response to this configuration + * request is indistinguishable from that to any other. I should + * fix this some day. In any event, the time out is fairly + * pessimistic to make sure that if an answer is coming back + * at all, we get it. + */ + for (;;) { + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + tvout.tv_sec = TIMEOUT_SEC; + tvout.tv_usec = TIMEOUT_USEC; + + n = select(sockfd + 1, &fdset, (fd_set *)0, + (fd_set *)0, &tvout); + + if (n < 0) + { + if (errno != EINTR) + msyslog(LOG_ERR, "select() fails: %m"); + return 0; + } + else if (n == 0) + { + if (debug) + msyslog(LOG_INFO, "select() returned 0."); + return 0; + } + +#ifndef SYS_WINNT + n = recv(sockfd, (char *)&reqpkt, REQ_LEN_MAC, 0); + if (n <= 0) { + if (n < 0) { + msyslog(LOG_ERR, "recv() fails: %m"); + return 0; + } + continue; + } +#else /* Overlapped I/O used on non-blocking sockets on Windows NT */ + ret = ReadFile((HANDLE)sockfd, (char *)&reqpkt, (DWORD)REQ_LEN_MAC, + (LPDWORD)&NumberOfBytesRead, (LPOVERLAPPED)&overlap); + if ((ret == FALSE) && (GetLastError() != ERROR_IO_PENDING)) { + msyslog(LOG_ERR, "ReadFile() fails: %m"); + return 0; + } + dwWait = WaitForSingleObject(hReadWriteEvent, (DWORD) TIMEOUT_SEC * 1000); + if ((dwWait == WAIT_FAILED) || (dwWait == WAIT_TIMEOUT)) { + if (dwWait == WAIT_FAILED) { + msyslog(LOG_ERR, "WaitForSingleObject fails: %m"); + return 0; + } + continue; + } + n = NumberOfBytesRead; +#endif /* SYS_WINNT */ + + /* + * Got one. Check through to make sure it is what + * we expect. + */ + if (n < RESP_HEADER_SIZE) { + msyslog(LOG_ERR, "received runt response (%d octets)", + n); + continue; + } + + if (!ISRESPONSE(reqpkt.rm_vn_mode)) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, "received non-response packet"); +#endif + continue; + } + + if (ISMORE(reqpkt.rm_vn_mode)) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, "received fragmented packet"); +#endif + continue; + } + + if ( ( (INFO_VERSION(reqpkt.rm_vn_mode) < 2) + || (INFO_VERSION(reqpkt.rm_vn_mode) > NTP_VERSION)) + || INFO_MODE(reqpkt.rm_vn_mode) != MODE_PRIVATE) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "version (%d/%d) or mode (%d/%d) incorrect", + INFO_VERSION(reqpkt.rm_vn_mode), + NTP_VERSION, + INFO_MODE(reqpkt.rm_vn_mode), + MODE_PRIVATE); +#endif + continue; + } + + if (INFO_SEQ(reqpkt.auth_seq) != 0) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "nonzero sequence number (%d)", + INFO_SEQ(reqpkt.auth_seq)); +#endif + continue; + } + + if (reqpkt.implementation != IMPL_XNTPD || + reqpkt.request != REQ_CONFIG) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "implementation (%d) or request (%d) incorrect", + reqpkt.implementation, reqpkt.request); +#endif + continue; + } + + if (INFO_NITEMS(reqpkt.err_nitems) != 0 || + INFO_MBZ(reqpkt.mbz_itemsize) != 0 || + INFO_ITEMSIZE(reqpkt.mbz_itemsize) != 0) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "nitems (%d) mbz (%d) or itemsize (%d) nonzero", + INFO_NITEMS(reqpkt.err_nitems), + INFO_MBZ(reqpkt.mbz_itemsize), + INFO_ITEMSIZE(reqpkt.mbz_itemsize)); +#endif + continue; + } + + n = INFO_ERR(reqpkt.err_nitems); + switch (n) { + case INFO_OKAY: + /* success */ + return 1; + + case INFO_ERR_IMPL: + msyslog(LOG_ERR, + "ntpd reports implementation mismatch!"); + return 0; + + case INFO_ERR_REQ: + msyslog(LOG_ERR, + "ntpd says configuration request is unknown!"); + return 0; + + case INFO_ERR_FMT: + msyslog(LOG_ERR, + "ntpd indicates a format error occurred!"); + return 0; + + case INFO_ERR_NODATA: + msyslog(LOG_ERR, + "ntpd indicates no data available!"); + return 0; + + case INFO_ERR_AUTH: + msyslog(LOG_ERR, + "ntpd returns a permission denied error!"); + return 0; + + default: + msyslog(LOG_ERR, + "ntpd returns unknown error code %d!", n); + return 0; + } + } +} + + +/* + * nexttoken - return the next token from a line + */ +static char * +nexttoken( + char **lptr + ) +{ + register char *cp; + register char *tstart; + + cp = *lptr; + + /* + * Skip leading white space + */ + while (*cp == ' ' || *cp == '\t') + cp++; + + /* + * If this is the end of the line, return nothing. + */ + if (*cp == '\n' || *cp == '\0') { + *lptr = cp; + return NULL; + } + + /* + * Must be the start of a token. Record the pointer and look + * for the end. + */ + tstart = cp++; + while (*cp != ' ' && *cp != '\t' && *cp != '\n' && *cp != '\0') + cp++; + + /* + * Terminate the token with a \0. If this isn't the end of the + * line, space to the next character. + */ + if (*cp == '\n' || *cp == '\0') + *cp = '\0'; + else + *cp++ = '\0'; + + *lptr = cp; + return tstart; +} + + +/* + * readconf - read the configuration information out of the file we + * were passed. Note that since the file is supposed to be + * machine generated, we bail out at the first sign of trouble. + */ +static void +readconf( + FILE *fp, + char *name + ) +{ + register int i; + char *token[NUMTOK]; + u_long intval[NUMTOK]; + u_int flags; + char buf[MAXLINESIZE]; + char *bp; + + while (fgets(buf, MAXLINESIZE, fp) != NULL) { + + bp = buf; + for (i = 0; i < NUMTOK; i++) { + if ((token[i] = nexttoken(&bp)) == NULL) { + msyslog(LOG_ERR, + "tokenizing error in file `%s', quitting", + name); + exit(1); + } + } + + for (i = 1; i < NUMTOK - 1; i++) { + if (!atouint(token[i], &intval[i])) { + msyslog(LOG_ERR, + "format error for integer token `%s', file `%s', quitting", + token[i], name); + exit(1); + } + } + + if (intval[TOK_HMODE] != MODE_ACTIVE && + intval[TOK_HMODE] != MODE_CLIENT && + intval[TOK_HMODE] != MODE_BROADCAST) { + msyslog(LOG_ERR, "invalid mode (%ld) in file %s", + intval[TOK_HMODE], name); + exit(1); + } + + if (intval[TOK_VERSION] > NTP_VERSION || + intval[TOK_VERSION] < NTP_OLDVERSION) { + msyslog(LOG_ERR, "invalid version (%ld) in file %s", + intval[TOK_VERSION], name); + exit(1); + } + if (intval[TOK_MINPOLL] < NTP_MINPOLL || + intval[TOK_MINPOLL] > NTP_MAXPOLL) { + msyslog(LOG_ERR, "invalid MINPOLL value (%ld) in file %s", + intval[TOK_MINPOLL], name); + exit(1); + } + + if (intval[TOK_MAXPOLL] < NTP_MINPOLL || + intval[TOK_MAXPOLL] > NTP_MAXPOLL) { + msyslog(LOG_ERR, "invalid MAXPOLL value (%ld) in file %s", + intval[TOK_MAXPOLL], name); + exit(1); + } + + if ((intval[TOK_FLAGS] & ~(FLAG_AUTHENABLE | FLAG_PREFER | + FLAG_NOSELECT | FLAG_BURST | FLAG_IBURST | FLAG_SKEY)) + != 0) { + msyslog(LOG_ERR, "invalid flags (%ld) in file %s", + intval[TOK_FLAGS], name); + exit(1); + } + + flags = 0; + if (intval[TOK_FLAGS] & FLAG_AUTHENABLE) + flags |= CONF_FLAG_AUTHENABLE; + if (intval[TOK_FLAGS] & FLAG_PREFER) + flags |= CONF_FLAG_PREFER; + if (intval[TOK_FLAGS] & FLAG_NOSELECT) + flags |= CONF_FLAG_NOSELECT; + if (intval[TOK_FLAGS] & FLAG_BURST) + flags |= CONF_FLAG_BURST; + if (intval[TOK_FLAGS] & FLAG_IBURST) + flags |= CONF_FLAG_IBURST; + if (intval[TOK_FLAGS] & FLAG_SKEY) + flags |= CONF_FLAG_SKEY; + + /* + * This is as good as we can check it. Add it in. + */ + addentry(token[TOK_HOSTNAME], (int)intval[TOK_HMODE], + (int)intval[TOK_VERSION], (int)intval[TOK_MINPOLL], + (int)intval[TOK_MAXPOLL], flags, (int)intval[TOK_TTL], + intval[TOK_KEYID], token[TOK_KEYSTR]); + } +} + + +/* + * doconfigure - attempt to resolve names and configure the server + */ +static void +doconfigure( + int dores + ) +{ + register struct conf_entry *ce; + register struct conf_entry *ceremove; + + ce = confentries; + while (ce != NULL) { +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_INFO, + "doconfigure: <%s> has peeraddr %s", + ce->ce_name, stoa(&ce->peer_store)); +#endif + if (dores && !SOCKNUL(&(ce->peer_store))) { + if (!findhostaddr(ce)) { + msyslog(LOG_ERR, + "couldn't resolve `%s', giving up on it", + ce->ce_name); + ceremove = ce; + ce = ceremove->ce_next; + removeentry(ceremove); + continue; + } + } + + if (!SOCKNUL(&ce->peer_store)) { + if (request(&ce->ce_config)) { + ceremove = ce; + ce = ceremove->ce_next; + removeentry(ceremove); + continue; + } +#ifdef DEBUG + if (debug > 1) { + msyslog(LOG_INFO, + "doconfigure: request() FAILED, maybe next time."); + } +#endif + } + ce = ce->ce_next; + } +} diff --git a/ntpd/ntp_io.c b/ntpd/ntp_io.c new file mode 100644 index 0000000..9f2acea --- /dev/null +++ b/ntpd/ntp_io.c @@ -0,0 +1,2257 @@ +/* + * ntp_io.c - input/output routines for ntpd. The socket-opening code + * was shamelessly stolen from ntpd. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntp_machine.h" +#include "ntpd.h" +#include "ntp_io.h" +#include "iosignal.h" +#include "ntp_refclock.h" +#include "ntp_if.h" +#include "ntp_stdlib.h" +#include "ntp.h" + +/* Don't include ISC's version of IPv6 variables and structures */ +#define ISC_IPV6_H 1 +#include <isc/interfaceiter.h> +#include <isc/list.h> +#include <isc/result.h> + +#ifdef SIM +#include "ntpsim.h" +#endif + +#include <stdio.h> +#include <signal.h> +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif /* HAVE_SYS_PARAM_H */ +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +# include <netinet/in_systm.h> +#else /* Some old linux systems at least have in_system.h instead. */ +# ifdef HAVE_NETINET_IN_SYSTEM_H +# include <netinet/in_system.h> +# endif +#endif /* HAVE_NETINET_IN_SYSTM_H */ +#ifdef HAVE_NETINET_IP_H +# include <netinet/ip.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif +#ifdef HAVE_SYS_SOCKIO_H /* UXPV: SIOC* #defines (Frank Vance <fvance@waii.com>) */ +# include <sys/sockio.h> +#endif +#include <arpa/inet.h> + +extern int listen_to_virtual_ips; + +#if defined(SYS_WINNT) +#include <transmitbuff.h> +#include <isc/win32os.h> +/* + * Define this macro to control the behavior of connection + * resets on UDP sockets. See Microsoft KnowledgeBase Article Q263823 + * for details. + * NOTE: This requires that Windows 2000 systems install Service Pack 2 + * or later. + */ +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) +#endif + +#endif + +/* + * We do asynchronous input using the SIGIO facility. A number of + * recvbuf buffers are preallocated for input. In the signal + * handler we poll to see which sockets are ready and read the + * packets from them into the recvbuf's along with a time stamp and + * an indication of the source host and the interface it was received + * through. This allows us to get as accurate receive time stamps + * as possible independent of other processing going on. + * + * We watch the number of recvbufs available to the signal handler + * and allocate more when this number drops below the low water + * mark. If the signal handler should run out of buffers in the + * interim it will drop incoming frames, the idea being that it is + * better to drop a packet than to be inaccurate. + */ + + +/* + * Other statistics of possible interest + */ +volatile u_long packets_dropped; /* total number of packets dropped on reception */ +volatile u_long packets_ignored; /* packets received on wild card interface */ +volatile u_long packets_received; /* total number of packets received */ +u_long packets_sent; /* total number of packets sent */ +u_long packets_notsent; /* total number of packets which couldn't be sent */ + +volatile u_long handler_calls; /* number of calls to interrupt handler */ +volatile u_long handler_pkts; /* number of pkts received by handler */ +u_long io_timereset; /* time counters were reset */ + +/* + * Interface stuff + */ +struct interface *any_interface; /* default ipv4 interface */ +struct interface *any6_interface; /* default ipv6 interface */ +struct interface *loopback_interface; /* loopback ipv4 interface */ +struct interface *loopback6_interface; /* loopback ipv6 interface */ +struct interface inter_list[MAXINTERFACES]; /* Interface list */ +int ninterfaces; /* Total number of interfaces */ +int nwilds; /* Total number of wildcard intefaces */ +int wildipv4 = -1; /* Index into inter_list for IPv4 wildcard */ +int wildipv6 = -1; /* Index into inter_list for IPv6 wildcard */ + +#ifdef REFCLOCK +/* + * Refclock stuff. We keep a chain of structures with data concerning + * the guys we are doing I/O for. + */ +static struct refclockio *refio; +#endif /* REFCLOCK */ + + +/* + * Define what the possible "soft" errors can be. These are non-fatal returns + * of various network related functions, like recv() and so on. + * + * For some reason, BSDI (and perhaps others) will sometimes return <0 + * from recv() but will have errno==0. This is broken, but we have to + * work around it here. + */ +#define SOFT_ERROR(e) ((e) == EAGAIN || \ + (e) == EWOULDBLOCK || \ + (e) == EINTR || \ + (e) == 0) + +/* + * File descriptor masks etc. for call to select + * Not needed for I/O Completion Ports + */ +fd_set activefds; +int maxactivefd; + +static int create_sockets P((u_short)); +static SOCKET open_socket P((struct sockaddr_storage *, int, int)); +static void close_socket P((SOCKET)); +#ifdef REFCLOCK +static void close_file P((SOCKET)); +#endif +static char * fdbits P((int, fd_set *)); +static void set_reuseaddr P((int)); + +typedef struct vsock vsock_t; + +struct vsock { + SOCKET fd; + ISC_LINK(vsock_t) link; +}; + +ISC_LIST(vsock_t) sockets_list; + +typedef struct remaddr remaddr_t; + +struct remaddr { + struct sockaddr_storage addr; + int if_index; + ISC_LINK(remaddr_t) link; +}; + +ISC_LIST(remaddr_t) remoteaddr_list; + +void add_socket_to_list P((SOCKET)); +void delete_socket_from_list P((SOCKET)); +void add_addr_to_list P((struct sockaddr_storage *, int)); +void delete_addr_from_list P((struct sockaddr_storage *)); +int find_addr_in_list P((struct sockaddr_storage *)); +int create_wildcards P((u_short)); +isc_boolean_t address_okay P((isc_interface_t *)); +void convert_isc_if P((isc_interface_t *, struct interface *, u_short)); + +#ifdef SYS_WINNT +/* + * Windows 2000 systems incorrectly cause UDP sockets using WASRecvFrom + * to not work correctly, returning a WSACONNRESET error when a WSASendTo + * fails with an "ICMP port unreachable" response and preventing the + * socket from using the WSARecvFrom in subsequent operations. + * The function below fixes this, but requires that Windows 2000 + * Service Pack 2 or later be installed on the system. NT 4.0 + * systems are not affected by this and work correctly. + * See Microsoft Knowledge Base Article Q263823 for details of this. + */ +isc_result_t +connection_reset_fix(SOCKET fd) { + DWORD dwBytesReturned = 0; + BOOL bNewBehavior = FALSE; + DWORD status; + + if(isc_win32os_majorversion() < 5) + return (ISC_R_SUCCESS); /* NT 4.0 has no problem */ + + /* disable bad behavior using IOCTL: SIO_UDP_CONNRESET */ + status = WSAIoctl(fd, SIO_UDP_CONNRESET, &bNewBehavior, + sizeof(bNewBehavior), NULL, 0, + &dwBytesReturned, NULL, NULL); + if (status != SOCKET_ERROR) + return (ISC_R_SUCCESS); + else + return (ISC_R_UNEXPECTED); +} +#endif +/* + * init_io - initialize I/O data structures and call socket creation routine + */ +void +init_io(void) +{ +#ifdef SYS_WINNT + init_transmitbuff(); +#endif /* SYS_WINNT */ + + /* + * Init buffer free list and stat counters + */ + init_recvbuff(RECV_INIT); + + packets_dropped = packets_received = 0; + packets_ignored = 0; + packets_sent = packets_notsent = 0; + handler_calls = handler_pkts = 0; + io_timereset = 0; + loopback_interface = NULL; + loopback6_interface = NULL; + +#ifdef REFCLOCK + refio = 0; +#endif + +#if defined(HAVE_SIGNALED_IO) + (void) set_signal(); +#endif + +#ifdef SYS_WINNT + if (!Win32InitSockets()) + { + netsyslog(LOG_ERR, "No useable winsock.dll: %m"); + exit(1); + } +#endif /* SYS_WINNT */ + + ISC_LIST_INIT(sockets_list); + + ISC_LIST_INIT(remoteaddr_list); + + /* + * Create the sockets + */ + BLOCKIO(); + (void) create_sockets(htons(NTP_PORT)); + UNBLOCKIO(); + +#ifdef DEBUG + if (debug) + printf("init_io: maxactivefd %d\n", maxactivefd); +#endif +} + +int +create_wildcards(u_short port) { + + int idx = 0; + /* + * create pseudo-interface with wildcard IPv4 address + */ + inter_list[idx].sin.ss_family = AF_INET; + ((struct sockaddr_in*)&inter_list[idx].sin)->sin_addr.s_addr = htonl(INADDR_ANY); + ((struct sockaddr_in*)&inter_list[idx].sin)->sin_port = port; + (void) strncpy(inter_list[idx].name, "wildcard", sizeof(inter_list[idx].name)); + inter_list[idx].mask.ss_family = AF_INET; + ((struct sockaddr_in*)&inter_list[idx].mask)->sin_addr.s_addr = htonl(~(u_int32)0); + inter_list[idx].bfd = INVALID_SOCKET; + inter_list[idx].num_mcast = 0; + inter_list[idx].received = 0; + inter_list[idx].sent = 0; + inter_list[idx].notsent = 0; + inter_list[idx].flags = INT_BROADCAST; + any_interface = &inter_list[idx]; +#if defined(MCAST) + /* + * enable possible multicast reception on the broadcast socket + */ + inter_list[idx].bcast.ss_family = AF_INET; + ((struct sockaddr_in*)&inter_list[idx].bcast)->sin_port = port; + ((struct sockaddr_in*)&inter_list[idx].bcast)->sin_addr.s_addr = htonl(INADDR_ANY); +#endif /* MCAST */ + wildipv4 = idx; + idx++; + +#ifdef HAVE_IPV6 + /* + * create pseudo-interface with wildcard IPv6 address + */ + if (isc_net_probeipv6() == ISC_R_SUCCESS) { + inter_list[idx].sin.ss_family = AF_INET6; + ((struct sockaddr_in6*)&inter_list[idx].sin)->sin6_addr = in6addr_any; + ((struct sockaddr_in6*)&inter_list[idx].sin)->sin6_port = port; + (void) strncpy(inter_list[idx].name, "wildcard", sizeof(inter_list[idx].name)); + inter_list[idx].mask.ss_family = AF_INET6; + memset(&((struct sockaddr_in6*)&inter_list[idx].mask)->sin6_addr.s6_addr, 0xff, sizeof(struct in6_addr)); + inter_list[idx].bfd = INVALID_SOCKET; + inter_list[idx].num_mcast = 0; + inter_list[idx].received = 0; + inter_list[idx].sent = 0; + inter_list[idx].notsent = 0; + inter_list[idx].flags = 0; + any6_interface = &inter_list[idx]; + wildipv6 = idx; + idx++; + } +#endif + return (idx); +} + +isc_boolean_t +address_okay(isc_interface_t *isc_if) { + +#ifdef DEBUG + if (debug > 2) + printf("address_okay: listen Virtual: %d, IF name: %s, Up Flag: %d\n", + listen_to_virtual_ips, isc_if->name, (isc_if->flags & INTERFACE_F_UP)); +#endif + + if (listen_to_virtual_ips == 0 && (strchr(isc_if->name, (int)':') != NULL)) + return (ISC_FALSE); + + /* XXXPDM This should be fixed later, but since we may not have set + * the UP flag, we at least get to use the interface. + * The UP flag is not always set so we don't do this right now. + */ +/* if ((isc_if->flags & INTERFACE_F_UP) == 0) + return (ISC_FALSE); +*/ + return (ISC_TRUE); +} +void +convert_isc_if(isc_interface_t *isc_if, struct interface *itf, u_short port) { + + if(isc_if->af == AF_INET) { + itf->sin.ss_family = (u_short) isc_if->af; + strcpy(itf->name, isc_if->name); + memcpy(&(((struct sockaddr_in*)&itf->sin)->sin_addr), + &(isc_if->address.type.in), + sizeof(struct in_addr)); + ((struct sockaddr_in*)&itf->sin)->sin_port = port; + + if((isc_if->flags & INTERFACE_F_BROADCAST) != 0) { + itf->flags |= INT_BROADCAST; + itf->bcast.ss_family = itf->sin.ss_family; + memcpy(&(((struct sockaddr_in*)&itf->bcast)->sin_addr), + &(isc_if->broadcast.type.in), + sizeof(struct in_addr)); + ((struct sockaddr_in*)&itf->bcast)->sin_port = port; + } + + itf->mask.ss_family = itf->sin.ss_family; + memcpy(&(((struct sockaddr_in*)&itf->mask)->sin_addr), + &(isc_if->netmask.type.in), + sizeof(struct in_addr)); + ((struct sockaddr_in*)&itf->mask)->sin_port = port; + + if (((isc_if->flags & INTERFACE_F_LOOPBACK) != 0) && (loopback_interface == NULL)) + { + loopback_interface = itf; + } + } +#ifdef HAVE_IPV6 + else if (isc_if->af == AF_INET6) { + itf->sin.ss_family = (u_short) isc_if->af; + strcpy(itf->name, isc_if->name); + memcpy(&(((struct sockaddr_in6 *)&itf->sin)->sin6_addr), + &(isc_if->address.type.in6), + sizeof(struct in6_addr)); + ((struct sockaddr_in6 *)&itf->sin)->sin6_port = port; + + itf->mask.ss_family = itf->sin.ss_family; + memcpy(&(((struct sockaddr_in6 *)&itf->mask)->sin6_addr), + &(isc_if->netmask.type.in6), + sizeof(struct in6_addr)); + ((struct sockaddr_in6 *)&itf->mask)->sin6_port = port; + + if (((isc_if->flags & INTERFACE_F_LOOPBACK) != 0) && (loopback6_interface == NULL)) + { + loopback6_interface = itf; + } + } +#endif /* HAVE_IPV6 */ + + /* Process the rest of the flags */ + + if((isc_if->flags & INTERFACE_F_UP) != 0) + itf->flags |= INT_UP; + if((isc_if->flags & INTERFACE_F_LOOPBACK) != 0) + itf->flags |= INT_LOOPBACK; + if((isc_if->flags & INTERFACE_F_POINTTOPOINT) != 0) + itf->flags |= INT_PPP; +} +/* + * create_sockets - create a socket for each interface plus a default + * socket for when we don't know where to send + */ +static int +create_sockets( + u_short port + ) +{ + struct sockaddr_storage resmask; + int i; + isc_mem_t *mctx = NULL; + isc_interfaceiter_t *iter = NULL; + isc_boolean_t scan_ipv4 = ISC_FALSE; + isc_boolean_t scan_ipv6 = ISC_FALSE; + isc_result_t result; + int idx = 0; + +#ifdef DEBUG + if (debug) + printf("create_sockets(%d)\n", ntohs( (u_short) port)); +#endif + + if (isc_net_probeipv6() == ISC_R_SUCCESS) + scan_ipv6 = ISC_TRUE; +#ifdef HAVE_IPV6 + else + netsyslog(LOG_ERR, "no IPv6 interfaces found"); +#endif + + if (isc_net_probeipv4() == ISC_R_SUCCESS) + scan_ipv4 = ISC_TRUE; + else + netsyslog(LOG_ERR, "no IPv4 interfaces found"); + + nwilds = create_wildcards(port); + idx = nwilds; + + result = isc_interfaceiter_create(mctx, &iter); + if (result != ISC_R_SUCCESS) + return (result); + + for (result = isc_interfaceiter_first(iter); + result == ISC_R_SUCCESS; + result = isc_interfaceiter_next(iter)) + { + isc_interface_t isc_if; + unsigned int family; + + result = isc_interfaceiter_current(iter, &isc_if); + if (result != ISC_R_SUCCESS) + break; + + /* See if we have a valid family to use */ + family = isc_if.address.family; + if (family != AF_INET && family != AF_INET6) + continue; + if (scan_ipv4 == ISC_FALSE && family == AF_INET) + continue; + if (scan_ipv6 == ISC_FALSE && family == AF_INET6) + continue; + + /* Check to see if we are going to use the interface */ + if (address_okay(&isc_if) == ISC_TRUE) { + convert_isc_if(&isc_if, &inter_list[idx], port); + inter_list[idx].fd = INVALID_SOCKET; + inter_list[idx].bfd = INVALID_SOCKET; + inter_list[idx].num_mcast = 0; + inter_list[idx].received = 0; + inter_list[idx].sent = 0; + inter_list[idx].notsent = 0; + idx++; + } + } + isc_interfaceiter_destroy(&iter); + + ninterfaces = idx; + /* + * I/O Completion Ports don't care about the select and FD_SET + */ +#ifndef HAVE_IO_COMPLETION_PORT + maxactivefd = 0; + FD_ZERO(&activefds); +#endif + for (i = 0; i < ninterfaces; i++) { + inter_list[i].fd = open_socket(&inter_list[i].sin, + inter_list[i].flags & INT_BROADCAST, 0); + if (inter_list[i].bfd != INVALID_SOCKET) + msyslog(LOG_INFO, "Listening on interface %s, %s#%d", + inter_list[i].name, + stoa((&inter_list[i].sin)), + NTP_PORT); + if ((inter_list[i].flags & INT_BROADCAST) && + inter_list[i].bfd != INVALID_SOCKET) + msyslog(LOG_INFO, "Listening on broadcast address %s#%d", + stoa((&inter_list[i].bcast)), + NTP_PORT); +#if defined (HAVE_IO_COMPLETION_PORT) + if (inter_list[i].fd != INVALID_SOCKET) { + io_completion_port_add_socket(inter_list[i].fd, &inter_list[i]); + } +#endif + } + + /* + * Now that we have opened all the sockets, turn off the reuse + * flag for security. + */ + set_reuseaddr(0); + + /* + * Blacklist all bound interface addresses + * Wildcard interfaces are ignored. + */ + + for (i = nwilds; i < ninterfaces; i++) { + SET_HOSTMASK(&resmask, inter_list[i].sin.ss_family); + hack_restrict(RESTRICT_FLAGS, &inter_list[i].sin, &resmask, + RESM_NTPONLY|RESM_INTERFACE, RES_IGNORE); + } + + /* + * Calculate the address hash for each interface address. + */ + for (i = 0; i < ninterfaces; i++) { + inter_list[i].addr_refid = addr2refid(&inter_list[i].sin); + } + + +#ifdef DEBUG + if (debug > 1) { + printf("create_sockets: ninterfaces=%d\n", ninterfaces); + for (i = 0; i < ninterfaces; i++) { + printf("interface %d: fd=%d, bfd=%d, name=%.8s, flags=0x%x\n", + i, + inter_list[i].fd, + inter_list[i].bfd, + inter_list[i].name, + inter_list[i].flags); + /* Leave these as three printf calls. */ + printf(" sin=%s", + stoa((&inter_list[i].sin))); + if (inter_list[i].flags & INT_BROADCAST) + printf(" bcast=%s,", + stoa((&inter_list[i].bcast))); + printf(" mask=%s\n", + stoa((&inter_list[i].mask))); + } + } +#endif + return ninterfaces; +} + +/* + * io_setbclient - open the broadcast client sockets + */ +void +io_setbclient(void) +{ + int i; + +#ifdef OPEN_BCAST_SOCKET + set_reuseaddr(1); +#endif + for (i = nwilds; i < ninterfaces; i++) { + /* Only IPv4 addresses are valid for broadcast */ + if (inter_list[i].bcast.ss_family != AF_INET) + continue; + + /* Is this a broadcast address? */ + if (!(inter_list[i].flags & INT_BROADCAST)) + continue; + + /* Do we already have the broadcast address open? */ + if (inter_list[i].flags & INT_BCASTOPEN) + continue; + +#ifdef SYS_SOLARIS + inter_list[i].bcast.sin_addr.s_addr = htonl(INADDR_ANY); +#endif +#ifdef OPEN_BCAST_SOCKET /* Was: !SYS_DOMAINOS && !SYS_LINUX */ + inter_list[i].bfd = open_socket(&inter_list[i].bcast, + INT_BROADCAST, 1); + if (inter_list[i].bfd != INVALID_SOCKET) { + inter_list[i].flags |= INT_BCASTOPEN; +#if defined (HAVE_IO_COMPLETION_PORT) + io_completion_port_add_socket(inter_list[i].bfd, &inter_list[i]); +#endif + } +#ifdef DEBUG + if (debug) { + if (inter_list[i].bfd != INVALID_SOCKET) + printf("io_setbclient: Opened broadcast client on interface %d, socket: %d\n", + i, inter_list[i].bfd); + else + printf("io_setbclient: Unable to Open broadcast client on interface %d\n", + i); + } +#endif +#endif + } +#ifdef OPEN_BCAST_SOCKET + set_reuseaddr(0); +#endif +#ifdef DEBUG + if (debug) + printf("io_setbclient: Opened broadcast clients\n"); +#endif +} + +/* + * set_reuseaddr() - set/clear REUSEADDR on all sockets + * NB possible hole - should we be doing this on broadcast + * fd's also? + */ +static void +set_reuseaddr(int flag) { + int i; + + for (i=0; i < ninterfaces; i++) { + /* + * if inter_list[ n ].fd is -1, we might have a adapter + * configured but not present + */ + if (inter_list[i].fd != INVALID_SOCKET) { + if (setsockopt(inter_list[i].fd, SOL_SOCKET, + SO_REUSEADDR, (char *)&flag, + sizeof(flag))) { + netsyslog(LOG_ERR, "set_reuseaddr: setsockopt(SO_REUSEADDR, %s) failed: %m", flag ? "on" : "off"); + } + } + } +} + + +/* + * io_multicast_add() - add multicast group address + */ +void +io_multicast_add( + struct sockaddr_storage addr + ) +{ +#ifdef MCAST + struct ip_mreq mreq; + int i = ninterfaces; /* Use the next interface */ + u_int32 haddr = ntohl(((struct sockaddr_in*)&addr)->sin_addr.s_addr); + struct in_addr iaddr; + SOCKET s; + struct sockaddr_in *sinp; + +#ifdef HAVE_IPV6 + struct ipv6_mreq mreq6; + struct in6_addr iaddr6; + struct sockaddr_in6 *sin6p; +#endif /* HAVE_IPV6 */ + + switch (addr.ss_family) + { + case AF_INET : + iaddr = (((struct sockaddr_in*)&addr)->sin_addr); + if (!IN_CLASSD(haddr)) { + netsyslog(LOG_ERR, + "multicast address %s not class D", + inet_ntoa(iaddr)); + return; + } + for (i = nwilds; i < ninterfaces; i++) { + /* Be sure it's the correct family */ + if (inter_list[i].sin.ss_family != AF_INET) + continue; + /* Already have this address */ + if (SOCKCMP(&inter_list[i].sin, &addr)) + return; + /* found a free slot */ + if (SOCKNUL(&inter_list[i].sin) && + inter_list[i].fd <= 0 && inter_list[i].bfd <= 0 && + inter_list[i].flags == 0) + break; + } + sinp = (struct sockaddr_in*)&(inter_list[i].sin); + memset((char *)&mreq, 0, sizeof(mreq)); + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + sinp->sin_family = AF_INET; + sinp->sin_addr = iaddr; + sinp->sin_port = htons(NTP_PORT); + + /* + * Try opening a socket for the specified class D address. This + * works under SunOS 4.x, but not OSF1 .. :-( + */ + set_reuseaddr(1); + s = open_socket((struct sockaddr_storage*)sinp, 0, 1); + set_reuseaddr(0); + if (s == INVALID_SOCKET) { + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + if (wildipv4 >= 0) { + i = wildipv4; + /* HACK ! -- stuff in an address */ + inter_list[i].bcast = addr; + netsyslog(LOG_ERR, + "...multicast address %s using wildcard socket", + inet_ntoa(iaddr)); + } else { + netsyslog(LOG_ERR, + "No wildcard socket available to use for address %s", + inet_ntoa(iaddr)); + return; + } + } else { + inter_list[i].fd = s; + inter_list[i].bfd = INVALID_SOCKET; + (void) strncpy(inter_list[i].name, "multicast", + sizeof(inter_list[i].name)); + ((struct sockaddr_in*)&inter_list[i].mask)->sin_addr.s_addr = htonl(~(u_int32)0); +#if defined (HAVE_IO_COMPLETION_PORT) + io_completion_port_add_socket(inter_list[i].fd, &inter_list[i]); +#endif + } + + /* + * enable reception of multicast packets + */ + mreq.imr_multiaddr = iaddr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(inter_list[i].fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + (char *)&mreq, sizeof(mreq)) == -1) + netsyslog(LOG_ERR, + "setsockopt IP_ADD_MEMBERSHIP fails: %m for %x / %x (%s)", + mreq.imr_multiaddr.s_addr, + mreq.imr_interface.s_addr, inet_ntoa(iaddr)); + inter_list[i].flags |= INT_MULTICAST; + inter_list[i].num_mcast++; + if (i >= ninterfaces) + ninterfaces = i+1; + + add_addr_to_list(&addr, i); + break; + +#ifdef HAVE_IPV6 + case AF_INET6 : + + iaddr6 = ((struct sockaddr_in6*)&addr)->sin6_addr; + if (!IN6_IS_ADDR_MULTICAST(&iaddr6)) { + netsyslog(LOG_ERR, + "address %s not IPv6 multicast address", + stoa(&addr)); + return; + } + for (i = nwilds; i < ninterfaces; i++) { + /* Be sure it's the correct family */ + if(inter_list[i].sin.ss_family != AF_INET6) + continue; + /* Already have this address */ + if (SOCKCMP(&inter_list[i].sin, &addr)) + return; + /* found a free slot */ + if (SOCKNUL(&inter_list[i].sin) && + inter_list[i].fd <= 0 && inter_list[i].bfd <= 0 && + inter_list[i].flags == 0) + break; + } + sin6p = (struct sockaddr_in6*)&inter_list[i].sin; + memset((char *)&mreq6, 0, sizeof(mreq6)); + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + sin6p->sin6_family = AF_INET6; + sin6p->sin6_addr = iaddr6; + sin6p->sin6_port = htons(NTP_PORT); + + /* + * Try opening a socket for the specified class D address. This + * works under SunOS 4.x, but not OSF1 .. :-( + */ + set_reuseaddr(1); + s = open_socket((struct sockaddr_storage*)sin6p, 0, 1); + set_reuseaddr(0); + if(s == INVALID_SOCKET){ + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + if (wildipv6 >= 0) { + i = wildipv6; + /* HACK ! -- stuff in an address */ + inter_list[i].bcast = addr; + netsyslog(LOG_ERR, + "...multicast address %s using wildcard socket", + stoa(&addr)); + } else { + netsyslog(LOG_ERR, + "No wildcard socket available to use for address %s", + stoa(&addr)); + return; + } + } else { + inter_list[i].fd = s; + inter_list[i].bfd = INVALID_SOCKET; + (void)strncpy(inter_list[i].name, "multicast", + sizeof(inter_list[i].name)); + memset(&(((struct sockaddr_in6*)&inter_list[i].mask)->sin6_addr), 1, sizeof(struct in6_addr)); +#if defined (HAVE_IO_COMPLETION_PORT) + io_completion_port_add_socket(inter_list[i].fd, &inter_list[i]); +#endif + } + + /* + * enable reception of multicast packets + */ + mreq6.ipv6mr_multiaddr = iaddr6; + mreq6.ipv6mr_interface = 0; + if(setsockopt(inter_list[i].fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, + (char *)&mreq6, sizeof(mreq6)) == -1) + netsyslog(LOG_ERR, + "setsockopt IPV6_JOIN_GROUP fails: %m on interface %d(%s)", + mreq6.ipv6mr_interface, stoa(&addr)); + inter_list[i].flags |= INT_MULTICAST; + inter_list[i].num_mcast++; + if(i >= ninterfaces) + ninterfaces = i+1; + + add_addr_to_list(&addr, i); + break; +#endif /* HAVE_IPV6 */ + } + +#ifdef DEBUG + if (debug) + printf("io_multicast_add %s\n", stoa(&addr)); +#endif +#else /* MCAST */ + netsyslog(LOG_ERR, + "cannot add multicast address %s as no MCAST support", + stoa(&addr)); +#endif /* MCAST */ +} + +/* + * io_unsetbclient - close the broadcast client sockets + */ +void +io_unsetbclient(void) +{ + int i; + + for (i = nwilds; i < ninterfaces; i++) + { + if (!(inter_list[i].flags & INT_BCASTOPEN)) + continue; + close_socket(inter_list[i].bfd); + inter_list[i].bfd = INVALID_SOCKET; + inter_list[i].flags &= ~INT_BCASTOPEN; + } +} + + +/* + * io_multicast_del() - delete multicast group address + */ +void +io_multicast_del( + struct sockaddr_storage addr + ) +{ +#ifdef MCAST + int i; + struct ip_mreq mreq; + u_int32 haddr; + +#ifdef HAVE_IPV6 + struct ipv6_mreq mreq6; + struct in6_addr haddr6; +#endif /* HAVE_IPV6 */ + + switch (addr.ss_family) + { + case AF_INET : + + haddr = ntohl(((struct sockaddr_in*)&addr)->sin_addr.s_addr); + + if (!IN_CLASSD(haddr)) + { + netsyslog(LOG_ERR, + "invalid multicast address %s", stoa(&addr)); + return; + } + + /* + * Disable reception of multicast packets + */ + mreq.imr_multiaddr = ((struct sockaddr_in*)&addr)->sin_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + for (i = 0; i < ninterfaces; i++) + { + /* Be sure it's the correct family */ + if (inter_list[i].sin.ss_family != AF_INET) + continue; + if (!(inter_list[i].flags & INT_MULTICAST)) + continue; + if (!(inter_list[i].fd < 0)) + continue; + if (!SOCKCMP(&addr, &inter_list[i].sin)) + continue; + if (i != wildipv4) + { + /* we have an explicit fd, so we can close it */ + close_socket(inter_list[i].fd); + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + inter_list[i].fd = INVALID_SOCKET; + inter_list[i].bfd = INVALID_SOCKET; + } + else + { + /* We are sharing "any address" port :-( Don't close it! */ + if (setsockopt(inter_list[i].fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + (char *)&mreq, sizeof(mreq)) == -1) + netsyslog(LOG_ERR, "setsockopt IP_DROP_MEMBERSHIP fails on address: %s %m", + stoa(&addr)); + inter_list[i].num_mcast--; + /* If there are none left negate the Multicast flag */ + if(inter_list[i].num_mcast == 0) + inter_list[i].flags &= ~INT_MULTICAST; + } + } + break; + +#ifdef HAVE_IPV6 + case AF_INET6 : + haddr6 = ((struct sockaddr_in6*)&addr)->sin6_addr; + + if (!IN6_IS_ADDR_MULTICAST(&haddr6)) + { + netsyslog(LOG_ERR, + "invalid multicast address %s", stoa(&addr)); + return; + } + + /* + * Disable reception of multicast packets + */ + mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&addr)->sin6_addr; + mreq6.ipv6mr_interface = 0; + for (i = 0; i < ninterfaces; i++) + { + /* Be sure it's the correct family */ + if (inter_list[i].sin.ss_family != AF_INET6) + continue; + if (!(inter_list[i].flags & INT_MULTICAST)) + continue; + if (!(inter_list[i].fd < 0)) + continue; + if (!SOCKCMP(&addr, &inter_list[i].sin)) + continue; + if (i != wildipv6) + { + /* we have an explicit fd, so we can close it */ + close_socket(inter_list[i].fd); + memset((char *)&inter_list[i], 0, sizeof(struct interface)); + inter_list[i].fd = INVALID_SOCKET; + inter_list[i].bfd = INVALID_SOCKET; + } + else + { + /* We are sharing "any address" port :-( Don't close it! */ + if (setsockopt(inter_list[i].fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + (char *)&mreq6, sizeof(mreq6)) == -1) + netsyslog(LOG_ERR, "setsockopt IP_DROP_MEMBERSHIP fails on address %s: %m", + stoa(&addr)); + /* If there are none left negate the Multicast flag */ + if(inter_list[i].num_mcast == 0) + inter_list[i].flags &= ~INT_MULTICAST; + } + } + break; +#endif /* HAVE_IPV6 */ + }/* switch */ + delete_addr_from_list(&addr); + +#else /* not MCAST */ + netsyslog(LOG_ERR, "this function requires multicast kernel"); +#endif /* not MCAST */ +} + + +/* + * open_socket - open a socket, returning the file descriptor + */ + +static SOCKET +open_socket( + struct sockaddr_storage *addr, + int flags, + int turn_off_reuse + ) +{ + int errval; + SOCKET fd; + int on = 1, off = 0; +#if defined(IPTOS_LOWDELAY) && defined(IPPROTO_IP) && defined(IP_TOS) + int tos; +#endif /* IPTOS_LOWDELAY && IPPROTO_IP && IP_TOS */ + + if ((addr->ss_family == AF_INET6) && (isc_net_probeipv6() != ISC_R_SUCCESS)) + return (INVALID_SOCKET); + + /* create a datagram (UDP) socket */ +#ifndef SYS_WINNT + if ( (fd = socket(addr->ss_family, SOCK_DGRAM, 0)) < 0) { + errval = errno; + if(addr->ss_family == AF_INET) + netsyslog(LOG_ERR, "socket(AF_INET, SOCK_DGRAM, 0) failed on address %s: %m", + stoa(addr)); + else if(addr->ss_family == AF_INET6) + netsyslog(LOG_ERR, "socket(AF_INET6, SOCK_DGRAM, 0) failed on address %s: %m", + stoa(addr)); + if (errval == EPROTONOSUPPORT || errval == EAFNOSUPPORT || + errval == EPFNOSUPPORT) + return (INVALID_SOCKET); + exit(1); + /*NOTREACHED*/ + } +#else + if ( (fd = socket(addr->ss_family, SOCK_DGRAM, 0)) == INVALID_SOCKET) { + errval = WSAGetLastError(); + if(addr->ss_family == AF_INET) + netsyslog(LOG_ERR, "socket(AF_INET, SOCK_DGRAM, 0) failed on address %s: %m", + stoa(addr)); + else if(addr->ss_family == AF_INET6) + netsyslog(LOG_ERR, "socket(AF_INET6, SOCK_DGRAM, 0) failed on address %s: %m", + stoa(addr)); + if (errval == WSAEPROTONOSUPPORT || errval == WSAEAFNOSUPPORT || + errval == WSAEPFNOSUPPORT) + return (INVALID_SOCKET); + exit(1); + /*NOTREACHED*/ + } + if (connection_reset_fix(fd) != ISC_R_SUCCESS) { + netsyslog(LOG_ERR, "connection_reset_fix(fd) failed on address %s: %m", + stoa(addr)); + } + +#endif /* SYS_WINNT */ + + /* set SO_REUSEADDR since we will be binding the same port + number on each interface */ + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof(on))) + { + netsyslog(LOG_ERR, "setsockopt SO_REUSEADDR on fails on address %s: %m", + stoa(addr)); + } + +#if defined(IPTOS_LOWDELAY) && defined(IPPROTO_IP) && defined(IP_TOS) + /* set IP_TOS to minimize packet delay */ + tos = IPTOS_LOWDELAY; + if (addr->ss_family == AF_INET) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(tos)) < 0) + { + netsyslog(LOG_ERR, "setsockopt IPTOS_LOWDELAY on fails on address %s: %m", + stoa(addr)); + } + +#if defined(IPV6_V6ONLY) + if (addr->ss_family == AF_INET6) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, + (char*)&on, sizeof(on))) + { + netsyslog(LOG_ERR, "setsockopt IPV6_V6ONLY on fails on address %s: %m", + stoa(addr)); + } +#else /* IPV6_V6ONLY */ +#if defined(IPV6_BINDV6ONLY) + if (addr->ss_family == AF_INET6) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_BINDV6ONLY, + (char*)&on, sizeof(on))) + { + netsyslog(LOG_ERR, + "setsockopt IPV6_BINDV6ONLY on fails on address %s: %m", + stoa(addr)); + } +#endif /* IPV6_BINDV6ONLY */ +#endif /* IPV6_V6ONLY */ + +#endif /* IPTOS_LOWDELAY && IPPROTO_IP && IP_TOS */ + + /* + * bind the local address. + */ + if (bind(fd, (struct sockaddr *)addr, SOCKLEN(addr)) < 0) { + char buff[160]; + + if(addr->ss_family == AF_INET) + sprintf(buff, + "bind() fd %d, family %d, port %d, addr %s, in_classd=%d flags=%d fails: %%m", + fd, addr->ss_family, (int)ntohs(((struct sockaddr_in*)addr)->sin_port), + stoa(addr), + IN_CLASSD(ntohl(((struct sockaddr_in*)addr)->sin_addr.s_addr)), flags); + else if(addr->ss_family == AF_INET6) + sprintf(buff, + "bind() fd %d, family %d, port %d, addr %s, in6_is_addr_multicast=%d flags=%d fails: %%m", + fd, addr->ss_family, (int)ntohs(((struct sockaddr_in6*)addr)->sin6_port), + stoa(addr), + IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)addr)->sin6_addr), flags); + else return INVALID_SOCKET; + + netsyslog(LOG_ERR, buff); + closesocket(fd); + + /* + * soft fail if opening a multicast address + */ + if(addr->ss_family == AF_INET){ + if(IN_CLASSD(ntohl(((struct sockaddr_in*)addr)->sin_addr.s_addr))) + return (INVALID_SOCKET); + } + else { + if(IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)addr)->sin6_addr)) + return (INVALID_SOCKET); + } +#if 0 + exit(1); +#else + return INVALID_SOCKET; +#endif + } +#ifdef DEBUG + if (debug) + printf("bind() fd %d, family %d, port %d, addr %s, flags=%d\n", + fd, + addr->ss_family, + (int)ntohs(((struct sockaddr_in*)addr)->sin_port), + stoa(addr), + flags); +#endif + + /* + * I/O Completion Ports don't care about the select and FD_SET + */ +#ifndef HAVE_IO_COMPLETION_PORT + if (fd > maxactivefd) + maxactivefd = fd; + FD_SET(fd, &activefds); +#endif + add_socket_to_list(fd); + /* + * set non-blocking, + */ + +#ifdef USE_FIONBIO + /* in vxWorks we use FIONBIO, but the others are defined for old systems, so + * all hell breaks loose if we leave them defined + */ +#undef O_NONBLOCK +#undef FNDELAY +#undef O_NDELAY +#endif + +#if defined(O_NONBLOCK) /* POSIX */ + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + { + netsyslog(LOG_ERR, "fcntl(O_NONBLOCK) fails on address %s: %m", + stoa(addr)); + exit(1); + /*NOTREACHED*/ + } +#elif defined(FNDELAY) + if (fcntl(fd, F_SETFL, FNDELAY) < 0) + { + netsyslog(LOG_ERR, "fcntl(FNDELAY) fails on address %s: %m", + stoa(addr)); + exit(1); + /*NOTREACHED*/ + } +#elif defined(O_NDELAY) /* generally the same as FNDELAY */ + if (fcntl(fd, F_SETFL, O_NDELAY) < 0) + { + netsyslog(LOG_ERR, "fcntl(O_NDELAY) fails on address %s: %m", + stoa(addr)); + exit(1); + /*NOTREACHED*/ + } +#elif defined(FIONBIO) +# if defined(VMS) + if (ioctl(fd,FIONBIO,&on) < 0) +# elif defined(SYS_WINNT) + if (ioctlsocket(fd,FIONBIO,(u_long *) &on) == SOCKET_ERROR) +# else + if (ioctl(fd,FIONBIO,&on) < 0) +# endif + { + netsyslog(LOG_ERR, "ioctl(FIONBIO) fails on address %s: %m", + stoa(addr)); + exit(1); + /*NOTREACHED*/ + } +#elif defined(FIOSNBIO) + if (ioctl(fd,FIOSNBIO,&on) < 0) + { + netsyslog(LOG_ERR, "ioctl(FIOSNBIO) fails on address %s: %m", + stoa(addr)); + exit(1); + /*NOTREACHED*/ + } +#else +# include "Bletch: Need non-blocking I/O!" +#endif + +#ifdef HAVE_SIGNALED_IO + init_socket_sig(fd); +#endif /* not HAVE_SIGNALED_IO */ + + /* + * Turn off the SO_REUSEADDR socket option. It apparently + * causes heartburn on systems with multicast IP installed. + * On normal systems it only gets looked at when the address + * is being bound anyway.. + */ + if (turn_off_reuse) + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (char *)&off, sizeof(off))) + { + netsyslog(LOG_ERR, "setsockopt SO_REUSEADDR off fails on address %s: %m", + stoa(addr)); + } + +#ifdef SO_BROADCAST + /* if this interface can support broadcast, set SO_BROADCAST */ + if (flags & INT_BROADCAST) + { + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, + (char *)&on, sizeof(on))) + { + netsyslog(LOG_ERR, "setsockopt(SO_BROADCAST) on address %s: %m", + stoa(addr)); + } + } +#endif /* SO_BROADCAST */ + +#if !defined(SYS_WINNT) && !defined(VMS) +# ifdef DEBUG + if (debug > 1) + printf("flags for fd %d: 0%o\n", fd, + fcntl(fd, F_GETFL, 0)); +# endif +#endif /* SYS_WINNT || VMS */ + + return fd; +} + + +/* + * close_socket - close a socket and remove from the activefd list + */ +static void +close_socket( + SOCKET fd + ) +{ + SOCKET i, newmax; + + (void) closesocket(fd); + + /* + * I/O Completion Ports don't care about select and fd_set + */ +#ifndef HAVE_IO_COMPLETION_PORT + FD_CLR( (u_int) fd, &activefds); + + if (fd == maxactivefd) { + newmax = 0; + for (i = 0; i < maxactivefd; i++) + if (FD_ISSET(i, &activefds)) + newmax = i; + maxactivefd = newmax; + } +#endif + delete_socket_from_list(fd); + +} + + +/* + * close_file - close a file and remove from the activefd list + * added 1/31/1997 Greg Schueman for Windows NT portability + */ +#ifdef REFCLOCK +static void +close_file( + SOCKET fd + ) +{ + int i, newmax; + + (void) close(fd); + /* + * I/O Completion Ports don't care about select and fd_set + */ +#ifndef HAVE_IO_COMPLETION_PORT + FD_CLR( (u_int) fd, &activefds); + + if (fd == maxactivefd) { + newmax = 0; + for (i = 0; i < maxactivefd; i++) + if (FD_ISSET(i, &activefds)) + newmax = i; + maxactivefd = newmax; + } +#endif + delete_socket_from_list(fd); + +} +#endif + + +/* XXX ELIMINATE sendpkt similar in ntpq.c, ntpdc.c, ntp_io.c, ntptrace.c */ +/* + * sendpkt - send a packet to the specified destination. Maintain a + * send error cache so that only the first consecutive error for a + * destination is logged. + */ +void +sendpkt( + struct sockaddr_storage *dest, + struct interface *inter, + int ttl, + struct pkt *pkt, + int len + ) +{ + int cc, slot; +#ifdef SYS_WINNT + DWORD err; +#endif /* SYS_WINNT */ + + /* + * Send error caches. Empty slots have port == 0 + * Set ERRORCACHESIZE to 0 to disable + */ + struct cache { + u_short port; + struct in_addr addr; + }; + +#ifdef HAVE_IPV6 + struct cache6 { + u_short port; + struct in6_addr addr; + }; +#endif /* HAVE_IPV6 */ + +#ifndef ERRORCACHESIZE +#define ERRORCACHESIZE 8 +#endif +#if ERRORCACHESIZE > 0 + static struct cache badaddrs[ERRORCACHESIZE]; +#ifdef HAVE_IPV6 + static struct cache6 badaddrs6[ERRORCACHESIZE]; +#endif /* HAVE_IPV6 */ +#else +#define badaddrs ((struct cache *)0) /* Only used in empty loops! */ +#ifdef HAVE_IPV6 +#define badaddrs6 ((struct cache6 *)0) /* Only used in empty loops! */ +#endif /* HAVE_IPV6 */ +#endif +#ifdef DEBUG + if (debug > 1) + printf("%ssendpkt(fd=%d dst=%s, src=%s, ttl=%d, len=%d)\n", + (ttl >= 0) ? "\tMCAST\t*****" : "", + inter->fd, stoa(dest), + stoa(&inter->sin), ttl, len); +#endif + +#ifdef MCAST + + switch (inter->sin.ss_family) { + + case AF_INET : + + /* + * for the moment we use the bcast option to set multicast ttl + */ + if (ttl > 0 && ttl != inter->last_ttl) { + + /* + * set the multicast ttl for outgoing packets + */ + if (setsockopt(inter->fd, IPPROTO_IP, IP_MULTICAST_TTL, + (char *) &ttl, sizeof(ttl)) != 0) { + netsyslog(LOG_ERR, "setsockopt IP_MULTICAST_TTL fails on address %s: %m", + stoa(&inter->sin)); + } + else + inter->last_ttl = ttl; + } + break; + +#ifdef HAVE_IPV6 + case AF_INET6 : + + /* + * for the moment we use the bcast option to set + * multicast max hops + */ + if (ttl > 0 && ttl != inter->last_ttl) { + + /* + * set the multicast ttl for outgoing packets + */ + if (setsockopt(inter->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &ttl, sizeof(ttl)) == -1) + netsyslog(LOG_ERR, "setsockopt IP_MULTICAST_TTL fails on address %s: %m", + stoa(&inter->sin)); + else + inter->last_ttl = ttl; + } + break; +#endif /* HAVE_IPV6 */ + + default : + exit(1); + + } + + +#endif /* MCAST */ + + for (slot = ERRORCACHESIZE; --slot >= 0; ) + if(dest->ss_family == AF_INET) { + if (badaddrs[slot].port == ((struct sockaddr_in*)dest)->sin_port && + badaddrs[slot].addr.s_addr == ((struct sockaddr_in*)dest)->sin_addr.s_addr) + break; + } +#ifdef HAVE_IPV6 + else if (dest->ss_family == AF_INET6) { + if (badaddrs6[slot].port == ((struct sockaddr_in6*)dest)->sin6_port && + badaddrs6[slot].addr.s6_addr == ((struct sockaddr_in6*)dest)->sin6_addr.s6_addr) + break; + } +#endif /* HAVE_IPV6 */ + else exit(1); /* address family not supported yet */ + +#if defined(HAVE_IO_COMPLETION_PORT) + err = io_completion_port_sendto(inter, pkt, len, dest); + if (err != ERROR_SUCCESS) +#else +#ifdef SIM + cc = srvr_rply(&ntp_node, dest, inter, pkt); +#else /* SIM */ + cc = sendto(inter->fd, (char *)pkt, (unsigned int)len, 0, (struct sockaddr *)dest, + SOCKLEN(dest)); +#endif /* SIM */ + if (cc == -1) +#endif + { + inter->notsent++; + packets_notsent++; +#if defined(HAVE_IO_COMPLETION_PORT) + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAENOBUFS && slot < 0) +#else + if (errno != EWOULDBLOCK && errno != ENOBUFS && slot < 0) +#endif + { + /* + * Remember this, if there's an empty slot + */ + switch (dest->ss_family) { + + case AF_INET : + + for (slot = ERRORCACHESIZE; --slot >= 0; ) + if (badaddrs[slot].port == 0) + { + badaddrs[slot].port = SRCPORT(dest); + badaddrs[slot].addr = ((struct sockaddr_in*)dest)->sin_addr; + break; + } + break; + +#ifdef HAVE_IPV6 + case AF_INET6 : + + for (slot = ERRORCACHESIZE; --slot >= 0; ) + if (badaddrs6[slot].port == 0) + { + badaddrs6[slot].port = SRCPORT(dest); + badaddrs6[slot].addr = ((struct sockaddr_in6*)dest)->sin6_addr; + break; + } + break; +#endif /* HAVE_IPV6 */ + + default : + exit(1); + } + + netsyslog(LOG_ERR, "sendto(%s): %m", stoa(dest)); + } + } + else + { + inter->sent++; + packets_sent++; + /* + * He's not bad any more + */ + if (slot >= 0) + { + netsyslog(LOG_INFO, "Connection re-established to %s", stoa(dest)); + switch (dest->ss_family) { + case AF_INET : + badaddrs[slot].port = 0; + break; +#ifdef HAVE_IPV6 + case AF_INET6 : + badaddrs6[slot].port = 0; + break; +#endif /* HAVE_IPV6 */ + } + } + } +} + +#if !defined(HAVE_IO_COMPLETION_PORT) +/* + * fdbits - generate ascii representation of fd_set (FAU debug support) + * HFDF format - highest fd first. + */ +static char * +fdbits( + int count, + fd_set *set + ) +{ + static char buffer[256]; + char * buf = buffer; + + count = (count < 256) ? count : 255; + + while (count >= 0) + { + *buf++ = FD_ISSET(count, set) ? '#' : '-'; + count--; + } + *buf = '\0'; + + return buffer; +} + +/* + * input_handler - receive packets asynchronously + */ +void +input_handler( + l_fp *cts + ) +{ + register int i, n; + register struct recvbuf *rb; + register int doing; + register SOCKET fd; + struct timeval tvzero; + int fromlen; + l_fp ts; /* Timestamp at BOselect() gob */ + l_fp ts_e; /* Timestamp at EOselect() gob */ + fd_set fds; + int select_count = 0; + static int handler_count = 0; + + ++handler_count; + if (handler_count != 1) + msyslog(LOG_ERR, "input_handler: handler_count is %d!", handler_count); + handler_calls++; + ts = *cts; + + for (;;) + { + /* + * Do a poll to see who has data + */ + + fds = activefds; + tvzero.tv_sec = tvzero.tv_usec = 0; + + /* + * If we have something to do, freeze a timestamp. + * See below for the other cases (nothing (left) to do or error) + */ + while (0 < (n = select(maxactivefd+1, &fds, (fd_set *)0, (fd_set *)0, &tvzero))) + { + ++select_count; + ++handler_pkts; + +#ifdef REFCLOCK + /* + * Check out the reference clocks first, if any + */ + if (refio != 0) + { + register struct refclockio *rp; + + for (rp = refio; rp != 0 && n > 0; rp = rp->next) + { + fd = rp->fd; + if (FD_ISSET(fd, &fds)) + { + n--; + if (free_recvbuffs() == 0) + { + char buf[RX_BUFF_SIZE]; + + (void) read(fd, buf, sizeof buf); + packets_dropped++; + goto select_again; + } + + rb = get_free_recv_buffer(); + + i = (rp->datalen == 0 + || rp->datalen > sizeof(rb->recv_space)) + ? sizeof(rb->recv_space) : rp->datalen; + rb->recv_length = + read(fd, (char *)&rb->recv_space, (unsigned)i); + + if (rb->recv_length == -1) + { + netsyslog(LOG_ERR, "clock read fd %d: %m", fd); + freerecvbuf(rb); + goto select_again; + } + + /* + * Got one. Mark how + * and when it got here, + * put it on the full + * list and do + * bookkeeping. + */ + rb->recv_srcclock = rp->srcclock; + rb->dstadr = 0; + rb->fd = fd; + rb->recv_time = ts; + rb->receiver = rp->clock_recv; + + if (rp->io_input) + { + /* + * have direct + * input routine + * for refclocks + */ + if (rp->io_input(rb) == 0) + { + /* + * data + * was + * consumed + * - + * nothing + * to + * pass + * up + * into + * block + * input + * machine + */ + freerecvbuf(rb); +#if 1 + goto select_again; +#else + continue; +#endif + } + } + + add_full_recv_buffer(rb); + + rp->recvcount++; + packets_received++; + } + } + } +#endif /* REFCLOCK */ + + /* + * Loop through the interfaces looking for data + * to read. + */ + for (i = ninterfaces - 1; (i >= 0) && (n > 0); i--) + { + for (doing = 0; (doing < 2) && (n > 0); doing++) + { + if (doing == 0) + { + fd = inter_list[i].fd; + } + else + { + if (!(inter_list[i].flags & INT_BCASTOPEN)) + break; + fd = inter_list[i].bfd; + } + if (fd < 0) continue; + if (FD_ISSET(fd, &fds)) + { + n--; + + /* + * Get a buffer and read + * the frame. If we + * haven't got a buffer, + * or this is received + * on the wild card + * socket, just dump the + * packet. + */ + if ( +#ifdef UDP_WILDCARD_DELIVERY + /* + * these guys manage to put properly addressed + * packets into the wildcard queue + */ + (free_recvbuffs() == 0) +#else + ((i == wildipv4) || (i == wildipv6)|| + (free_recvbuffs() == 0)) +#endif + ) + { + char buf[RX_BUFF_SIZE]; + struct sockaddr_storage from; + + fromlen = sizeof from; + (void) recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&from, &fromlen); +#ifdef DEBUG + if (debug) + printf("%s on %d(%lu) fd=%d from %s\n", + (i) ? "drop" : "ignore", + i, free_recvbuffs(), fd, + stoa(&from)); +#endif + if (i == wildipv4 || i == wildipv6) + packets_ignored++; + else + packets_dropped++; + goto select_again; + } + + rb = get_free_recv_buffer(); + + fromlen = sizeof(struct sockaddr_storage); + rb->recv_length = recvfrom(fd, + (char *)&rb->recv_space, + sizeof(rb->recv_space), 0, + (struct sockaddr *)&rb->recv_srcadr, + &fromlen); + if (rb->recv_length == 0 +#ifdef EWOULDBLOCK + || errno==EWOULDBLOCK +#endif +#ifdef EAGAIN + || errno==EAGAIN +#endif + ) { + freerecvbuf(rb); + continue; + } + else if (rb->recv_length < 0) + { + netsyslog(LOG_ERR, "recvfrom(%s) fd=%d: %m", + stoa(&rb->recv_srcadr), fd); +#ifdef DEBUG + if (debug) + printf("input_handler: fd=%d dropped (bad recvfrom)\n", fd); +#endif + freerecvbuf(rb); + continue; + } +#ifdef DEBUG + if (debug > 2) { + if(rb->recv_srcadr.ss_family == AF_INET) + printf("input_handler: if=%d fd=%d length %d from %08lx %s\n", + i, fd, rb->recv_length, + (u_long)ntohl(((struct sockaddr_in*)&rb->recv_srcadr)->sin_addr.s_addr) & + 0x00000000ffffffff, + stoa(&rb->recv_srcadr)); + else + printf("input_handler: if=%d fd=%d length %d from %s\n", + i, fd, rb->recv_length, + stoa(&rb->recv_srcadr)); + } +#endif + + /* + * Got one. Mark how and when it got here, + * put it on the full list and do bookkeeping. + */ + rb->dstadr = &inter_list[i]; + rb->fd = fd; + rb->recv_time = ts; + rb->receiver = receive; + + add_full_recv_buffer(rb); + + inter_list[i].received++; + packets_received++; + goto select_again; + } + /* Check more interfaces */ + } + } + select_again:; + /* + * Done everything from that select. Poll again. + */ + } + + /* + * If nothing more to do, try again. + * If nothing to do, just return. + * If an error occurred, complain and return. + */ + if (n == 0) + { + if (select_count == 0) /* We really had nothing to do */ + { + if (debug) + netsyslog(LOG_DEBUG, "input_handler: select() returned 0"); + --handler_count; + return; + } + /* We've done our work */ + get_systime(&ts_e); + /* + * (ts_e - ts) is the amount of time we spent + * processing this gob of file descriptors. Log + * it. + */ + L_SUB(&ts_e, &ts); + if (debug > 3) + netsyslog(LOG_INFO, "input_handler: Processed a gob of fd's in %s msec", lfptoms(&ts_e, 6)); + + /* just bail. */ + --handler_count; + return; + } + else if (n == -1) + { + int err = errno; + + /* + * extended FAU debugging output + */ + if (err != EINTR) + netsyslog(LOG_ERR, + "select(%d, %s, 0L, 0L, &0.0) error: %m", + maxactivefd+1, + fdbits(maxactivefd, &activefds)); + if (err == EBADF) { + int j, b; + + fds = activefds; + for (j = 0; j <= maxactivefd; j++) + if ( + (FD_ISSET(j, &fds) && (read(j, &b, 0) == -1)) + ) + netsyslog(LOG_ERR, "Bad file descriptor %d", j); + } + --handler_count; + return; + } + } + msyslog(LOG_ERR, "input_handler: fell out of infinite for(;;) loop!"); + --handler_count; + return; +} + +#endif +/* + * findinterface - find interface corresponding to address + */ +struct interface * +findinterface( + struct sockaddr_storage *addr + ) +{ + SOCKET s; + int rtn, i; + struct sockaddr_storage saddr; + int saddrlen = SOCKLEN(addr); + /* + * This is considerably hoke. We open a socket, connect to it + * and slap a getsockname() on it. If anything breaks, as it + * probably will in some j-random knockoff, we just return the + * wildcard interface. + */ + memset(&saddr, 0, sizeof(saddr)); + saddr.ss_family = addr->ss_family; + if(addr->ss_family == AF_INET) + memcpy(&((struct sockaddr_in*)&saddr)->sin_addr, &((struct sockaddr_in*)addr)->sin_addr, sizeof(struct in_addr)); + else if(addr->ss_family == AF_INET6) + memcpy(&((struct sockaddr_in6*)&saddr)->sin6_addr, &((struct sockaddr_in6*)addr)->sin6_addr, sizeof(struct in6_addr)); + ((struct sockaddr_in*)&saddr)->sin_port = htons(2000); + s = socket(addr->ss_family, SOCK_DGRAM, 0); + if (s == INVALID_SOCKET) + return ANY_INTERFACE_CHOOSE(addr); + + rtn = connect(s, (struct sockaddr *)&saddr, SOCKLEN(&saddr)); +#ifndef SYS_WINNT + if (rtn < 0) +#else + if (rtn == SOCKET_ERROR) +#endif + { + closesocket(s); + return ANY_INTERFACE_CHOOSE(addr); + } + + rtn = getsockname(s, (struct sockaddr *)&saddr, &saddrlen); + closesocket(s); +#ifndef SYS_WINNT + if (rtn < 0) +#else + if (rtn == SOCKET_ERROR) +#endif + return ANY_INTERFACE_CHOOSE(addr); + + for (i = 0; i < ninterfaces; i++) { + /* + * First look if is the the correct family + */ + if(inter_list[i].sin.ss_family != saddr.ss_family) + continue; + /* + * We match the unicast address only. + */ + if (SOCKCMP(&inter_list[i].sin, &saddr)) + return (&inter_list[i]); + } + return ANY_INTERFACE_CHOOSE(addr); +} + +/* + * findbcastinter - find broadcast interface corresponding to address + */ +struct interface * +findbcastinter( + struct sockaddr_storage *addr + ) +{ +#if !defined(MPE) && (defined(SIOCGIFCONF) || defined(SYS_WINNT)) + register int i; + + i = find_addr_in_list(addr); + if(i >= 0) + return (&inter_list[i]); + + for (i = 0; i < ninterfaces; i++) { + /* + * First look if this is the correct family + */ + if(inter_list[i].sin.ss_family != addr->ss_family) + continue; + /* + * We match only those interfaces marked as + * broadcastable and either the explicit broadcast + * address or the network portion of the IP address. + * Sloppy. + */ + if (!(inter_list[i].flags & INT_BROADCAST)) + continue; + if(addr->ss_family == AF_INET) { + if (SOCKCMP(&inter_list[i].bcast, addr)) + return (&inter_list[i]); + if ((NSRCADR(&inter_list[i].sin) & + NSRCADR(&inter_list[i].mask)) == (NSRCADR(addr) & + NSRCADR(&inter_list[i].mask))) + return (&inter_list[i]); + } + else if(addr->ss_family == AF_INET6) { + if (SOCKCMP(&inter_list[i].bcast, addr)) + return (&inter_list[i]); + if (SOCKCMP(netof(&inter_list[i].sin), netof(addr))) + return (&inter_list[i]); + } + } +#endif /* SIOCGIFCONF */ + return ANY_INTERFACE_CHOOSE(addr); +} + + +/* + * io_clr_stats - clear I/O module statistics + */ +void +io_clr_stats(void) +{ + packets_dropped = 0; + packets_ignored = 0; + packets_received = 0; + packets_sent = 0; + packets_notsent = 0; + + handler_calls = 0; + handler_pkts = 0; + io_timereset = current_time; +} + + +#ifdef REFCLOCK +/* + * This is a hack so that I don't have to fool with these ioctls in the + * pps driver ... we are already non-blocking and turn on SIGIO thru + * another mechanisim + */ +int +io_addclock_simple( + struct refclockio *rio + ) +{ + BLOCKIO(); + /* + * Stuff the I/O structure in the list and mark the descriptor + * in use. There is a harmless (I hope) race condition here. + */ + rio->next = refio; + refio = rio; + + /* + * I/O Completion Ports don't care about select and fd_set + */ +#ifndef HAVE_IO_COMPLETION_PORT + if (rio->fd > maxactivefd) + maxactivefd = rio->fd; + FD_SET(rio->fd, &activefds); +#endif + UNBLOCKIO(); + return 1; +} + +/* + * io_addclock - add a reference clock to the list and arrange that we + * get SIGIO interrupts from it. + */ +int +io_addclock( + struct refclockio *rio + ) +{ + BLOCKIO(); + /* + * Stuff the I/O structure in the list and mark the descriptor + * in use. There is a harmless (I hope) race condition here. + */ + rio->next = refio; + refio = rio; + +# ifdef HAVE_SIGNALED_IO + if (init_clock_sig(rio)) + { + refio = rio->next; + UNBLOCKIO(); + return 0; + } +# elif defined(HAVE_IO_COMPLETION_PORT) + if (io_completion_port_add_clock_io(rio)) + { + add_socket_to_list(rio->fd); + refio = rio->next; + UNBLOCKIO(); + return 0; + } +# endif + + /* + * I/O Completion Ports don't care about select and fd_set + */ +#ifndef HAVE_IO_COMPLETION_PORT + if (rio->fd > maxactivefd) + maxactivefd = rio->fd; + FD_SET(rio->fd, &activefds); +#endif + UNBLOCKIO(); + return 1; +} + +/* + * io_closeclock - close the clock in the I/O structure given + */ +void +io_closeclock( + struct refclockio *rio + ) +{ + /* + * Remove structure from the list + */ + if (refio == rio) + { + refio = rio->next; + } + else + { + register struct refclockio *rp; + + for (rp = refio; rp != 0; rp = rp->next) + if (rp->next == rio) + { + rp->next = rio->next; + break; + } + + if (rp == 0) + { + /* + * Internal error. Report it. + */ + msyslog(LOG_ERR, + "internal error: refclockio structure not found"); + return; + } + } + + /* + * Close the descriptor. + */ + close_file(rio->fd); +} +#endif /* REFCLOCK */ + + /* + * I/O Completion Ports don't care about select and fd_set + */ +#ifndef HAVE_IO_COMPLETION_PORT +void +kill_asyncio( + int startfd + ) +{ + SOCKET i; + + BLOCKIO(); + for (i = startfd; i <= maxactivefd; i++) + (void)close_socket(i); +} +#else +/* + * On NT a SOCKET is an unsigned int so we cannot possibly keep it in + * an array. So we use one of the ISC_LIST functions to hold the + * socket value and use that when we want to enumerate it. + */ +void +kill_asyncio(int startfd) +{ + vsock_t *lsock; + vsock_t *next; + + BLOCKIO(); + + lsock = ISC_LIST_HEAD(sockets_list); + while (lsock != NULL) { + next = ISC_LIST_NEXT(lsock, link); + close_socket(lsock->fd); + lsock = next; + } + +} +#endif +/* + * Add and delete functions for the list of open sockets + */ +void +add_socket_to_list(SOCKET fd){ + vsock_t *lsock = malloc(sizeof(vsock_t)); + lsock->fd = fd; + + ISC_LIST_APPEND(sockets_list, lsock, link); +} +void +delete_socket_from_list(SOCKET fd) { + + vsock_t *next; + vsock_t *lsock = ISC_LIST_HEAD(sockets_list); + + while(lsock != NULL) { + next = ISC_LIST_NEXT(lsock, link); + if(lsock->fd == fd) { + ISC_LIST_DEQUEUE(sockets_list, lsock, link); + free(lsock); + break; + } + else + lsock = next; + } +} +void +add_addr_to_list(struct sockaddr_storage *addr, int if_index){ + remaddr_t *laddr = malloc(sizeof(remaddr_t)); + memcpy(&laddr->addr, addr, sizeof(addr)); + laddr->if_index = if_index; + + ISC_LIST_APPEND(remoteaddr_list, laddr, link); +#ifdef DEBUG + if (debug) + printf("Added addr %s to list of addresses\n", + stoa(addr)); +#endif + + +} +void +delete_addr_from_list(struct sockaddr_storage *addr) { + + remaddr_t *next; + remaddr_t *laddr = ISC_LIST_HEAD(remoteaddr_list); + + while(laddr != NULL) { + next = ISC_LIST_NEXT(laddr, link); + if(SOCKCMP(&laddr->addr, addr)) { + ISC_LIST_DEQUEUE(remoteaddr_list, laddr, link); + free(laddr); + break; + } + else + laddr = next; + } +#ifdef DEBUG + if (debug) + printf("Deleted addr %s from list of addresses\n", + stoa(addr)); +#endif +} +int +find_addr_in_list(struct sockaddr_storage *addr) { + + remaddr_t *next; + remaddr_t *laddr = ISC_LIST_HEAD(remoteaddr_list); +#ifdef DEBUG + if (debug) + printf("Finding addr %s in list of addresses\n", + stoa(addr)); +#endif + + while(laddr != NULL) { + next = ISC_LIST_NEXT(laddr, link); + if(SOCKCMP(&laddr->addr, addr)) { + return (laddr->if_index); + break; + } + else + laddr = next; + } + return (-1); /* Not found */ +} diff --git a/ntpd/ntp_loopfilter.c b/ntpd/ntp_loopfilter.c new file mode 100644 index 0000000..99d1cc4 --- /dev/null +++ b/ntpd/ntp_loopfilter.c @@ -0,0 +1,1000 @@ +/* + * ntp_loopfilter.c - implements the NTP loop filter algorithm + * + * ATTENTION: Get approval from Dave Mills on all changes to this file! + * + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#include <signal.h> +#include <setjmp.h> + +#if defined(VMS) && defined(VMS_LOCALUNIT) /*wjm*/ +#include "ntp_refclock.h" +#endif /* VMS */ + +#ifdef KERNEL_PLL +#include "ntp_syscall.h" +#endif /* KERNEL_PLL */ + +/* + * This is an implementation of the clock discipline algorithm described + * in UDel TR 97-4-3, as amended. It operates as an adaptive parameter, + * hybrid phase/frequency-lock loop. A number of sanity checks are + * included to protect against timewarps, timespikes and general mayhem. + * All units are in s and s/s, unless noted otherwise. + */ +#define CLOCK_MAX .128 /* default step threshold (s) */ +#define CLOCK_MINSTEP 900. /* default stepout threshold (s) */ +#define CLOCK_PANIC 1000. /* default panic threshold (s) */ +#define CLOCK_PHI 15e-6 /* max frequency error (s/s) */ +#define CLOCK_PLL 16. /* PLL loop gain */ +#define CLOCK_FLL 8. /* FLL loop gain */ +#define CLOCK_AVG 4. /* parameter averaging constant */ +#define CLOCK_ALLAN 1500. /* compromise Allan intercept (s) */ +#define CLOCK_DAY 86400. /* one day in seconds (s) */ +#define CLOCK_LIMIT 30 /* poll-adjust threshold */ +#define CLOCK_PGATE 4. /* poll-adjust gate */ +#define PPS_MAXAGE 120 /* kernel pps signal timeout (s) */ + +/* + * Clock discipline state machine. This is used to control the + * synchronization behavior during initialization and following a + * timewarp. + * + * State < max > max Comments + * ==================================================== + * NSET FREQ FREQ no ntp.drift + * + * FSET TSET if (allow) TSET, ntp.drift + * else FREQ + * + * TSET SYNC FREQ time set + * + * FREQ SYNC if (mu < 900) FREQ calculate frequency + * else if (allow) TSET + * else FREQ + * + * SYNC SYNC if (mu < 900) SYNC normal state + * else SPIK + * + * SPIK SYNC if (allow) TSET spike detector + * else FREQ + */ +#define S_NSET 0 /* clock never set */ +#define S_FSET 1 /* frequency set from the drift file */ +#define S_TSET 2 /* time set */ +#define S_FREQ 3 /* frequency mode */ +#define S_SYNC 4 /* clock synchronized */ +#define S_SPIK 5 /* spike detected */ + +/* + * Kernel PLL/PPS state machine. This is used with the kernel PLL + * modifications described in the README.kernel file. + * + * If kernel support for the ntp_adjtime() system call is available, the + * ntp_control flag is set. The ntp_enable and kern_enable flags can be + * set at configuration time or run time using ntpdc. If ntp_enable is + * false, the discipline loop is unlocked and no correctios of any kind + * are made. If both ntp_control and kern_enable are set, the kernel + * support is used as described above; if false, the kernel is bypassed + * entirely and the daemon PLL used instead. + * + * Each update to a prefer peer sets pps_stratum if it survives the + * intersection algorithm and its time is within range. The PPS time + * discipline is enabled (STA_PPSTIME bit set in the status word) when + * pps_stratum is true and the PPS frequency discipline is enabled. If + * the PPS time discipline is enabled and the kernel reports a PPS + * signal is present, the pps_control variable is set to the current + * time. If the current time is later than pps_control by PPS_MAXAGE + * (120 s), this variable is set to zero. + * + * If an external clock is present, the clock driver sets STA_CLK in the + * status word. When the local clock driver sees this bit, it updates + * via this routine, which then calls ntp_adjtime() with the STA_PLL bit + * set to zero, in which case the system clock is not adjusted. This is + * also a signal for the external clock driver to discipline the system + * clock. + */ +/* + * Program variables that can be tinkered. + */ +double clock_max = CLOCK_MAX; /* step threshold (s) */ +double clock_minstep = CLOCK_MINSTEP; /* stepout threshold (s) */ +double clock_panic = CLOCK_PANIC; /* panic threshold (s) */ +double clock_phi = CLOCK_PHI; /* dispersion rate (s/s) */ +double allan_xpt = CLOCK_ALLAN; /* Allan intercept (s) */ + +/* + * Program variables + */ +static double clock_offset; /* clock offset adjustment (s) */ +double drift_comp; /* clock frequency (s/s) */ +double clock_stability; /* clock stability (s/s) */ +u_long pps_control; /* last pps sample time */ +static void rstclock P((int, u_long, double)); /* transition function */ + +#ifdef KERNEL_PLL +struct timex ntv; /* kernel API parameters */ +int pll_status; /* status bits for kernel pll */ +int pll_nano; /* nanosecond kernel switch */ +#endif /* KERNEL_PLL */ + +/* + * Clock state machine control flags + */ +int ntp_enable; /* clock discipline enabled */ +int pll_control; /* kernel support available */ +int kern_enable; /* kernel support enabled */ +int pps_enable; /* kernel PPS discipline enabled */ +int ext_enable; /* external clock enabled */ +int pps_stratum; /* pps stratum */ +int allow_panic = FALSE; /* allow panic correction */ +int mode_ntpdate = FALSE; /* exit on first clock set */ + +/* + * Clock state machine variables + */ +u_char sys_poll = NTP_MINDPOLL; /* system poll interval (log2 s) */ +int state; /* clock discipline state */ +int tc_counter; /* hysteresis counter */ +u_long last_time; /* time of last clock update (s) */ +double last_offset; /* last clock offset (s) */ +double sys_jitter; /* system RMS jitter (s) */ + +/* + * Huff-n'-puff filter variables + */ +static double *sys_huffpuff; /* huff-n'-puff filter */ +static int sys_hufflen; /* huff-n'-puff filter stages */ +static int sys_huffptr; /* huff-n'-puff filter pointer */ +static double sys_mindly; /* huff-n'-puff filter min delay */ + +#if defined(KERNEL_PLL) +/* Emacs cc-mode goes nuts if we split the next line... */ +#define MOD_BITS (MOD_OFFSET | MOD_MAXERROR | MOD_ESTERROR | \ + MOD_STATUS | MOD_TIMECONST) +#ifdef SIGSYS +static void pll_trap P((int)); /* configuration trap */ +static struct sigaction sigsys; /* current sigaction status */ +static struct sigaction newsigsys; /* new sigaction status */ +static sigjmp_buf env; /* environment var. for pll_trap() */ +#endif /* SIGSYS */ +#endif /* KERNEL_PLL */ + +/* + * init_loopfilter - initialize loop filter data + */ +void +init_loopfilter(void) +{ + /* + * Initialize state variables. Initially, we expect no drift + * file, so set the state to S_NSET. + */ + rstclock(S_NSET, current_time, 0); +} + +/* + * local_clock - the NTP logical clock loop filter. Returns 1 if the + * clock was stepped, 0 if it was slewed and -1 if it is hopeless. + * + * LOCKCLOCK: The only thing this routine does is set the + * sys_rootdispersion variable equal to the peer dispersion. + */ +int +local_clock( + struct peer *peer, /* synch source peer structure */ + double fp_offset, /* clock offset (s) */ + double epsil /* jittter (square s*s) */ + ) +{ + u_long mu; /* interval since last update (s) */ + double oerror; /* previous error estimate */ + double flladj; /* FLL frequency adjustment (ppm) */ + double plladj; /* PLL frequency adjustment (ppm) */ + double clock_frequency; /* clock frequency adjustment (ppm) */ + double dtemp, etemp; /* double temps */ + int retval; /* return value */ + + /* + * If the loop is opened, monitor and record the offsets + * anyway in order to determine the open-loop response. + */ +#ifdef DEBUG + if (debug) + printf( + "local_clock: assocID %d offset %.9f jitter %.9f state %d\n", + peer->associd, fp_offset, SQRT(epsil), state); +#endif +#ifdef LOCKCLOCK + sys_rootdispersion = peer->rootdispersion; + return (0); + +#else /* LOCKCLOCK */ + if (!ntp_enable) { + record_loop_stats(fp_offset, drift_comp, SQRT(epsil), + clock_stability, sys_poll); + return (0); + } + + /* + * If the clock is way off, panic is declared. The clock_panic + * defaults to 1000 s; if set to zero, the panic will never + * occur. The allow_panic defaults to FALSE, so the first panic + * will exit. It can be set TRUE by a command line option, in + * which case the clock will be set anyway and time marches on. + * But, allow_panic will be set it FALSE when the update is + * within the step range; so, subsequent panics will exit. + */ + if (fabs(fp_offset) > clock_panic && clock_panic > 0 && + !allow_panic) { + msyslog(LOG_ERR, + "time correction of %.0f seconds exceeds sanity limit (%.0f); set clock manually to the correct UTC time.", + fp_offset, clock_panic); + return (-1); + } + + /* + * If simulating ntpdate, set the clock directly, rather than + * using the discipline. The clock_max defines the step + * threshold, above which the clock will be stepped instead of + * slewed. The value defaults to 128 ms, but can be set to even + * unreasonable values. If set to zero, the clock will never be + * stepped. + * + * Note that if ntpdate is active, the terminal does not detach, + * so the termination comments print directly to the console. + */ + if (mode_ntpdate) { + if (fabs(fp_offset) > clock_max && clock_max > 0) { + step_systime(fp_offset); + msyslog(LOG_NOTICE, "time reset %+.6f s", + fp_offset); + printf("ntpd: time set %+.6fs\n", fp_offset); + } else { + adj_systime(fp_offset); + msyslog(LOG_NOTICE, "time slew %+.6f s", + fp_offset); + printf("ntpd: time slew %+.6fs\n", fp_offset); + } + record_loop_stats(fp_offset, drift_comp, SQRT(epsil), + clock_stability, sys_poll); + exit (0); + } + + /* + * If the clock has never been set, set it and initialize the + * discipline parameters. We then switch to frequency mode to + * speed the inital convergence process. If lucky, after an hour + * the ntp.drift file is created and initialized and we don't + * get here again. + */ + if (state == S_NSET) { + if (fabs(fp_offset) > clock_max && clock_max > 0) { + step_systime(fp_offset); + msyslog(LOG_NOTICE, "time reset %+.6f s", + fp_offset); + reinit_timer(); + } + rstclock(S_FREQ, peer->epoch, 0); + return (1); + } + + /* + * Update the jitter estimate. + */ + oerror = sys_jitter; + dtemp = SQUARE(sys_jitter); + sys_jitter = SQRT(dtemp + (epsil - dtemp) / CLOCK_AVG); + + /* + * The huff-n'-puff filter finds the lowest delay in the recent + * interval. This is used to correct the offset by one-half the + * difference between the sample delay and minimum delay. This + * is most effective if the delays are highly assymetric and + * clockhopping is avoided and the clock frequency wander is + * relatively small. + */ + if (sys_huffpuff != NULL) { + if (peer->delay < sys_huffpuff[sys_huffptr]) + sys_huffpuff[sys_huffptr] = peer->delay; + if (peer->delay < sys_mindly) + sys_mindly = peer->delay; + if (fp_offset > 0) + dtemp = -(peer->delay - sys_mindly) / 2; + else + dtemp = (peer->delay - sys_mindly) / 2; + fp_offset += dtemp; +#ifdef DEBUG + if (debug) + printf( + "local_clock: size %d mindly %.6f huffpuff %.6f\n", + sys_hufflen, sys_mindly, dtemp); +#endif + } + + /* + * Clock state machine transition function. This is where the + * action is and defines how the system reacts to large phase + * and frequency errors. There are two main regimes: when the + * offset exceeds the step threshold and when it does not. + * However, if the step threshold is set to zero, a step will + * never occur. See the instruction manual for the details how + * these actions interact with the command line options. + */ + retval = 0; + if (sys_poll > peer->maxpoll) + sys_poll = peer->maxpoll; + else if (sys_poll < peer->minpoll) + sys_poll = peer->minpoll; + clock_frequency = flladj = plladj = 0; + mu = peer->epoch - last_time; + if (fabs(fp_offset) > clock_max && clock_max > 0) { + switch (state) { + + /* + * In S_TSET state the time has been set at the last + * valid update and the offset at that time set to zero. + * If following that we cruise outside the capture + * range, assume a really bad frequency error and switch + * to S_FREQ state. + */ + case S_TSET: + state = S_FREQ; + break; + + /* + * In S_SYNC state we ignore outlyers. At the first + * outlyer after the stepout threshold, switch to S_SPIK + * state. + */ + case S_SYNC: + if (mu < clock_minstep) + return (0); + state = S_SPIK; + return (0); + + /* + * In S_FREQ state we ignore outlyers. At the first + * outlyer after 900 s, compute the apparent phase and + * frequency correction. + */ + case S_FREQ: + if (mu < clock_minstep) + return (0); + /* fall through to S_SPIK */ + + /* + * In S_SPIK state a large correction is necessary. + * Since the outlyer may be due to a large frequency + * error, compute the apparent frequency correction. + */ + case S_SPIK: + clock_frequency = (fp_offset - clock_offset) / + mu; + /* fall through to default */ + + /* + * We get here directly in S_FSET state and indirectly + * from S_FREQ and S_SPIK states. The clock is either + * reset or shaken, but never stirred. + */ + default: + step_systime(fp_offset); + msyslog(LOG_NOTICE, "time reset %+.6f s", + fp_offset); + reinit_timer(); + rstclock(S_TSET, peer->epoch, 0); + retval = 1; + break; + } + } else { + switch (state) { + + /* + * In S_FSET state this is the first update. Adjust the + * phase, but don't adjust the frequency until the next + * update. + */ + case S_FSET: + rstclock(S_TSET, peer->epoch, fp_offset); + break; + + /* + * In S_FREQ state ignore updates until the stepout + * threshold. After that, correct the phase and + * frequency and switch to S_SYNC state. + */ + case S_FREQ: + if (mu < clock_minstep) + return (0); + clock_frequency = (fp_offset - clock_offset) / + mu; + rstclock(S_SYNC, peer->epoch, fp_offset); + break; + + /* + * Either the clock has just been set or the previous + * update was a spike and ignored. Since this update is + * not an outlyer, fold the tent and resume life. + */ + case S_TSET: + case S_SPIK: + state = S_SYNC; + /* fall through to default */ + + /* + * We come here in the normal case for linear phase and + * frequency adjustments. If the difference between the + * last offset and the current one exceeds the jitter by + * CLOCK_SGATE and the interval since the last update is + * less than twice the system poll interval, consider + * the update a popcorn spike and ignore it.. + */ + default: + allow_panic = FALSE; + dtemp = fabs(fp_offset - last_offset); +/* + if (dtemp > CLOCK_SGATE * oerror && mu < + (u_long) ULOGTOD(sys_poll + 1)) { +#ifdef DEBUG + if (debug) + printf( + "local_clock: popcorn %.6f %.6f\n", + dtemp, oerror); +#endif + last_offset = fp_offset; + return (0); + } +*/ + + /* + * The FLL and PLL frequency gain constants + * depend on the poll interval and Allan + * intercept. The PLL constant is calculated + * throughout the poll interval range, but the + * update interval is clamped so as not to + * exceed the poll interval. The FLL gain is + * zero below one-half the Allan intercept and + * unity at MAXPOLL. It decreases as 1 / + * (MAXPOLL + 1 - poll interval) in a feeble + * effort to match the loop stiffness to the + * Allan wobble. Particularly for the PLL, these + * measures allow oversampling, but not + * undersampling and insure stability even when + * the rules of fair engagement are broken. + */ + if (ULOGTOD(sys_poll) > allan_xpt / 2) { + dtemp = NTP_MAXPOLL + 1 - sys_poll; + flladj = (fp_offset - clock_offset) / + (max(mu, allan_xpt) * dtemp); + } + etemp = min(mu, (u_long)ULOGTOD(sys_poll)); + dtemp = 4 * CLOCK_PLL * ULOGTOD(sys_poll); + plladj = fp_offset * etemp / (dtemp * dtemp); + last_time = peer->epoch; + last_offset = clock_offset = fp_offset; + break; + } + } + +#ifdef KERNEL_PLL + /* + * This code segment works when clock adjustments are made using + * precision time kernel support and the ntp_adjtime() system + * call. This support is available in Solaris 2.6 and later, + * Digital Unix 4.0 and later, FreeBSD, Linux and specially + * modified kernels for HP-UX 9 and Ultrix 4. In the case of the + * DECstation 5000/240 and Alpha AXP, additional kernel + * modifications provide a true microsecond clock and nanosecond + * clock, respectively. + */ + if (pll_control && kern_enable) { + + /* + * We initialize the structure for the ntp_adjtime() + * system call. We have to convert everything to + * microseconds or nanoseconds first. Do not update the + * system variables if the ext_enable flag is set. In + * this case, the external clock driver will update the + * variables, which will be read later by the local + * clock driver. Afterwards, remember the time and + * frequency offsets for jitter and stability values and + * to update the drift file. + */ + memset(&ntv, 0, sizeof(ntv)); + if (ext_enable) { + ntv.modes = MOD_STATUS; + } else { + ntv.modes = MOD_BITS; + if (clock_offset < 0) + dtemp = -.5; + else + dtemp = .5; + if (pll_nano) { + ntv.offset = (int32)(clock_offset * + 1e9 + dtemp); + ntv.constant = sys_poll; + } else { + ntv.offset = (int32)(clock_offset * + 1e6 + dtemp); + ntv.constant = sys_poll - 4; + } + if (clock_frequency != 0) { + ntv.modes |= MOD_FREQUENCY; + ntv.freq = (int32)((clock_frequency + + drift_comp) * 65536e6); + } + ntv.esterror = (u_int32)(sys_jitter * 1e6); + ntv.maxerror = (u_int32)((sys_rootdelay / 2 + + sys_rootdispersion) * 1e6); + ntv.status = STA_PLL; + + /* + * Set the leap bits in the status word. + */ + if (sys_leap == LEAP_NOTINSYNC) { + ntv.status |= STA_UNSYNC; + } else if (calleapwhen(sys_reftime.l_ui) < + CLOCK_DAY) { + if (sys_leap & LEAP_ADDSECOND) + ntv.status |= STA_INS; + else if (sys_leap & LEAP_DELSECOND) + ntv.status |= STA_DEL; + } + + /* + * Switch to FLL mode if the poll interval is + * greater than MAXDPOLL, so that the kernel + * loop behaves as the daemon loop; viz., + * selects the FLL when necessary, etc. For + * legacy only. + */ + if (sys_poll > NTP_MAXDPOLL) + ntv.status |= STA_FLL; + + /* + * If the PPS signal is up and enabled, light + * the frequency bit. If the PPS driver is + * working, light the phase bit as well. If not, + * douse the lights, since somebody else may + * have left the switch on. + */ + if (pps_enable && pll_status & STA_PPSSIGNAL) { + ntv.status |= STA_PPSFREQ; + if (pps_stratum < STRATUM_UNSPEC) + ntv.status |= STA_PPSTIME; + } else { + ntv.status &= ~(STA_PPSFREQ | + STA_PPSTIME); + } + } + + /* + * Pass the stuff to the kernel. If it squeals, turn off + * the pigs. In any case, fetch the kernel offset and + * frequency and pretend we did it here. + */ + if (ntp_adjtime(&ntv) == TIME_ERROR) { + if (ntv.status != pll_status) + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "kernel time sync disabled %04x", + ntv.status); + ntv.status &= ~(STA_PPSFREQ | STA_PPSTIME); + } else { + if (ntv.status != pll_status) + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "kernel time sync enabled %04x", + ntv.status); + } + pll_status = ntv.status; + if (pll_nano) + clock_offset = ntv.offset / 1e9; + else + clock_offset = ntv.offset / 1e6; + clock_frequency = ntv.freq / 65536e6 - drift_comp; + flladj = plladj = 0; + + /* + * If the kernel PPS is lit, monitor its performance. + */ + if (ntv.status & STA_PPSTIME) { + pps_control = current_time; + if (pll_nano) + sys_jitter = ntv.jitter / 1e9; + else + sys_jitter = ntv.jitter / 1e6; + } + } +#endif /* KERNEL_PLL */ + + /* + * Adjust the clock frequency and calculate the stability. If + * kernel support is available, we use the results of the kernel + * discipline instead of the PLL/FLL discipline. In this case, + * drift_comp is a sham and used only for updating the drift + * file and for billboard eye candy. + */ + dtemp = clock_frequency + flladj + plladj; + etemp = drift_comp + dtemp; + if (etemp > NTP_MAXFREQ) + drift_comp = NTP_MAXFREQ; + else if (etemp <= -NTP_MAXFREQ) + drift_comp = -NTP_MAXFREQ; + else + drift_comp = etemp; + if (fabs(etemp) > NTP_MAXFREQ) + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "frequency error %.0f PPM exceeds tolerance %.0f PPM", + etemp * 1e6, NTP_MAXFREQ * 1e6); + + etemp = SQUARE(clock_stability); + dtemp = SQUARE(dtemp); + clock_stability = SQRT(etemp + (dtemp - etemp) / CLOCK_AVG); + + /* + * In SYNC state, adjust the poll interval. The trick here is to + * compare the apparent frequency change induced by the system + * jitter over the poll interval, or fritter, to the frequency + * stability. If the fritter is greater than the stability, + * phase noise predominates and the averaging interval is + * increased; otherwise, it is decreased. A bit of hysteresis + * helps calm the dance. Works best using burst mode. + */ + if (state == S_SYNC) { + if (sys_jitter > ULOGTOD(sys_poll) * clock_stability && + fabs(clock_offset) < CLOCK_PGATE * sys_jitter) { + tc_counter += sys_poll; + if (tc_counter > CLOCK_LIMIT) { + tc_counter = CLOCK_LIMIT; + if (sys_poll < peer->maxpoll) { + tc_counter = 0; + sys_poll++; + } + } + } else { + tc_counter -= sys_poll << 1; + if (tc_counter < -CLOCK_LIMIT) { + tc_counter = -CLOCK_LIMIT; + if (sys_poll > peer->minpoll) { + tc_counter = 0; + sys_poll--; + } + } + } + } + + /* + * Update the system time variables. + */ + dtemp = peer->disp + (current_time - peer->epoch) * clock_phi + + sys_jitter + fabs(last_offset); + if (!(peer->flags & FLAG_REFCLOCK) && dtemp < MINDISPERSE) + dtemp = MINDISPERSE; + sys_rootdispersion = peer->rootdispersion + dtemp; + record_loop_stats(last_offset, drift_comp, sys_jitter, + clock_stability, sys_poll); + +#ifdef DEBUG + if (debug) + printf( + "local_clock: mu %lu rootjit %.6f stab %.3f poll %d count %d\n", + mu, dtemp, clock_stability * 1e6, sys_poll, + tc_counter); +#endif /* DEBUG */ + return (retval); +#endif /* LOCKCLOCK */ +} + + +/* + * adj_host_clock - Called once every second to update the local clock. + * + * LOCKCLOCK: The only thing this routine does is increment the + * sys_rootdispersion variable. + */ +void +adj_host_clock( + void + ) +{ + double adjustment; + + /* + * Update the dispersion since the last update. In contrast to + * NTPv3, NTPv4 does not declare unsynchronized after one day, + * since the dispersion check serves this function. Also, + * since the poll interval can exceed one day, the old test + * would be counterproductive. Note we do this even with + * external clocks, since the clock driver will recompute the + * maximum error and the local clock driver will pick it up and + * pass to the common refclock routines. Very elegant. + */ + sys_rootdispersion += clock_phi; + +#ifndef LOCKCLOCK + /* + * Declare PPS kernel unsync if the pps signal has not been + * heard for a few minutes. + */ + if (pps_control && current_time - pps_control > PPS_MAXAGE) { + if (pps_control) + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, "pps sync disabled"); + pps_control = 0; + } + + /* + * If NTP is disabled or ntpdate mode enabled or the kernel + * discipline enabled, we have no business going further. + */ + if (!ntp_enable || mode_ntpdate || (pll_control && kern_enable)) + return; + + /* + * Intricate wrinkle for legacy only. If the local clock driver + * is in use and selected for synchronization, somebody else may + * tinker the adjtime() syscall. If this is the case, the driver + * is marked prefer and we have to avoid calling adjtime(), + * since that may truncate the other guy's requests. + */ + if (sys_peer != 0) { + if (sys_peer->refclktype == REFCLK_LOCALCLOCK && + sys_peer->flags & FLAG_PREFER) + return; + } + + /* + * Implement the phase and frequency adjustments. Note the + * black art formerly practiced here has been whitewashed. + */ + adjustment = clock_offset / (CLOCK_PLL * ULOGTOD(sys_poll)); + clock_offset -= adjustment; + adj_systime(adjustment + drift_comp); +#endif /* LOCKCLOCK */ +} + + +/* + * Clock state machine. Enter new state and set state variables. + */ +static void +rstclock( + int trans, /* new state */ + u_long epoch, /* last time */ + double offset /* last offset */ + ) +{ + tc_counter = 0; + sys_poll = NTP_MINPOLL; + state = trans; + last_time = epoch; + last_offset = clock_offset = offset; +#ifdef DEBUG + if (debug) + printf("local_clock: at %lu state %d\n", last_time, + trans); +#endif +} + + +/* + * huff-n'-puff filter + */ +void +huffpuff() +{ + int i; + + if (sys_huffpuff == NULL) + return; + sys_huffptr = (sys_huffptr + 1) % sys_hufflen; + sys_huffpuff[sys_huffptr] = 1e9; + sys_mindly = 1e9; + for (i = 0; i < sys_hufflen; i++) { + if (sys_huffpuff[i] < sys_mindly) + sys_mindly = sys_huffpuff[i]; + } +} + + +/* + * loop_config - configure the loop filter + * + * LOCKCLOCK: The LOOP_DRIFTINIT and LOOP_DRIFTCOMP cases are no-ops. + */ +void +loop_config( + int item, + double freq + ) +{ + int i; + + switch (item) { + + case LOOP_DRIFTINIT: + +#ifndef LOCKCLOCK +#ifdef KERNEL_PLL + /* + * Assume the kernel supports the ntp_adjtime() syscall. + * If that syscall works, initialize the kernel + * variables. Otherwise, continue leaving no harm + * behind. While at it, ask to set nanosecond mode. If + * the kernel agrees, rejoice; othewise, it does only + * microseconds. + * + * Call out the safety patrol. If ntpdate mode or if the + * step threshold has been changed by the -x option or + * tinker command, kernel discipline is unsafe, so don't + * do any of this stuff. + */ + if (mode_ntpdate || clock_max != CLOCK_MAX) + break; + + pll_control = 1; + memset(&ntv, 0, sizeof(ntv)); +#ifdef STA_NANO + ntv.modes = MOD_BITS | MOD_NANO; +#else + ntv.modes = MOD_BITS; +#endif /* STA_NANO */ + ntv.maxerror = MAXDISPERSE; + ntv.esterror = MAXDISPERSE; + ntv.status = STA_UNSYNC; +#ifdef SIGSYS + /* + * Use sigsetjmp() to save state and then call + * ntp_adjtime(); if it fails, then siglongjmp() is used + * to return control + */ + newsigsys.sa_handler = pll_trap; + newsigsys.sa_flags = 0; + if (sigaction(SIGSYS, &newsigsys, &sigsys)) { + msyslog(LOG_ERR, + "sigaction() fails to save SIGSYS trap: %m"); + pll_control = 0; + } + if (sigsetjmp(env, 1) == 0) + ntp_adjtime(&ntv); + if ((sigaction(SIGSYS, &sigsys, + (struct sigaction *)NULL))) { + msyslog(LOG_ERR, + "sigaction() fails to restore SIGSYS trap: %m"); + pll_control = 0; + } +#else /* SIGSYS */ + ntp_adjtime(&ntv); +#endif /* SIGSYS */ + pll_status = ntv.status; + if (pll_control) { +#ifdef STA_NANO + if (pll_status & STA_NANO) + pll_nano = 1; + if (pll_status & STA_CLK) + ext_enable = 1; +#endif /* STA_NANO */ + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_INFO, + "kernel time sync status %04x", + pll_status); + } +#endif /* KERNEL_PLL */ +#endif /* LOCKCLOCK */ + break; + + case LOOP_DRIFTCOMP: + +#ifndef LOCKCLOCK + /* + * If the frequency value is reasonable, set the initial + * frequency to the given value and the state to S_FSET. + * Otherwise, the drift file may be missing or broken, + * so set the frequency to zero. This erases past + * history should somebody break something. + */ + if (freq <= NTP_MAXFREQ && freq >= -NTP_MAXFREQ) { + drift_comp = freq; + rstclock(S_FSET, current_time, 0); + } else { + drift_comp = 0; + } + +#ifdef KERNEL_PLL + /* + * Sanity check. If the kernel is enabled, load the + * frequency and light up the loop. If not, set the + * kernel frequency to zero and leave the loop dark. In + * either case set the time to zero to cancel any + * previous nonsense. + */ + if (pll_control) { + memset((char *)&ntv, 0, sizeof(ntv)); + ntv.modes = MOD_OFFSET | MOD_FREQUENCY; + if (kern_enable) { + ntv.modes |= MOD_STATUS; + ntv.status = STA_PLL; + ntv.freq = (int32)(drift_comp * + 65536e6); + } + (void)ntp_adjtime(&ntv); + } +#endif /* KERNEL_PLL */ +#endif /* LOCKCLOCK */ + break; + + /* + * Special tinker variables for Ulrich Windl. Very dangerous. + */ + case LOOP_MAX: /* step threshold */ + clock_max = freq; + break; + + case LOOP_PANIC: /* panic threshold */ + clock_panic = freq; + break; + + case LOOP_PHI: /* dispersion rate */ + clock_phi = freq; + break; + + case LOOP_MINSTEP: /* watchdog bark */ + clock_minstep = freq; + break; + + case LOOP_ALLAN: /* Allan intercept */ + allan_xpt = freq; + break; + + case LOOP_HUFFPUFF: /* huff-n'-puff filter length */ + if (freq < HUFFPUFF) + freq = HUFFPUFF; + sys_hufflen = (int)(freq / HUFFPUFF); + sys_huffpuff = (double *)emalloc(sizeof(double) * + sys_hufflen); + for (i = 0; i < sys_hufflen; i++) + sys_huffpuff[i] = 1e9; + sys_mindly = 1e9; + break; + + case LOOP_FREQ: /* initial frequency */ + drift_comp = freq / 1e6; + rstclock(S_FSET, current_time, 0); + break; + } +} + + +#if defined(KERNEL_PLL) && defined(SIGSYS) +/* + * _trap - trap processor for undefined syscalls + * + * This nugget is called by the kernel when the SYS_ntp_adjtime() + * syscall bombs because the silly thing has not been implemented in + * the kernel. In this case the phase-lock loop is emulated by + * the stock adjtime() syscall and a lot of indelicate abuse. + */ +static RETSIGTYPE +pll_trap( + int arg + ) +{ + pll_control = 0; + siglongjmp(env, 1); +} +#endif /* KERNEL_PLL && SIGSYS */ diff --git a/ntpd/ntp_monitor.c b/ntpd/ntp_monitor.c new file mode 100644 index 0000000..6b288fc --- /dev/null +++ b/ntpd/ntp_monitor.c @@ -0,0 +1,335 @@ +/* + * ntp_monitor - monitor ntpd statistics + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_if.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <signal.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +/* + * I'm still not sure I like what I've done here. It certainly consumes + * memory like it is going out of style, and also may not be as low + * overhead as I'd imagined. + * + * Anyway, we record statistics based on source address, mode and + * version (for now, anyway. Check the code). The receive procedure + * calls us with the incoming rbufp before it does anything else. + * + * Each entry is doubly linked into two lists, a hash table and a + * most-recently-used list. When a packet arrives it is looked up in + * the hash table. If found, the statistics are updated and the entry + * relinked at the head of the MRU list. If not found, a new entry is + * allocated, initialized and linked into both the hash table and at the + * head of the MRU list. + * + * Memory is usually allocated by grabbing a big chunk of new memory and + * cutting it up into littler pieces. The exception to this when we hit + * the memory limit. Then we free memory by grabbing entries off the + * tail for the MRU list, unlinking from the hash table, and + * reinitializing. + * + * trimmed back memory consumption ... jdg 8/94 + */ +/* + * Limits on the number of structures allocated. This limit is picked + * with the illicit knowlege that we can only return somewhat less + * than 8K bytes in a mode 7 response packet, and that each structure + * will require about 20 bytes of space in the response. + * + * ... I don't believe the above is true anymore ... jdg + */ +#ifndef MAXMONMEM +#define MAXMONMEM 600 /* we allocate up to 600 structures */ +#endif +#ifndef MONMEMINC +#define MONMEMINC 40 /* allocate them 40 at a time */ +#endif + +/* + * Hashing stuff + */ +#define MON_HASH_SIZE 128 +#define MON_HASH_MASK (MON_HASH_SIZE-1) +#define MON_HASH(addr) sock_hash(addr) + +/* + * Pointers to the hash table, the MRU list and the count table. Memory + * for the hash and count tables is only allocated if monitoring is + * turned on. + */ +static struct mon_data *mon_hash[MON_HASH_SIZE]; /* list ptrs */ +struct mon_data mon_mru_list; + +/* + * List of free structures structures, and counters of free and total + * structures. The free structures are linked with the hash_next field. + */ +static struct mon_data *mon_free; /* free list or null if none */ +static int mon_total_mem; /* total structures allocated */ +static int mon_mem_increments; /* times called malloc() */ + +/* + * Initialization state. We may be monitoring, we may not. If + * we aren't, we may not even have allocated any memory yet. + */ +int mon_enabled; /* enable switch */ +u_long mon_age = 3000; /* preemption limit */ +static int mon_have_memory; +static void mon_getmoremem P((void)); +static void remove_from_hash P((struct mon_data *)); + +/* + * init_mon - initialize monitoring global data + */ +void +init_mon(void) +{ + /* + * Don't do much of anything here. We don't allocate memory + * until someone explicitly starts us. + */ + mon_enabled = MON_OFF; + mon_have_memory = 0; + + mon_total_mem = 0; + mon_mem_increments = 0; + mon_free = NULL; + memset(&mon_hash[0], 0, sizeof mon_hash); + memset(&mon_mru_list, 0, sizeof mon_mru_list); +} + + +/* + * mon_start - start up the monitoring software + */ +void +mon_start( + int mode + ) +{ + + if (mon_enabled != MON_OFF) { + mon_enabled |= mode; + return; + } + if (mode == MON_OFF) + return; + + if (!mon_have_memory) { + mon_total_mem = 0; + mon_mem_increments = 0; + mon_free = NULL; + mon_getmoremem(); + mon_have_memory = 1; + } + + mon_mru_list.mru_next = &mon_mru_list; + mon_mru_list.mru_prev = &mon_mru_list; + mon_enabled = mode; +} + + +/* + * mon_stop - stop the monitoring software + */ +void +mon_stop( + int mode + ) +{ + register struct mon_data *md, *md_next; + register int i; + + if (mon_enabled == MON_OFF) + return; + if ((mon_enabled & mode) == 0 || mode == MON_OFF) + return; + + mon_enabled &= ~mode; + if (mon_enabled != MON_OFF) + return; + + /* + * Put everything back on the free list + */ + for (i = 0; i < MON_HASH_SIZE; i++) { + md = mon_hash[i]; /* get next list */ + mon_hash[i] = NULL; /* zero the list head */ + while (md != NULL) { + md_next = md->hash_next; + md->hash_next = mon_free; + mon_free = md; + md = md_next; + } + } + + mon_mru_list.mru_next = &mon_mru_list; + mon_mru_list.mru_prev = &mon_mru_list; +} + + +/* + * ntp_monitor - record stats about this packet + */ +void +ntp_monitor( + struct recvbuf *rbufp + ) +{ + register struct pkt *pkt; + register struct mon_data *md; + struct sockaddr_storage addr; + register int hash; + register int mode; + + if (mon_enabled == MON_OFF) + return; + + pkt = &rbufp->recv_pkt; + memset(&addr, 0, sizeof(addr)); + memcpy(&addr, &(rbufp->recv_srcadr), sizeof(addr)); + hash = MON_HASH(&addr); + mode = PKT_MODE(pkt->li_vn_mode); + md = mon_hash[hash]; + while (md != NULL) { + + /* + * Match address only to conserve MRU size. + */ + if (SOCKCMP(&md->rmtadr, &addr)) { + md->drop_count = current_time - md->lasttime; + md->lasttime = current_time; + md->count++; + md->rmtport = NSRCPORT(&rbufp->recv_srcadr); + md->mode = (u_char) mode; + md->version = PKT_VERSION(pkt->li_vn_mode); + + /* + * Shuffle to the head of the MRU list. + */ + md->mru_next->mru_prev = md->mru_prev; + md->mru_prev->mru_next = md->mru_next; + md->mru_next = mon_mru_list.mru_next; + md->mru_prev = &mon_mru_list; + mon_mru_list.mru_next->mru_prev = md; + mon_mru_list.mru_next = md; + return; + } + md = md->hash_next; + } + + /* + * If we got here, this is the first we've heard of this + * guy. Get him some memory, either from the free list + * or from the tail of the MRU list. + */ + if (mon_free == NULL && mon_total_mem >= MAXMONMEM) { + + /* + * Preempt from the MRU list if old enough. + */ + md = mon_mru_list.mru_prev; + if (((u_long)RANDOM & 0xffffffff) / FRAC > + (double)(current_time - md->lasttime) / mon_age) + return; + + md->mru_prev->mru_next = &mon_mru_list; + mon_mru_list.mru_prev = md->mru_prev; + remove_from_hash(md); + } else { + if (mon_free == NULL) + mon_getmoremem(); + md = mon_free; + mon_free = md->hash_next; + } + + /* + * Got one, initialize it + */ + md->avg_interval = 0; + md->lasttime = current_time; + md->count = 1; + md->drop_count = 0; + memset(&md->rmtadr, 0, sizeof(md->rmtadr)); + memcpy(&md->rmtadr, &addr, sizeof(addr)); + md->rmtport = NSRCPORT(&rbufp->recv_srcadr); + md->mode = (u_char) mode; + md->version = PKT_VERSION(pkt->li_vn_mode); + md->interface = rbufp->dstadr; + md->cast_flags = (u_char)(((rbufp->dstadr->flags & INT_MULTICAST) && + rbufp->fd == md->interface->fd) ? MDF_MCAST: rbufp->fd == + md->interface->bfd ? MDF_BCAST : MDF_UCAST); + + /* + * Drop him into front of the hash table. Also put him on top of + * the MRU list. + */ + md->hash_next = mon_hash[hash]; + mon_hash[hash] = md; + md->mru_next = mon_mru_list.mru_next; + md->mru_prev = &mon_mru_list; + mon_mru_list.mru_next->mru_prev = md; + mon_mru_list.mru_next = md; +} + + +/* + * mon_getmoremem - get more memory and put it on the free list + */ +static void +mon_getmoremem(void) +{ + register struct mon_data *md; + register int i; + struct mon_data *freedata; /* 'old' free list (null) */ + + md = (struct mon_data *)emalloc(MONMEMINC * + sizeof(struct mon_data)); + freedata = mon_free; + mon_free = md; + for (i = 0; i < (MONMEMINC-1); i++) { + md->hash_next = (md + 1); + md++; + } + + /* + * md now points at the last. Link in the rest of the chain. + */ + md->hash_next = freedata; + mon_total_mem += MONMEMINC; + mon_mem_increments++; +} + +static void +remove_from_hash( + struct mon_data *md + ) +{ + register int hash; + register struct mon_data *md_prev; + + hash = MON_HASH(&md->rmtadr); + if (mon_hash[hash] == md) { + mon_hash[hash] = md->hash_next; + } else { + md_prev = mon_hash[hash]; + while (md_prev->hash_next != md) { + md_prev = md_prev->hash_next; + if (md_prev == NULL) { + /* logic error */ + return; + } + } + md_prev->hash_next = md->hash_next; + } +} diff --git a/ntpd/ntp_peer.c b/ntpd/ntp_peer.c new file mode 100644 index 0000000..cf8a600 --- /dev/null +++ b/ntpd/ntp_peer.c @@ -0,0 +1,880 @@ +/* + * ntp_peer.c - management of data maintained for peer associations + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <sys/types.h> + +#include "ntpd.h" +#include "ntp_stdlib.h" +#ifdef OPENSSL +#include "openssl/rand.h" +#endif /* OPENSSL */ + +/* + * Table of valid association combinations + * --------------------------------------- + * + * packet->mode + * peer->mode | UNSPEC ACTIVE PASSIVE CLIENT SERVER BCAST + * ---------- | --------------------------------------------- + * NO_PEER | e 1 e 1 1 1 + * ACTIVE | e 1 1 0 0 0 + * PASSIVE | e 1 e 0 0 0 + * CLIENT | e 0 0 0 1 1 + * SERVER | e 0 0 0 0 0 + * BCAST | e 0 0 0 0 0 + * CONTROL | e 0 0 0 0 0 + * PRIVATE | e 0 0 0 0 0 + * BCLIENT | e 0 0 0 e 1 + * + * One point to note here: a packet in BCAST mode can potentially match + * a peer in CLIENT mode, but we that is a special case and we check for + * that early in the decision process. This avoids having to keep track + * of what kind of associations are possible etc... We actually + * circumvent that problem by requiring that the first b(m)roadcast + * received after the change back to BCLIENT mode sets the clock. + */ + +int AM[AM_MODES][AM_MODES] = { +/* { UNSPEC, ACTIVE, PASSIVE, CLIENT, SERVER, BCAST } */ + +/*NONE*/{ AM_ERR, AM_NEWPASS, AM_ERR, AM_FXMIT, AM_MANYCAST, AM_NEWBCL}, + +/*A*/ { AM_ERR, AM_PROCPKT, AM_PROCPKT, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*P*/ { AM_ERR, AM_PROCPKT, AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*C*/ { AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_PROCPKT, AM_POSSBCL}, + +/*S*/ { AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*BCST*/{ AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*CNTL*/{ AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*PRIV*/{ AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH}, + +/*BCL*/ { AM_ERR, AM_NOMATCH, AM_NOMATCH, AM_NOMATCH, AM_ERR, AM_PROCPKT}, +}; + +#define MATCH_ASSOC(x,y) AM[(x)][(y)] + +/* + * These routines manage the allocation of memory to peer structures + * and the maintenance of the peer hash table. The two main entry + * points are findpeer(), which looks for matching peer sturctures in + * the peer list, newpeer(), which allocates a new peer structure and + * adds it to the list, and unpeer(), which demobilizes the association + * and deallocates the structure. + */ +/* + * Peer hash tables + */ +struct peer *peer_hash[HASH_SIZE]; /* peer hash table */ +int peer_hash_count[HASH_SIZE]; /* peers in each bucket */ +struct peer *assoc_hash[HASH_SIZE]; /* association ID hash table */ +int assoc_hash_count[HASH_SIZE]; /* peers in each bucket */ +static struct peer *peer_free; /* peer structures free list */ +int peer_free_count; /* count of free structures */ + +/* + * Association ID. We initialize this value randomly, then assign a new + * value every time the peer structure is incremented. + */ +static associd_t current_association_ID; /* association ID */ + +/* + * Memory allocation watermarks. + */ +#define INIT_PEER_ALLOC 15 /* initialize for 15 peers */ +#define INC_PEER_ALLOC 5 /* when run out, add 5 more */ + +/* + * Miscellaneous statistic counters which may be queried. + */ +u_long peer_timereset; /* time stat counters zeroed */ +u_long findpeer_calls; /* calls to findpeer */ +u_long assocpeer_calls; /* calls to findpeerbyassoc */ +u_long peer_allocations; /* allocations from free list */ +u_long peer_demobilizations; /* structs freed to free list */ +int total_peer_structs; /* peer structs */ +int peer_associations; /* active associations */ +static struct peer init_peer_alloc[INIT_PEER_ALLOC]; /* init alloc */ + +static void getmorepeermem P((void)); + +/* + * init_peer - initialize peer data structures and counters + * + * N.B. We use the random number routine in here. It had better be + * initialized prior to getting here. + */ +void +init_peer(void) +{ + register int i; + + /* + * Clear hash table and counters. + */ + for (i = 0; i < HASH_SIZE; i++) { + peer_hash[i] = 0; + peer_hash_count[i] = 0; + assoc_hash[i] = 0; + assoc_hash_count[i] = 0; + } + + /* + * Clear stat counters + */ + findpeer_calls = peer_allocations = 0; + assocpeer_calls = peer_demobilizations = 0; + + /* + * Initialize peer memory. + */ + peer_free = 0; + for (i = 0; i < INIT_PEER_ALLOC; i++) { + init_peer_alloc[i].next = peer_free; + peer_free = &init_peer_alloc[i]; + } + total_peer_structs = INIT_PEER_ALLOC; + peer_free_count = INIT_PEER_ALLOC; + + /* + * Initialize our first association ID + */ + current_association_ID = (associd_t)ranp2(16); + if (current_association_ID == 0) + current_association_ID = 1; +} + + +/* + * getmorepeermem - add more peer structures to the free list + */ +static void +getmorepeermem(void) +{ + register int i; + register struct peer *peer; + + peer = (struct peer *)emalloc(INC_PEER_ALLOC * + sizeof(struct peer)); + for (i = 0; i < INC_PEER_ALLOC; i++) { + peer->next = peer_free; + peer_free = peer; + peer++; + } + + total_peer_structs += INC_PEER_ALLOC; + peer_free_count += INC_PEER_ALLOC; +} + + +/* + * findexistingpeer - return a pointer to a peer in the hash table + */ +struct peer * +findexistingpeer( + struct sockaddr_storage *addr, + struct peer *start_peer, + int mode + ) +{ + register struct peer *peer; + + /* + * start_peer is included so we can locate instances of the + * same peer through different interfaces in the hash table. + */ + if (start_peer == 0) + peer = peer_hash[HASH_ADDR(addr)]; + else + peer = start_peer->next; + + while (peer != 0) { + if (SOCKCMP(addr, &peer->srcadr) + && NSRCPORT(addr) == NSRCPORT(&peer->srcadr)) { + if (mode == -1) + return (peer); + else if (peer->hmode == mode) + break; + } + peer = peer->next; + } + return (peer); +} + + +/* + * findpeer - find and return a peer in the hash table. + */ +struct peer * +findpeer( + struct sockaddr_storage *srcadr, + struct interface *dstadr, + int fd, + int pkt_mode, + int *action + ) +{ + register struct peer *peer; + int hash; + + findpeer_calls++; + hash = HASH_ADDR(srcadr); + for (peer = peer_hash[hash]; peer != NULL; peer = peer->next) { + if (SOCKCMP(srcadr, &peer->srcadr) + && NSRCPORT(srcadr) == NSRCPORT(&peer->srcadr)) { + + /* + * if the association matching rules determine + * that this is not a valid combination, then + * look for the next valid peer association. + */ + *action = MATCH_ASSOC(peer->hmode, pkt_mode); + + /* + * Sigh! Check if BCLIENT peer in client + * server mode, else return error. + */ + if ((*action == AM_POSSBCL) && !(peer->flags & + FLAG_MCAST)) + *action = AM_ERR; + + /* + * if an error was returned, exit back right + * here. + */ + if (*action == AM_ERR) + return ((struct peer *)0); + + /* + * if a match is found, we stop our search. + */ + if (*action != AM_NOMATCH) + break; + } + } + + /* + * If no matching association is found + */ + if (peer == 0) { + *action = MATCH_ASSOC(NO_PEER, pkt_mode); + return ((struct peer *)0); + } + peer->dstadr = dstadr; + return (peer); +} + +/* + * findpeerbyassocid - find and return a peer using his association ID + */ +struct peer * +findpeerbyassoc( + u_int assoc + ) +{ + register struct peer *peer; + int hash; + + assocpeer_calls++; + + hash = assoc & HASH_MASK; + for (peer = assoc_hash[hash]; peer != 0; peer = + peer->ass_next) { + if (assoc == peer->associd) + return (peer); + } + return (NULL); +} + + +/* + * clear_all - flush all time values for all associations + */ +void +clear_all(void) +{ + struct peer *peer, *next_peer; + int n; + + /* + * This routine is called when the clock is stepped, and so all + * previously saved time values are untrusted. + */ + for (n = 0; n < HASH_SIZE; n++) { + for (peer = peer_hash[n]; peer != 0; peer = next_peer) { + next_peer = peer->next; + if (peer->flags & FLAG_CONFIG) { + if (!(peer->cast_flags & (MDF_ACAST | + MDF_MCAST | MDF_BCAST))) + peer_clear(peer, "STEP"); + } else { + unpeer(peer); + } + } + } +#ifdef DEBUG + if (debug) + printf("clear_all: at %lu\n", current_time); +#endif +} + + +/* + * unpeer - remove peer structure from hash table and free structure + */ +void +unpeer( + struct peer *peer_to_remove + ) +{ + int hash; +#ifdef OPENSSL + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + + if (peer_to_remove->flags & FLAG_SKEY) { + sprintf(statstr, "unpeer %d flash %x reach %03o flags %04x", + peer_to_remove->associd, peer_to_remove->flash, + peer_to_remove->reach, peer_to_remove->flags); + record_crypto_stats(&peer_to_remove->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("peer: %s\n", statstr); +#endif + } +#endif /* OPENSSL */ +#ifdef DEBUG + if (debug) + printf("demobilize %u %d\n", peer_to_remove->associd, + peer_associations); +#endif + peer_clear(peer_to_remove, "NULL"); + hash = HASH_ADDR(&peer_to_remove->srcadr); + peer_hash_count[hash]--; + peer_demobilizations++; +#ifdef REFCLOCK + /* + * If this peer is actually a clock, shut it down first + */ + if (peer_to_remove->flags & FLAG_REFCLOCK) + refclock_unpeer(peer_to_remove); +#endif + peer_to_remove->action = 0; /* disable timeout actions */ + if (peer_hash[hash] == peer_to_remove) + peer_hash[hash] = peer_to_remove->next; + else { + register struct peer *peer; + + peer = peer_hash[hash]; + while (peer != 0 && peer->next != peer_to_remove) + peer = peer->next; + + if (peer == 0) { + peer_hash_count[hash]++; + msyslog(LOG_ERR, "peer struct for %s not in table!", + stoa(&peer->srcadr)); + } else { + peer->next = peer_to_remove->next; + } + } + + /* + * Remove him from the association hash as well. + */ + hash = peer_to_remove->associd & HASH_MASK; + assoc_hash_count[hash]--; + if (assoc_hash[hash] == peer_to_remove) + assoc_hash[hash] = peer_to_remove->ass_next; + else { + register struct peer *peer; + + peer = assoc_hash[hash]; + while (peer != 0 && peer->ass_next != peer_to_remove) + peer = peer->ass_next; + + if (peer == 0) { + assoc_hash_count[hash]++; + msyslog(LOG_ERR, + "peer struct for %s not in association table!", + stoa(&peer->srcadr)); + } else { + peer->ass_next = peer_to_remove->ass_next; + } + } + peer_to_remove->next = peer_free; + peer_free = peer_to_remove; + peer_free_count++; + peer_associations--; +} + + +/* + * peer_config - configure a new association + */ +struct peer * +peer_config( + struct sockaddr_storage *srcadr, + struct interface *dstadr, + int hmode, + int version, + int minpoll, + int maxpoll, + u_int flags, + int ttl, + keyid_t key, + u_char *keystr + ) +{ + register struct peer *peer; + u_char cast_flags; + + /* + * First search from the beginning for an association with given + * remote address and mode. If an interface is given, search + * from there to find the association which matches that + * destination. + */ + peer = findexistingpeer(srcadr, (struct peer *)0, hmode); + if (dstadr != 0) { + while (peer != 0) { + if (peer->dstadr == dstadr) + break; + peer = findexistingpeer(srcadr, peer, hmode); + } + } + + /* + * We do a dirty little jig to figure the cast flags. This is + * probably not the best place to do this, at least until the + * configure code is rebuilt. Note only one flag can be set. + */ + switch (hmode) { + + case MODE_BROADCAST: + if(srcadr->ss_family == AF_INET) { + if (IN_CLASSD(ntohl(((struct sockaddr_in*)srcadr)->sin_addr.s_addr))) + cast_flags = MDF_MCAST; + else + cast_flags = MDF_BCAST; + break; + } + else { + if (IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)srcadr)->sin6_addr)) + cast_flags = MDF_MCAST; + else + cast_flags = MDF_BCAST; + break; + } + + case MODE_CLIENT: + if(srcadr->ss_family == AF_INET) { + if (IN_CLASSD(ntohl(((struct sockaddr_in*)srcadr)->sin_addr.s_addr))) + cast_flags = MDF_ACAST; + else + cast_flags = MDF_UCAST; + break; + } + else { + if (IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6*)srcadr)->sin6_addr)) + cast_flags = MDF_ACAST; + else + cast_flags = MDF_UCAST; + break; + } + + default: + cast_flags = MDF_UCAST; + } + + /* + * If the peer is already configured, some dope has a duplicate + * configureation entry or another dope is wiggling from afar. + */ + if (peer != 0) { + peer->hmode = (u_char)hmode; + peer->version = (u_char) version; + peer->minpoll = (u_char) minpoll; + peer->maxpoll = (u_char) maxpoll; + peer->flags = flags | FLAG_CONFIG | + (peer->flags & FLAG_REFCLOCK); + peer->cast_flags = cast_flags; + peer->ttl = (u_char) ttl; + peer->keyid = key; + peer->precision = sys_precision; + peer_clear(peer, "RMOT"); + return (peer); + } + + /* + * Here no match has been found, so presumably this is a new + * persistent association. Mobilize the thing and initialize its + * variables. If emulating ntpdate, force iburst. + */ + if (mode_ntpdate) + flags |= FLAG_IBURST; + peer = newpeer(srcadr, dstadr, hmode, version, minpoll, maxpoll, + flags | FLAG_CONFIG, cast_flags, ttl, key); + return (peer); +} + + +/* + * newpeer - initialize a new peer association + */ +struct peer * +newpeer( + struct sockaddr_storage *srcadr, + struct interface *dstadr, + int hmode, + int version, + int minpoll, + int maxpoll, + u_int flags, + u_char cast_flags, + int ttl, + keyid_t key + ) +{ + register struct peer *peer; + register int i; +#ifdef OPENSSL + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ +#endif /* OPENSSL */ + + /* + * Allocate a new peer structure. Some dirt here, since some of + * the initialization requires knowlege of our system state. + */ + if (peer_free_count == 0) + getmorepeermem(); + peer = peer_free; + peer_free = peer->next; + peer_free_count--; + peer_associations++; + memset((char *)peer, 0, sizeof(struct peer)); + + /* + * Assign an association ID and increment the system variable. + */ + peer->associd = current_association_ID; + if (++current_association_ID == 0) + ++current_association_ID; + + /* + * Initialize the peer structure and dance the interface jig. + * Reference clocks step the loopback waltz, the others + * squaredance around the interface list looking for a buddy. If + * the dance peters out, there is always the wildcard interface. + * This might happen in some systems and would preclude proper + * operation with public key cryptography. + */ + if (ISREFCLOCKADR(srcadr)) + peer->dstadr = loopback_interface; + else if (cast_flags & (MDF_BCLNT | MDF_ACAST | MDF_MCAST | MDF_BCAST)) { + peer->dstadr = findbcastinter(srcadr); + /* + * If it was a multicast packet, findbcastinter() may not + * find it, so try a little harder. + */ + if (peer->dstadr == ANY_INTERFACE_CHOOSE(srcadr)) + peer->dstadr = findinterface(srcadr); + } else if (dstadr != NULL && dstadr != ANY_INTERFACE_CHOOSE(srcadr)) + peer->dstadr = dstadr; + else + peer->dstadr = findinterface(srcadr); + peer->srcadr = *srcadr; + peer->hmode = (u_char)hmode; + peer->version = (u_char)version; + peer->minpoll = (u_char)max(NTP_MINPOLL, minpoll); + peer->maxpoll = (u_char)min(NTP_MAXPOLL, maxpoll); + peer->flags = flags; + if (key != 0) + peer->flags |= FLAG_AUTHENABLE; + if (key > NTP_MAXKEY) + peer->flags |= FLAG_SKEY; + peer->cast_flags = cast_flags; + peer->ttl = (u_char)ttl; + peer->keyid = key; + peer->precision = sys_precision; + if (cast_flags & MDF_ACAST) + peer_clear(peer, "ACST"); + else if (cast_flags & MDF_MCAST) + peer_clear(peer, "MCST"); + else if (cast_flags & MDF_BCAST) + peer_clear(peer, "BCST"); + else + peer_clear(peer, "INIT"); + if (mode_ntpdate) + peer_ntpdate++; + + /* + * Note time on statistics timers. + */ + peer->timereset = current_time; + peer->timereachable = current_time; + peer->timereceived = current_time; +#ifdef REFCLOCK + if (ISREFCLOCKADR(&peer->srcadr)) { + /* + * We let the reference clock support do clock + * dependent initialization. This includes setting + * the peer timer, since the clock may have requirements + * for this. + */ + if (!refclock_newpeer(peer)) { + /* + * Dump it, something screwed up + */ + peer->next = peer_free; + peer_free = peer; + peer_free_count++; + return (NULL); + } + } +#endif + + /* + * Put the new peer in the hash tables. + */ + i = HASH_ADDR(&peer->srcadr); + peer->next = peer_hash[i]; + peer_hash[i] = peer; + peer_hash_count[i]++; + i = peer->associd & HASH_MASK; + peer->ass_next = assoc_hash[i]; + assoc_hash[i] = peer; + assoc_hash_count[i]++; +#ifdef OPENSSL + if (peer->flags & FLAG_SKEY) { + sprintf(statstr, "newpeer %d", peer->associd); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("peer: %s\n", statstr); +#endif + } +#endif /* OPENSSL */ +#ifdef DEBUG + if (debug) + printf( + "newpeer: %s->%s mode %d vers %d poll %d %d flags 0x%x 0x%x ttl %d key %08x\n", + peer->dstadr == NULL ? "null" : stoa(&peer->dstadr->sin), + stoa(&peer->srcadr), + peer->hmode, peer->version, peer->minpoll, + peer->maxpoll, peer->flags, peer->cast_flags, + peer->ttl, peer->keyid); +#endif + return (peer); +} + + +/* + * peer_unconfig - remove the configuration bit from a peer + */ +int +peer_unconfig( + struct sockaddr_storage *srcadr, + struct interface *dstadr, + int mode + ) +{ + register struct peer *peer; + int num_found; + + num_found = 0; + peer = findexistingpeer(srcadr, (struct peer *)0, mode); + while (peer != 0) { + if (peer->flags & FLAG_CONFIG + && (dstadr == 0 || peer->dstadr == dstadr)) { + num_found++; + + /* + * Tricky stuff here. If the peer is polling us + * in active mode, turn off the configuration + * bit and make the mode passive. This allows us + * to avoid dumping a lot of history for peers + * we might choose to keep track of in passive + * mode. The protocol will eventually terminate + * undesirables on its own. + */ + if (peer->hmode == MODE_ACTIVE + && peer->pmode == MODE_ACTIVE) { + peer->hmode = MODE_PASSIVE; + peer->flags &= ~FLAG_CONFIG; + } else { + unpeer(peer); + peer = 0; + } + } + peer = findexistingpeer(srcadr, peer, mode); + } + return (num_found); +} + +/* + * peer_clr_stats - clear peer module stat counters + */ +void +peer_clr_stats(void) +{ + findpeer_calls = 0; + assocpeer_calls = 0; + peer_allocations = 0; + peer_demobilizations = 0; + peer_timereset = current_time; +} + +/* + * peer_reset - reset stat counters in a peer structure + */ +void +peer_reset( + struct peer *peer + ) +{ + if (peer == 0) + return; + peer->sent = 0; + peer->received = 0; + peer->processed = 0; + peer->badauth = 0; + peer->bogusorg = 0; + peer->oldpkt = 0; + peer->seldisptoolarge = 0; + peer->selbroken = 0; + peer->rank = 0; + peer->timereset = current_time; +} + + +/* + * peer_all_reset - reset all peer stat counters + */ +void +peer_all_reset(void) +{ + struct peer *peer; + int hash; + + for (hash = 0; hash < HASH_SIZE; hash++) + for (peer = peer_hash[hash]; peer != 0; peer = peer->next) + peer_reset(peer); +} + + +#ifdef OPENSSL +/* + * expire_all - flush all crypto data and update timestamps. + */ +void +expire_all(void) +{ + struct peer *peer, *next_peer; + int n; + + /* + * This routine is called about once per day from the timer + * routine and when the client is first synchronized. Search the + * peer list for all associations and flush only the key list + * and cookie. If a manycast client association, flush + * everything. Then, recompute and sign the agreement public + * value, if present. + */ + if (!crypto_flags) + return; + for (n = 0; n < HASH_SIZE; n++) { + for (peer = peer_hash[n]; peer != 0; peer = next_peer) { + next_peer = peer->next; + if (!(peer->flags & FLAG_SKEY)) { + continue; + } else if (peer->cast_flags & MDF_ACAST) { + peer_clear(peer, "ACST"); + } else if (peer->hmode == MODE_ACTIVE || + peer->hmode == MODE_PASSIVE) { + key_expire(peer); + peer->crypto &= ~(CRYPTO_FLAG_AUTO | + CRYPTO_FLAG_AGREE); + } + + } + } + RAND_bytes((u_char *)&sys_private, 4); + crypto_update(); + resetmanycast(); +} +#endif /* OPENSSL */ + + +/* + * findmanycastpeer - find and return a manycast peer + */ +struct peer * +findmanycastpeer( + struct recvbuf *rbufp + ) +{ + register struct peer *peer; + struct pkt *pkt; + l_fp p_org; + int i; + + /* + * This routine is called upon arrival of a client-mode message + * from a manycast server. Search the peer list for a manycast + * client association where the last transmit timestamp matches + * the originate timestamp. This assumes the transmit timestamps + * for possibly more than one manycast association are unique. + */ + pkt = &rbufp->recv_pkt; + for (i = 0; i < HASH_SIZE; i++) { + if (peer_hash_count[i] == 0) + continue; + + for (peer = peer_hash[i]; peer != 0; peer = + peer->next) { + if (peer->cast_flags & MDF_ACAST) { + NTOHL_FP(&pkt->org, &p_org); + if (L_ISEQU(&peer->xmt, &p_org)) + return (peer); + } + } + } + return (NULL); +} + + +/* + * resetmanycast - reset all manycast clients + */ +void +resetmanycast(void) +{ + register struct peer *peer; + int i; + + /* + * This routine is called when the number of client associations + * falls below the minimum. Search the peer list for manycast + * client associations and reset the ttl and poll interval. + */ + for (i = 0; i < HASH_SIZE; i++) { + if (peer_hash_count[i] == 0) + continue; + + for (peer = peer_hash[i]; peer != 0; peer = + peer->next) { + if (peer->cast_flags & MDF_ACAST) { + peer->ttl = 0; + poll_update(peer, 0); + } + } + } +} diff --git a/ntpd/ntp_proto.c b/ntpd/ntp_proto.c new file mode 100644 index 0000000..451bc9a --- /dev/null +++ b/ntpd/ntp_proto.c @@ -0,0 +1,3208 @@ +/* + * ntp_proto.c - NTP version 4 protocol machinery + * + * ATTENTION: Get approval from Dave Mills on all changes to this file! + * + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_stdlib.h" +#include "ntp_unixtime.h" +#include "ntp_control.h" +#include "ntp_string.h" + +#include <stdio.h> + +#if defined(VMS) && defined(VMS_LOCALUNIT) /*wjm*/ +#include "ntp_refclock.h" +#endif + +#if defined(__FreeBSD__) && __FreeBSD__ >= 3 +#include <sys/sysctl.h> +#endif + +/* + * System variables are declared here. See Section 3.2 of the + * specification. + */ +u_char sys_leap; /* system leap indicator */ +u_char sys_stratum; /* stratum of system */ +s_char sys_precision; /* local clock precision */ +double sys_rootdelay; /* roundtrip delay to primary source */ +double sys_rootdispersion; /* dispersion to primary source */ +u_int32 sys_refid; /* reference source for local clock */ +u_int32 sys_peer_refid; /* hashed refid of our current peer */ +static double sys_offset; /* current local clock offset */ +l_fp sys_reftime; /* time we were last updated */ +struct peer *sys_peer; /* our current peer */ +struct peer *sys_prefer; /* our cherished peer */ +int sys_kod; /* kod credit */ +int sys_kod_rate = 2; /* max kod packets per second */ +#ifdef OPENSSL +u_long sys_automax; /* maximum session key lifetime */ +#endif /* OPENSSL */ + +/* + * Nonspecified system state variables. + */ +int sys_bclient; /* broadcast client enable */ +double sys_bdelay; /* broadcast client default delay */ +int sys_calldelay; /* modem callup delay (s) */ +int sys_authenticate; /* requre authentication for config */ +l_fp sys_authdelay; /* authentication delay */ +static u_long sys_authdly[2]; /* authentication delay shift reg */ +static u_char leap_consensus; /* consensus of survivor leap bits */ +static double sys_selerr; /* select error (squares) */ +static double sys_syserr; /* system error (squares) */ +keyid_t sys_private; /* private value for session seed */ +int sys_manycastserver; /* respond to manycast client pkts */ +int peer_ntpdate; /* active peers in ntpdate mode */ +int sys_survivors; /* truest of the truechimers */ +#ifdef OPENSSL +char *sys_hostname; /* gethostname() name */ +#endif /* OPENSSL */ + +/* + * TOS and multicast mapping stuff + */ +int sys_floor = 1; /* cluster stratum floor */ +int sys_ceiling = STRATUM_UNSPEC; /* cluster stratum ceiling*/ +int sys_minsane = 1; /* minimum candidates */ +int sys_minclock = NTP_MINCLOCK; /* minimum survivors */ +int sys_cohort = 0; /* cohort switch */ +int sys_ttlmax; /* max ttl mapping vector index */ +u_char sys_ttl[MAX_TTL]; /* ttl mapping vector */ + +/* + * Statistics counters + */ +u_long sys_stattime; /* time since reset */ +u_long sys_received; /* packets received */ +u_long sys_processed; /* packets processed */ +u_long sys_newversionpkt; /* current version */ +u_long sys_oldversionpkt; /* recent version */ +u_long sys_unknownversion; /* invalid version */ +u_long sys_restricted; /* access denied */ +u_long sys_badlength; /* bad length or format */ +u_long sys_badauth; /* bad authentication */ +u_long sys_limitrejected; /* rate exceeded */ + +static double root_distance P((struct peer *)); +static double clock_combine P((struct peer **, int)); +static void peer_xmit P((struct peer *)); +static void fast_xmit P((struct recvbuf *, int, keyid_t, int)); +static void clock_update P((void)); +int default_get_precision P((void)); +static int peer_unfit P((struct peer *)); + +/* + * transmit - Transmit Procedure. See Section 3.4.2 of the + * specification. + */ +void +transmit( + struct peer *peer /* peer structure pointer */ + ) +{ + int hpoll; + + + /* + * The polling state machine. There are two kinds of machines, + * those that never expect a reply (broadcast and manycast + * server modes) and those that do (all other modes). The dance + * is intricate... + */ + hpoll = peer->hpoll; + if (peer->cast_flags & (MDF_BCAST | MDF_MCAST)) { + + /* + * In broadcast mode the poll interval is fixed + * at minpoll. + */ + hpoll = peer->minpoll; + } else if (peer->cast_flags & MDF_ACAST) { + + /* + * In manycast mode we start with the minpoll interval + * and ttl. However, the actual poll interval is eight + * times the nominal poll interval shown here. If fewer + * than sys_minclock servers are found, the ttl is + * increased by one and we try again. If this continues + * to the max ttl, the poll interval is bumped by one + * and we try again. If at least sys_minclock servers + * are found, the poll interval increases with the + * system poll interval to the max and we continue + * indefinately. However, about once per day when the + * agreement parameters are refreshed, the manycast + * clients are reset and we start from the beginning. + * This is to catch and clamp the ttl to the lowest + * practical value and avoid knocking on spurious doors. + */ + if (sys_survivors < sys_minclock && peer->ttl < + sys_ttlmax) + peer->ttl++; + hpoll = sys_poll; + } else { + + /* + * For associations expecting a reply, the watchdog + * counter is bumped by one if the peer has not been + * heard since the previous poll. If the counter reaches + * the max, the poll interval is doubled and the peer is + * demobilized if not configured. + */ + peer->unreach++; + if (peer->unreach >= NTP_UNREACH) { + hpoll++; + if (peer->flags & FLAG_CONFIG) { + + /* + * If nothing is likely to change in + * future, flash the access denied bit + * so we won't bother the dude again. + */ + if (memcmp((char *)&peer->refid, + "DENY", 4) == 0 || + memcmp((char *)&peer->refid, + "CRYP", 4) == 0) + peer->flash |= TEST4; + } else { + unpeer(peer); + return; + } + } + if (peer->burst == 0) { + u_char oreach; + + oreach = peer->reach; + peer->reach <<= 1; + peer->hyst *= HYST_TC; + if (peer->reach == 0) { + + /* + * If this association has become + * unreachable, clear it and raise a + * trap. + */ + if (oreach != 0) { + report_event(EVNT_UNREACH, + peer); + peer->timereachable = + current_time; + if (peer->flags & FLAG_CONFIG) { + peer_clear(peer, + "INIT"); + } else { + unpeer(peer); + return; + } + } + if (peer->flags & FLAG_IBURST) + peer->burst = NTP_BURST; + } else { + /* + * Here the peer is reachable. If it has + * not been heard for three consecutive + * polls, stuff the clock filter. Next, + * determine the poll interval. If the + * peer is unfit for synchronization, + * increase it by one; otherwise, use + * the system poll interval. + */ + if (!(peer->reach & 0x07)) { + clock_filter(peer, 0., 0., + MAXDISPERSE); + clock_select(); + } + if (peer_unfit(peer)) + hpoll++; + else + hpoll = sys_poll; + if (peer->flags & FLAG_BURST) + peer->burst = NTP_BURST; + } + } else { + + /* + * Source rate control. If we are restrained, + * each burst consists of only one packet. + */ + if (memcmp((char *)&peer->refid, "RSTR", 4) == + 0) + peer->burst = 0; + else + peer->burst--; + if (peer->burst == 0) { + /* + * If a broadcast client at this point, + * the burst has concluded, so we switch + * to client mode and purge the keylist, + * since no further transmissions will + * be made. + */ + if (peer->cast_flags & MDF_BCLNT) { + peer->hmode = MODE_BCLIENT; +#ifdef OPENSSL + key_expire(peer); +#endif /* OPENSSL */ + } + poll_update(peer, hpoll); + clock_select(); + + /* + * If ntpdate mode and the clock has not + * been set and all peers have completed + * the burst, we declare a successful + * failure. + */ + if (mode_ntpdate) { + peer_ntpdate--; + if (peer_ntpdate > 0) { + poll_update( + peer, hpoll); + return; + } + msyslog(LOG_NOTICE, + "no reply; clock not set"); + exit (0); + } + poll_update(peer, hpoll); + return; + } + } + } + peer->outdate = current_time; + + /* + * Do not transmit if in broadcast cclient mode or access has + * been denied. + */ + if (peer->hmode == MODE_BCLIENT || peer->flash & TEST4) { + poll_update(peer, hpoll); + return; + + /* + * Do not transmit in broadcast mode unless we are synchronized. + */ + } else if (peer->hmode == MODE_BROADCAST && sys_peer == NULL) { + poll_update(peer, hpoll); + return; + } + peer_xmit(peer); + poll_update(peer, hpoll); +} + +/* + * receive - Receive Procedure. See section 3.4.3 in the specification. + */ +void +receive( + struct recvbuf *rbufp + ) +{ + register struct peer *peer; /* peer structure pointer */ + register struct pkt *pkt; /* receive packet pointer */ + int hismode; /* packet mode */ + int restrict_mask; /* restrict bits */ + int has_mac; /* length of MAC field */ + int authlen; /* offset of MAC field */ + int is_authentic; /* cryptosum ok */ + keyid_t skeyid = 0; /* key ID */ + struct sockaddr_storage *dstadr_sin; /* active runway */ + struct peer *peer2; /* aux peer structure pointer */ + l_fp p_org; /* originate timestamp */ + l_fp p_xmt; /* transmit timestamp */ +#ifdef OPENSSL + keyid_t tkeyid = 0; /* temporary key ID */ + keyid_t pkeyid = 0; /* previous key ID */ + struct autokey *ap; /* autokey structure pointer */ + int rval; /* cookie snatcher */ +#endif /* OPENSSL */ + int retcode = AM_NOMATCH; + + /* + * Monitor the packet and get restrictions. Note that the packet + * length for control and private mode packets must be checked + * by the service routines. Note that no statistics counters are + * recorded for restrict violations, since these counters are in + * the restriction routine. Note the careful distinctions here + * between a packet with a format error and a packet that is + * simply discarded without prejudice. Some restrictions have to + * be handled later in order to generate a kiss-of-death packet. + */ + /* + * Bogus port check is before anything, since it probably + * reveals a clogging attack. + */ + sys_received++; + if (SRCPORT(&rbufp->recv_srcadr) == 0) { + sys_badlength++; + return; /* bogus port */ + } + ntp_monitor(rbufp); + restrict_mask = restrictions(&rbufp->recv_srcadr); +#ifdef DEBUG + if (debug > 1) + printf("receive: at %ld %s<-%s restrict %03x\n", + current_time, stoa(&rbufp->dstadr->sin), + stoa(&rbufp->recv_srcadr), restrict_mask); +#endif + if (restrict_mask & RES_IGNORE) { + sys_restricted++; + return; /* no anything */ + } + pkt = &rbufp->recv_pkt; + hismode = (int)PKT_MODE(pkt->li_vn_mode); + if (hismode == MODE_PRIVATE) { + if (restrict_mask & RES_NOQUERY) { + sys_restricted++; + return; /* no query private */ + } + process_private(rbufp, ((restrict_mask & + RES_NOMODIFY) == 0)); + return; + } + if (hismode == MODE_CONTROL) { + if (restrict_mask & RES_NOQUERY) { + sys_restricted++; + return; /* no query control */ + } + process_control(rbufp, restrict_mask); + return; + } + if (restrict_mask & RES_DONTSERVE) { + sys_restricted++; + return; /* no time */ + } + if (rbufp->recv_length < LEN_PKT_NOMAC) { + sys_badlength++; + return; /* runt packet */ + } + + /* + * Version check must be after the query packets, since they + * intentionally use early version. + */ + if (PKT_VERSION(pkt->li_vn_mode) == NTP_VERSION) { + sys_newversionpkt++; /* new version */ + } else if (!(restrict_mask & RES_VERSION) && + PKT_VERSION(pkt->li_vn_mode) >= NTP_OLDVERSION) { + sys_oldversionpkt++; /* previous version */ + } else { + sys_unknownversion++; + return; /* old version */ + } + + /* + * Figure out his mode and validate the packet. This has some + * legacy raunch that probably should be removed. In very early + * NTP versions mode 0 was equivalent to what later versions + * would interpret as client mode. + */ + if (hismode == MODE_UNSPEC) { + if (PKT_VERSION(pkt->li_vn_mode) == NTP_OLDVERSION) { + hismode = MODE_CLIENT; + } else { + sys_badlength++; + return; /* invalid mode */ + } + } + + /* + * Discard broadcast if not enabled as broadcast client. If + * Autokey, the wildcard interface cannot be used, so dump + * packets gettiing off the bus at that stop as well. This means + * that some systems with broken interface code, specifically + * Linux, will not work with Autokey. + */ + if (hismode == MODE_BROADCAST) { + if (!sys_bclient || restrict_mask & RES_NOPEER) { + sys_restricted++; + return; /* no client */ + } +#ifdef OPENSSL + if (crypto_flags && rbufp->dstadr == any_interface) { + sys_restricted++; + return; /* no client */ + } +#endif /* OPENSSL */ + } + + /* + * Parse the extension field if present. We figure out whether + * an extension field is present by measuring the MAC size. If + * the number of words following the packet header is 0 or 1, no + * MAC is present and the packet is not authenticated. If 1, the + * packet is a reply to a previous request that failed to + * authenticate. If 3, the packet is authenticated with DES; if + * 5, the packet is authenticated with MD5. If greater than 5, + * an extension field is present. If 2 or 4, the packet is a + * runt and goes poof! with a brilliant flash. + */ + authlen = LEN_PKT_NOMAC; + has_mac = rbufp->recv_length - authlen; + while (has_mac > 0) { + int temp; + + if (has_mac % 4 != 0 || has_mac < 0) { + sys_badlength++; + return; /* bad MAC length */ + } + if (has_mac == 1 * 4 || has_mac == 3 * 4 || has_mac == + MAX_MAC_LEN) { + skeyid = ntohl(((u_int32 *)pkt)[authlen / 4]); + break; + + } else if (has_mac > MAX_MAC_LEN) { + temp = ntohl(((u_int32 *)pkt)[authlen / 4]) & + 0xffff; + if (temp < 4 || temp > NTP_MAXEXTEN || temp % 4 + != 0) { + sys_badlength++; + return; /* bad MAC length */ + } + authlen += temp; + has_mac -= temp; + } else { + sys_badlength++; + return; /* bad MAC length */ + } + } +#ifdef OPENSSL + pkeyid = tkeyid = 0; +#endif /* OPENSSL */ + + /* + * We have tossed out as many buggy packets as possible early in + * the game to reduce the exposure to a clogging attack. Now we + * have to burn some cycles to find the association and + * authenticate the packet if required. Note that we burn only + * MD5 cycles, again to reduce exposure. There may be no + * matching association and that's okay. + * + * More on the autokey mambo. Normally the local interface is + * found when the association was mobilized with respect to a + * designated remote address. We assume packets arriving from + * the remote address arrive via this interface and the local + * address used to construct the autokey is the unicast address + * of the interface. However, if the sender is a broadcaster, + * the interface broadcast address is used instead. + * Notwithstanding this technobabble, if the sender is a + * multicaster, the broadcast address is null, so we use the + * unicast address anyway. Don't ask. + */ + peer = findpeer(&rbufp->recv_srcadr, rbufp->dstadr, rbufp->fd, + hismode, &retcode); + is_authentic = 0; + dstadr_sin = &rbufp->dstadr->sin; + if (has_mac == 0) { +#ifdef DEBUG + if (debug) + printf("receive: at %ld %s<-%s mode %d code %d\n", + current_time, stoa(&rbufp->dstadr->sin), + stoa(&rbufp->recv_srcadr), hismode, + retcode); +#endif + } else { +#ifdef OPENSSL + /* + * For autokey modes, generate the session key + * and install in the key cache. Use the socket + * broadcast or unicast address as appropriate. + */ + if (skeyid > NTP_MAXKEY) { + + /* + * More on the autokey dance (AKD). A cookie is + * constructed from public and private values. + * For broadcast packets, the cookie is public + * (zero). For packets that match no + * association, the cookie is hashed from the + * addresses and private value. For server + * packets, the cookie was previously obtained + * from the server. For symmetric modes, the + * cookie was previously constructed using an + * agreement protocol; however, should PKI be + * unavailable, we construct a fake agreement as + * the EXOR of the peer and host cookies. + * + * hismode ephemeral persistent + * ======================================= + * active 0 cookie# + * passive 0% cookie# + * client sys cookie 0% + * server 0% sys cookie + * broadcast 0 0 + * + * # if unsync, 0 + * % can't happen + */ + if (hismode == MODE_BROADCAST) { + + /* + * For broadcaster, use the interface + * broadcast address when available; + * otherwise, use the unicast address + * found when the association was + * mobilized. + */ + pkeyid = 0; + if (!SOCKNUL(&rbufp->dstadr->bcast)) + dstadr_sin = + &rbufp->dstadr->bcast; + } else if (peer == NULL) { + pkeyid = session_key( + &rbufp->recv_srcadr, dstadr_sin, 0, + sys_private, 0); + } else { + pkeyid = peer->pcookie; + } + + /* + * The session key includes both the public + * values and cookie. In case of an extension + * field, the cookie used for authentication + * purposes is zero. Note the hash is saved for + * use later in the autokey mambo. + */ + if (authlen > LEN_PKT_NOMAC && pkeyid != 0) { + session_key(&rbufp->recv_srcadr, + dstadr_sin, skeyid, 0, 2); + tkeyid = session_key( + &rbufp->recv_srcadr, dstadr_sin, + skeyid, pkeyid, 0); + } else { + tkeyid = session_key( + &rbufp->recv_srcadr, dstadr_sin, + skeyid, pkeyid, 2); + } + + } +#endif /* OPENSSL */ + + /* + * Compute the cryptosum. Note a clogging attack may + * succeed in bloating the key cache. If an autokey, + * purge it immediately, since we won't be needing it + * again. If the packet is authentic, it may mobilize an + * association. + */ + if (authdecrypt(skeyid, (u_int32 *)pkt, authlen, + has_mac)) { + is_authentic = 1; + restrict_mask &= ~RES_DONTTRUST; + } else { + sys_badauth++; + } +#ifdef OPENSSL + if (skeyid > NTP_MAXKEY) + authtrust(skeyid, 0); +#endif /* OPENSSL */ +#ifdef DEBUG + if (debug) + printf( + "receive: at %ld %s<-%s mode %d code %d keyid %08x len %d mac %d auth %d\n", + current_time, stoa(dstadr_sin), + stoa(&rbufp->recv_srcadr), hismode, retcode, + skeyid, authlen, has_mac, + is_authentic); +#endif + } + + /* + * The association matching rules are implemented by a set of + * routines and a table in ntp_peer.c. A packet matching an + * association is processed by that association. If not and + * certain conditions prevail, then an ephemeral association is + * mobilized: a broadcast packet mobilizes a broadcast client + * aassociation; a manycast server packet mobilizes a manycast + * client association; a symmetric active packet mobilizes a + * symmetric passive association. And, the adventure + * continues... + */ + switch (retcode) { + case AM_FXMIT: + + /* + * This is a client mode packet not matching a known + * association. If from a manycast client we run a few + * sanity checks before deciding to send a unicast + * server response. Otherwise, it must be a client + * request, so send a server response and go home. + */ + if (sys_manycastserver && (rbufp->dstadr->flags & + INT_MULTICAST)) { + + /* + * There is no reason to respond to a request if + * our time is worse than the manycaster or it + * has already synchronized to us. + */ + if (sys_peer == NULL || + PKT_TO_STRATUM(pkt->stratum) < + sys_stratum || (sys_cohort && + PKT_TO_STRATUM(pkt->stratum) == + sys_stratum) || + rbufp->dstadr->addr_refid == pkt->refid) + return; /* manycast dropped */ + } + + /* + * Note that we don't require an authentication check + * here, since we can't set the system clock; but, we do + * send a crypto-NAK to tell the caller about this. + */ + if (has_mac && !is_authentic) + fast_xmit(rbufp, MODE_SERVER, 0, restrict_mask); + else + fast_xmit(rbufp, MODE_SERVER, skeyid, + restrict_mask); + return; + + case AM_MANYCAST: + + /* + * This is a server mode packet returned in response to + * a client mode packet sent to a multicast group + * address. The originate timestamp is a good nonce to + * reliably associate the reply with what was sent. If + * there is no match, that's curious and could be an + * intruder attempting to clog, so we just ignore it. + * + * First, make sure the packet is authentic and not + * restricted. If so and the manycast association is + * found, we mobilize a client association and copy + * pertinent variables from the manycast association to + * the new client association. + * + * There is an implosion hazard at the manycast client, + * since the manycast servers send the server packet + * immediately. If the guy is already here, don't fire + * up a duplicate. + */ + if (restrict_mask & RES_DONTTRUST) { + sys_restricted++; + return; /* no trust */ + } + + if (sys_authenticate && !is_authentic) + return; /* bad auth */ + + if ((peer2 = findmanycastpeer(rbufp)) == NULL) + return; /* no assoc match */ + + if ((peer = newpeer(&rbufp->recv_srcadr, rbufp->dstadr, + MODE_CLIENT, PKT_VERSION(pkt->li_vn_mode), + NTP_MINDPOLL, NTP_MAXDPOLL, FLAG_IBURST, MDF_UCAST | + MDF_ACLNT, 0, skeyid)) == NULL) + return; /* system error */ + + /* + * We don't need these, but it warms the billboards. + */ + peer->ttl = peer2->ttl; + break; + + case AM_NEWPASS: + + /* + * This is the first packet received from a symmetric + * active peer. First, make sure it is authentic and not + * restricted. If so, mobilize a passive association. + * If authentication fails send a crypto-NAK; otherwise, + * kiss the frog. + */ + if (restrict_mask & RES_DONTTRUST) { + sys_restricted++; + return; /* no trust */ + } + if (sys_authenticate && !is_authentic) { + fast_xmit(rbufp, MODE_PASSIVE, 0, + restrict_mask); + return; /* bad auth */ + } + if ((peer = newpeer(&rbufp->recv_srcadr, rbufp->dstadr, + MODE_PASSIVE, PKT_VERSION(pkt->li_vn_mode), + NTP_MINDPOLL, NTP_MAXDPOLL, 0, MDF_UCAST, 0, + skeyid)) == NULL) + return; /* system error */ + + break; + + case AM_NEWBCL: + + /* + * This is the first packet received from a broadcast + * server. First, make sure it is authentic and not + * restricted and that we are a broadcast client. If so, + * mobilize a broadcast client association. We don't + * kiss any frogs here. + */ + if (restrict_mask & RES_DONTTRUST) { + sys_restricted++; + return; /* no trust */ + } + if (sys_authenticate && !is_authentic) + return; /* bad auth */ + + if (!sys_bclient) + return; /* not a client */ + + if ((peer = newpeer(&rbufp->recv_srcadr, rbufp->dstadr, + MODE_CLIENT, PKT_VERSION(pkt->li_vn_mode), + NTP_MINDPOLL, NTP_MAXDPOLL, FLAG_MCAST | + FLAG_IBURST, MDF_BCLNT, 0, skeyid)) == NULL) + return; /* system error */ +#ifdef OPENSSL + /* + * Danger looms. If this is autokey, go process the + * extension fields. If something goes wrong, abandon + * ship and don't trust subsequent packets. + */ + if (crypto_flags) { + if ((rval = crypto_recv(peer, rbufp)) != + XEVNT_OK) { + struct sockaddr_storage mskadr_sin; + + unpeer(peer); + sys_restricted++; + SET_HOSTMASK(&mskadr_sin, + rbufp->recv_srcadr.ss_family); + hack_restrict(RESTRICT_FLAGS, + &rbufp->recv_srcadr, &mskadr_sin, + 0, RES_DONTTRUST | RES_TIMEOUT); +#ifdef DEBUG + if (debug) + printf( + "packet: bad exten %x\n", + rval); +#endif + } + } +#endif /* OPENSSL */ + return; + + case AM_POSSBCL: + + /* + * This is a broadcast packet received in client mode. + * It could happen if the initial client/server volley + * is not complete before the next broadcast packet is + * received. Be liberal in what we accept. + */ + case AM_PROCPKT: + + /* + * This is a symmetric mode packet received in symmetric + * mode, a server packet received in client mode or a + * broadcast packet received in broadcast client mode. + * If it is restricted, this is very strange because it + * is rude to send a packet to a restricted address. If + * anyway, flash a restrain kiss and skedaddle to + * Seattle. If not authentic, leave a light on and + * continue. + */ + peer->flash = 0; + if (restrict_mask & RES_DONTTRUST) { + sys_restricted++; + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "RSTR"); + else + unpeer(peer); + return; /* no trust */ + } + if (has_mac && !is_authentic) + peer->flash |= TEST5; /* bad auth */ + break; + + default: + + /* + * Invalid mode combination. This happens when a passive + * mode packet arrives and matches another passive + * association or no association at all, or when a + * server mode packet arrives and matches a broadcast + * client association. This is usually the result of + * reconfiguring a client on-fly. If authenticated + * passive mode packet, send a crypto-NAK; otherwise, + * ignore it. + */ + if (has_mac && hismode == MODE_PASSIVE) + fast_xmit(rbufp, MODE_ACTIVE, 0, restrict_mask); +#ifdef DEBUG + if (debug) + printf("receive: bad protocol %d\n", retcode); +#endif + return; + } + + /* + * We do a little homework. Note we can get here with an + * authentication error. We Need to do this in order to validate + * a crypto-NAK later. Note the order of processing; it is very + * important to avoid livelocks, deadlocks and lockpicks. + */ + peer->timereceived = current_time; + peer->received++; + if (peer->flash & TEST5) + peer->flags &= ~FLAG_AUTHENTIC; + else + peer->flags |= FLAG_AUTHENTIC; + NTOHL_FP(&pkt->org, &p_org); + NTOHL_FP(&pkt->xmt, &p_xmt); + + /* + * If the packet is an old duplicate, we let it through so the + * extension fields will be processed. + */ + if (L_ISEQU(&peer->org, &p_xmt)) { /* test 1 */ + peer->flash |= TEST1; /* dupe */ + /* fall through */ + + /* + * For broadcast server mode, loopback checking is disabled. An + * authentication error probably means the server restarted or + * rolled a new private value. If so, dump the association + * and wait for the next message. + */ + } else if (hismode == MODE_BROADCAST) { + if (peer->flash & TEST5) { + unpeer(peer); + return; + } + /* fall through */ + + /* + * For server and symmetric modes, if the association transmit + * timestamp matches the packet originate timestamp, loopback is + * confirmed. Note in symmetric modes this also happens when the + * first packet from the active peer arrives at the newly + * mobilized passive peer. An authentication error probably + * means the server or peer restarted or rolled a new private + * value, but could be an intruder trying to stir up trouble. + * However, if this is a crypto-NAK, we know it is authentic, so + * dump the association and wait for the next message. + */ + } else if (L_ISEQU(&peer->xmt, &p_org)) { + if (peer->flash & TEST5) { + if (has_mac == 4 && pkt->exten[0] == 0) { + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "AUTH"); + else + unpeer(peer); + } + return; + } + /* fall through */ + + /* + * If the client or passive peer has never transmitted anything, + * this is either the first message from a symmetric peer or + * possibly a duplicate received before the transmit timeout. + * Pass it on. + */ + } else if (L_ISZERO(&peer->xmt)) { + /* fall through */ + + /* + * Now it gets interesting. We have transmitted at least one + * packet. If the packet originate timestamp is nonzero, it + * does not match the association transmit timestamp, which is a + * loopback error. This error might mean a manycast server has + * answered a manycast honk from us and we already have an + * association for him, in which case quietly drop the packet + * here. It might mean an old duplicate, dropped packet or + * intruder replay, in which case we drop it later after + * extension field processing, but never let it touch the time + * values. + */ + } else if (!L_ISZERO(&p_org)) { + if (peer->cast_flags & MDF_ACLNT) + return; /* not a client */ + + peer->flash |= TEST2; + /* fall through */ + + /* + * The packet originate timestamp is zero, meaning the other guy + * either didn't receive the first packet or died and restarted. + * If the association originate timestamp is zero, this is the + * first packet received, so we pass it on. + */ + } else if (L_ISZERO(&peer->org)) { + /* fall through */ + + /* + * The other guy has restarted and we are still on the wire. We + * should demobilize/clear and get out of Dodge. If this is + * symmetric mode, we should also send a crypto-NAK. + */ + } else { + if (hismode == MODE_ACTIVE) + fast_xmit(rbufp, MODE_PASSIVE, 0, + restrict_mask); + else if (hismode == MODE_PASSIVE) + fast_xmit(rbufp, MODE_ACTIVE, 0, restrict_mask); +#if DEBUG + if (debug) + printf("receive: dropped %03x\n", peer->flash); +#endif + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "DROP"); + else + unpeer(peer); + return; + } + if (peer->flash & ~TEST2) { + return; + } + +#ifdef OPENSSL + /* + * More autokey dance. The rules of the cha-cha are as follows: + * + * 1. If there is no key or the key is not auto, do nothing. + * + * 2. If this packet is in response to the one just previously + * sent or from a broadcast server, do the extension fields. + * Otherwise, assume bogosity and bail out. + * + * 3. If an extension field contains a verified signature, it is + * self-authenticated and we sit the dance. + * + * 4. If this is a server reply, check only to see that the + * transmitted key ID matches the received key ID. + * + * 5. Check to see that one or more hashes of the current key ID + * matches the previous key ID or ultimate original key ID + * obtained from the broadcaster or symmetric peer. If no + * match, sit the dance and wait for timeout. + */ + if (crypto_flags && (peer->flags & FLAG_SKEY)) { + peer->flash |= TEST10; + rval = crypto_recv(peer, rbufp); + if (rval != XEVNT_OK) { + /* fall through */ + + } else if (hismode == MODE_SERVER) { + if (skeyid == peer->keyid) + peer->flash &= ~TEST10; + } else if (!peer->flash & TEST10) { + peer->pkeyid = skeyid; + } else if ((ap = (struct autokey *)peer->recval.ptr) != + NULL) { + int i; + + for (i = 0; ; i++) { + if (tkeyid == peer->pkeyid || + tkeyid == ap->key) { + peer->flash &= ~TEST10; + peer->pkeyid = skeyid; + break; + } + if (i > ap->seq) + break; + tkeyid = session_key( + &rbufp->recv_srcadr, dstadr_sin, + tkeyid, pkeyid, 0); + } + } + if (!(peer->crypto & CRYPTO_FLAG_PROV)) /* test 11 */ + peer->flash |= TEST11; /* not proventic */ + + /* + * If the transmit queue is nonempty, clamp the host + * poll interval to the packet poll interval. + */ + if (peer->cmmd != 0) { + peer->ppoll = pkt->ppoll; + poll_update(peer, 0); + } + + /* + * If the return code from extension field processing is + * not okay, we scrub the association and start over. + */ + if (rval != XEVNT_OK) { + + /* + * If the return code is bad, the crypto machine + * may be jammed or an intruder may lurk. First, + * we demobilize the association, then see if + * the error is recoverable. + */ + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "CRYP"); + else + unpeer(peer); +#ifdef DEBUG + if (debug) + printf("packet: bad exten %x\n", rval); +#endif + return; + } + + /* + * If TEST10 is lit, the autokey sequence has broken, + * which probably means the server has refreshed its + * private value. We reset the poll interval to the + & minimum and scrub the association clean. + */ + if (peer->flash & TEST10 && peer->crypto & + CRYPTO_FLAG_AUTO) { + poll_update(peer, peer->minpoll); +#ifdef DEBUG + if (debug) + printf( + "packet: bad auto %03x\n", + peer->flash); +#endif + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "AUTO"); + else + unpeer(peer); + return; + } + } +#endif /* OPENSSL */ + + /* + * We have survived the gaunt. Forward to the packet routine. If + * a symmetric passive association has been mobilized and the + * association doesn't deserve to live, it will die in the + * transmit routine if not reachable after timeout. However, if + * either symmetric mode and the crypto code has something + * urgent to say, we expedite the response. + */ + process_packet(peer, pkt, &rbufp->recv_time); +} + + +/* + * process_packet - Packet Procedure, a la Section 3.4.4 of the + * specification. Or almost, at least. If we're in here we have a + * reasonable expectation that we will be having a long term + * relationship with this host. + */ +void +process_packet( + register struct peer *peer, + register struct pkt *pkt, + l_fp *recv_ts + ) +{ + l_fp t34, t21; + double p_offset, p_del, p_disp; + double dtemp; + l_fp p_rec, p_xmt, p_org, p_reftime; + l_fp ci; + u_char pmode, pleap, pstratum; + + /* + * Swap header fields and keep the books. The books amount to + * the receive timestamp and poll interval in the header. We + * need these even if there are other problems in order to crank + * up the state machine. + */ + sys_processed++; + peer->processed++; + p_del = FPTOD(NTOHS_FP(pkt->rootdelay)); + p_disp = FPTOD(NTOHS_FP(pkt->rootdispersion)); + NTOHL_FP(&pkt->reftime, &p_reftime); + NTOHL_FP(&pkt->rec, &p_rec); + NTOHL_FP(&pkt->xmt, &p_xmt); + pmode = PKT_MODE(pkt->li_vn_mode); + pleap = PKT_LEAP(pkt->li_vn_mode); + if (pmode != MODE_BROADCAST) + NTOHL_FP(&pkt->org, &p_org); + else + p_org = peer->rec; + pstratum = PKT_TO_STRATUM(pkt->stratum); + + /* + * Test for unsynchronized server. + */ + if (L_ISHIS(&peer->org, &p_xmt)) /* count old packets */ + peer->oldpkt++; + if (pmode != MODE_BROADCAST && (L_ISZERO(&p_rec) || + L_ISZERO(&p_org))) /* test 3 */ + peer->flash |= TEST3; /* unsynch */ + if (L_ISZERO(&p_xmt)) /* test 3 */ + peer->flash |= TEST3; /* unsynch */ + + /* + * If any tests fail, the packet is discarded leaving only the + * timestamps, which are enough to get the protocol started. The + * originate timestamp is copied from the packet transmit + * timestamp and the receive timestamp is copied from the + * packet receive timestamp. If okay so far, we save the leap, + * stratum and refid for billboards. + */ + peer->org = p_xmt; + peer->rec = *recv_ts; + if (peer->flash) { +#ifdef DEBUG + if (debug) + printf("packet: bad data %03x from address: %s\n", + peer->flash, stoa(&peer->srcadr)); +#endif + return; + } + peer->leap = pleap; + peer->stratum = pstratum; + peer->refid = pkt->refid; + + /* + * Test for valid peer data (tests 6-8) + */ + ci = p_xmt; + L_SUB(&ci, &p_reftime); + LFPTOD(&ci, dtemp); + if (pleap == LEAP_NOTINSYNC || /* test 6 */ + pstratum >= STRATUM_UNSPEC || dtemp < 0) + peer->flash |= TEST6; /* bad synch */ + if (!(peer->flags & FLAG_CONFIG) && sys_peer != NULL) { /* test 7 */ + if (pstratum > sys_stratum && pmode != MODE_ACTIVE) + peer->flash |= TEST7; /* bad stratum */ + } + if (p_del < 0 || p_disp < 0 || p_del / /* test 8 */ + 2 + p_disp >= MAXDISPERSE) + peer->flash |= TEST8; /* bad peer values */ + + /* + * If any tests fail at this point, the packet is discarded. + */ + if (peer->flash) { +#ifdef DEBUG + if (debug) + printf("packet: bad header %03x\n", + peer->flash); +#endif + return; + } + + /* + * The header is valid. Capture the remaining header values and + * mark as reachable. + */ + record_raw_stats(&peer->srcadr, &peer->dstadr->sin, &p_org, + &p_rec, &p_xmt, &peer->rec); + peer->pmode = pmode; + peer->ppoll = pkt->ppoll; + peer->precision = pkt->precision; + peer->rootdelay = p_del; + peer->rootdispersion = p_disp; + peer->reftime = p_reftime; + if (!(peer->reach)) { + report_event(EVNT_REACH, peer); + peer->timereachable = current_time; + } + peer->reach |= 1; + peer->unreach = 0; + poll_update(peer, 0); + + /* + * If running in a client/server association, calculate the + * clock offset c, roundtrip delay d and dispersion e. We use + * the equations (reordered from those in the spec). Note that, + * in a broadcast association, org has been set to the time of + * last reception. Note the computation of dispersion includes + * the system precision plus that due to the frequency error + * since the originate time. + * + * Let t1 = p_org, t2 = p_rec, t3 = p_xmt, t4 = peer->rec: + */ + t34 = p_xmt; /* t3 - t4 */ + L_SUB(&t34, &peer->rec); + t21 = p_rec; /* t2 - t1 */ + L_SUB(&t21, &p_org); + ci = peer->rec; /* t4 - t1 */ + L_SUB(&ci, &p_org); + LFPTOD(&ci, p_disp); + p_disp = clock_phi * max(p_disp, LOGTOD(sys_precision)); + + /* + * If running in a broadcast association, the clock offset is + * (t1 - t0) corrected by the one-way delay, but we can't + * measure that directly. Therefore, we start up in MODE_CLIENT + * mode, set FLAG_MCAST and exchange eight messages to determine + * the clock offset. When the last message is sent, we switch to + * MODE_BCLIENT mode. The next broadcast message after that + * computes the broadcast offset and clears FLAG_MCAST. + */ + ci = t34; + if (pmode == MODE_BROADCAST) { + if (peer->flags & FLAG_MCAST) { + LFPTOD(&ci, p_offset); + peer->estbdelay = peer->offset - p_offset; + if (peer->hmode == MODE_CLIENT) + return; + + peer->flags &= ~FLAG_MCAST; + } + DTOLFP(peer->estbdelay, &t34); + L_ADD(&ci, &t34); + p_del = peer->delay; + } else { + L_ADD(&ci, &t21); /* (t2 - t1) + (t3 - t4) */ + L_RSHIFT(&ci); + L_SUB(&t21, &t34); /* (t2 - t1) - (t3 - t4) */ + LFPTOD(&t21, p_del); + } + p_del = max(p_del, LOGTOD(sys_precision)); + LFPTOD(&ci, p_offset); + if ((peer->rootdelay + p_del) / 2. + peer->rootdispersion + + p_disp >= MAXDISPERSE) /* test 9 */ + peer->flash |= TEST9; /* bad root distance */ + + /* + * If any flasher bits remain set at this point, abandon ship. + * Otherwise, forward to the clock filter. + */ + if (peer->flash) { +#ifdef DEBUG + if (debug) + printf("packet: bad packet data %03x\n", + peer->flash); +#endif + return; + } + clock_filter(peer, p_offset, p_del, p_disp); + clock_select(); + record_peer_stats(&peer->srcadr, ctlpeerstatus(peer), + peer->offset, peer->delay, peer->disp, + SQRT(peer->jitter)); +} + + +/* + * clock_update - Called at system process update intervals. + */ +static void +clock_update(void) +{ + u_char oleap; + u_char ostratum; + + /* + * Reset/adjust the system clock. Do this only if there is a + * system peer and the peer epoch is not older than the last + * update. + */ + if (sys_peer == NULL) + return; + if (sys_peer->epoch <= last_time) + return; +#ifdef DEBUG + if (debug) + printf("clock_update: at %ld assoc %d \n", current_time, + peer_associations); +#endif + oleap = sys_leap; + ostratum = sys_stratum; + switch (local_clock(sys_peer, sys_offset, sys_syserr)) { + + /* + * Clock is too screwed up. Just exit for now. + */ + case -1: + report_event(EVNT_SYSFAULT, NULL); + exit (-1); + /*NOTREACHED*/ + + /* + * Clock was stepped. Flush all time values of all peers. + */ + case 1: + clear_all(); + sys_peer = NULL; + sys_stratum = STRATUM_UNSPEC; + memcpy(&sys_refid, "STEP", 4); + sys_poll = NTP_MINPOLL; + report_event(EVNT_CLOCKRESET, NULL); +#ifdef OPENSSL + if (oleap != LEAP_NOTINSYNC) + expire_all(); +#endif /* OPENSSL */ + break; + + /* + * Update the system stratum, leap bits, root delay, root + * dispersion, reference ID and reference time. We also update + * select dispersion and max frequency error. If the leap + * changes, we gotta reroll the keys. + */ + default: + sys_stratum = (u_char) (sys_peer->stratum + 1); + if (sys_stratum == 1 || sys_stratum == STRATUM_UNSPEC) + sys_refid = sys_peer->refid; + else + sys_refid = sys_peer_refid; + sys_reftime = sys_peer->rec; + sys_rootdelay = sys_peer->rootdelay + sys_peer->delay; + sys_leap = leap_consensus; + if (oleap == LEAP_NOTINSYNC) { + report_event(EVNT_SYNCCHG, NULL); +#ifdef OPENSSL + expire_all(); +#endif /* OPENSSL */ + } + } + if (ostratum != sys_stratum) + report_event(EVNT_PEERSTCHG, NULL); +} + + +/* + * poll_update - update peer poll interval + */ +void +poll_update( + struct peer *peer, + int hpoll + ) +{ +#ifdef OPENSSL + int oldpoll; +#endif /* OPENSSL */ + + /* + * A little foxtrot to determine what controls the poll + * interval. If the peer is reachable, but the last four polls + * have not been answered, use the minimum. If declared + * truechimer, use the system poll interval. This allows each + * association to ramp up the poll interval for useless sources + * and to clamp it to the minimum when first starting up. + */ +#ifdef OPENSSL + oldpoll = peer->kpoll; +#endif /* OPENSSL */ + if (hpoll > 0) { + if (hpoll > peer->maxpoll) + peer->hpoll = peer->maxpoll; + else if (hpoll < peer->minpoll) + peer->hpoll = peer->minpoll; + else + peer->hpoll = (u_char)hpoll; + } + + /* + * Bit of adventure here. If during a burst and not a poll, just + * slink away. If a poll, figure what the next poll should be. + * If a burst is pending and a reference clock or a pending + * crypto response, delay for one second. If the first sent in a + * burst, delay ten seconds for the modem to come up. For others + * in the burst, delay two seconds. + * + * In case of manycast server, make the poll interval, which is + * axtually the manycast beacon interval, eight times the system + * poll interval. Normally when the host poll interval settles + * up to 1024 s, the beacon interval settles up to 2.3 hours. + */ +#ifdef OPENSSL + if (peer->cmmd != NULL && (sys_leap != LEAP_NOTINSYNC || + peer->crypto)) { + peer->nextdate = current_time + RESP_DELAY; + } else if (peer->burst > 0) { +#else /* OPENSSL */ + if (peer->burst > 0) { +#endif /* OPENSSL */ + if (hpoll == 0 && peer->nextdate != current_time) + return; +#ifdef REFCLOCK + else if (peer->flags & FLAG_REFCLOCK) + peer->nextdate += RESP_DELAY; +#endif + else if (peer->flags & (FLAG_IBURST | FLAG_BURST) && + peer->burst == NTP_BURST) + peer->nextdate += sys_calldelay; + else + peer->nextdate += BURST_DELAY; + } else if (peer->cast_flags & MDF_ACAST) { + if (sys_survivors >= sys_minclock || peer->ttl >= + sys_ttlmax) + peer->kpoll = (u_char) (peer->hpoll + 3); + else + peer->kpoll = peer->hpoll; + peer->nextdate = peer->outdate + RANDPOLL(peer->kpoll); + } else { + peer->kpoll = (u_char) max(min(peer->ppoll, + peer->hpoll), peer->minpoll); + peer->nextdate = peer->outdate + RANDPOLL(peer->kpoll); + } + if (peer->nextdate < current_time) + peer->nextdate = current_time; +#ifdef OPENSSL + /* + * Bit of crass arrogance at this point. If the poll interval + * has changed and we have a keylist, the lifetimes in the + * keylist are probably bogus. In this case purge the keylist + * and regenerate it later. + */ + if (peer->kpoll != oldpoll) + key_expire(peer); +#endif /* OPENSSL */ +#ifdef DEBUG + if (debug > 1) + printf("poll_update: at %lu %s flags %04x poll %d burst %d last %lu next %lu\n", + current_time, ntoa(&peer->srcadr), peer->flags, + peer->kpoll, peer->burst, peer->outdate, + peer->nextdate); +#endif +} + + +/* + * clear - clear peer filter registers. See Section 3.4.8 of the spec. + */ +void +peer_clear( + struct peer *peer, /* peer structure */ + char *ident /* tally lights */ + ) +{ + u_char oreach, i; + + /* + * If cryptographic credentials have been acquired, toss them to + * Valhalla. Note that autokeys are ephemeral, in that they are + * tossed immediately upon use. Therefore, the keylist can be + * purged anytime without needing to preserve random keys. Note + * that, if the peer is purged, the cryptographic variables are + * purged, too. This makes it much harder to sneak in some + * unauthenticated data in the clock filter. + */ + oreach = peer->reach; +#ifdef OPENSSL + key_expire(peer); + if (peer->pkey != NULL) + EVP_PKEY_free(peer->pkey); + if (peer->ident_pkey != NULL) + EVP_PKEY_free(peer->ident_pkey); + if (peer->subject != NULL) + free(peer->subject); + if (peer->issuer != NULL) + free(peer->issuer); + if (peer->iffval != NULL) + BN_free(peer->iffval); + if (peer->grpkey != NULL) + BN_free(peer->grpkey); + if (peer->cmmd != NULL) + free(peer->cmmd); + value_free(&peer->cookval); + value_free(&peer->recval); + value_free(&peer->tai_leap); + value_free(&peer->encrypt); + value_free(&peer->sndval); +#endif /* OPENSSL */ + + /* + * Wipe the association clean and initialize the nonzero values. + */ + memset(CLEAR_TO_ZERO(peer), 0, LEN_CLEAR_TO_ZERO); + if (peer == sys_peer) + sys_peer = NULL; + peer->estbdelay = sys_bdelay; + peer->hpoll = peer->kpoll = peer->minpoll; + peer->ppoll = peer->maxpoll; + peer->jitter = MAXDISPERSE; + peer->epoch = current_time; +#ifdef REFCLOCK + if (!(peer->flags & FLAG_REFCLOCK)) { + peer->leap = LEAP_NOTINSYNC; + peer->stratum = STRATUM_UNSPEC; + memcpy(&peer->refid, ident, 4); + } +#else + peer->leap = LEAP_NOTINSYNC; + peer->stratum = STRATUM_UNSPEC; + memcpy(&peer->refid, ident, 4); +#endif + for (i = 0; i < NTP_SHIFT; i++) { + peer->filter_order[i] = i; + peer->filter_disp[i] = MAXDISPERSE; + peer->filter_epoch[i] = current_time; + } + + /* + * If he dies as a broadcast client, he comes back to life as + * a broadcast client in client mode in order to recover the + * initial autokey values. + */ + if (peer->cast_flags & MDF_BCLNT) { + peer->flags |= FLAG_MCAST; + peer->hmode = MODE_CLIENT; + } + + /* + * Randomize the first poll to avoid bunching, but only if the + * rascal has never been heard. During initialization use the + * association count to spread out the polls at one-second + * intervals. + */ + peer->nextdate = peer->update = peer->outdate = current_time; + peer->burst = 0; + if (oreach) + poll_update(peer, 0); + else if (initializing) + peer->nextdate = current_time + peer_associations; + else + peer->nextdate = current_time + (u_int)RANDOM % + peer_associations; +#ifdef DEBUG + if (debug) + printf("peer_clear: at %ld assoc ID %d refid %s\n", + current_time, peer->associd, ident); +#endif +} + + +/* + * clock_filter - add incoming clock sample to filter register and run + * the filter procedure to find the best sample. + */ +void +clock_filter( + struct peer *peer, /* peer structure pointer */ + double sample_offset, /* clock offset */ + double sample_delay, /* roundtrip delay */ + double sample_disp /* dispersion */ + ) +{ + double dst[NTP_SHIFT]; /* distance vector */ + int ord[NTP_SHIFT]; /* index vector */ + int i, j, k, m; + double dsp, jit, dtemp, etemp; + + /* + * Shift the new sample into the register and discard the oldest + * one. The new offset and delay come directly from the + * timestamp calculations. The dispersion grows from the last + * outbound packet or reference clock update to the present time + * and increased by the sum of the peer precision and the system + * precision. The delay can sometimes swing negative due to + * frequency skew, so it is clamped non-negative. + */ + dsp = min(LOGTOD(peer->precision) + LOGTOD(sys_precision) + + sample_disp, MAXDISPERSE); + j = peer->filter_nextpt; + peer->filter_offset[j] = sample_offset; + peer->filter_delay[j] = max(0, sample_delay); + peer->filter_disp[j] = dsp; + j++; j %= NTP_SHIFT; + peer->filter_nextpt = (u_short) j; + + /* + * Update dispersions since the last update and at the same + * time initialize the distance and index lists. The distance + * list uses a compound metric. If the sample is valid and + * younger than the minimum Allan intercept, use delay; + * otherwise, use biased dispersion. + */ + dtemp = clock_phi * (current_time - peer->update); + peer->update = current_time; + for (i = NTP_SHIFT - 1; i >= 0; i--) { + if (i != 0) + peer->filter_disp[j] += dtemp; + if (peer->filter_disp[j] >= MAXDISPERSE) + peer->filter_disp[j] = MAXDISPERSE; + if (peer->filter_disp[j] >= MAXDISPERSE) + dst[i] = MAXDISPERSE; + else if (peer->update - peer->filter_epoch[j] > + allan_xpt) + dst[i] = MAXDISTANCE + peer->filter_disp[j]; + else + dst[i] = peer->filter_delay[j]; + ord[i] = j; + j++; j %= NTP_SHIFT; + } + peer->filter_epoch[j] = current_time; + + /* + * Sort the samples in both lists by distance. + */ + for (i = 1; i < NTP_SHIFT; i++) { + for (j = 0; j < i; j++) { + if (dst[j] > dst[i]) { + k = ord[j]; + ord[j] = ord[i]; + ord[i] = k; + etemp = dst[j]; + dst[j] = dst[i]; + dst[i] = etemp; + } + } + } + + /* + * Copy the index list to the association structure so ntpq + * can see it later. Prune the distance list to samples less + * than MAXDISTANCE, but keep at least two valid samples for + * jitter calculation. + */ + m = 0; + for (i = 0; i < NTP_SHIFT; i++) { + peer->filter_order[i] = (u_char) ord[i]; + if (dst[i] >= MAXDISPERSE || (m >= 2 && dst[i] >= + MAXDISTANCE)) + continue; + m++; + } + + /* + * Compute the dispersion and jitter squares. The dispersion + * is weighted exponentially by NTP_FWEIGHT (0.5) so it is + * normalized close to 1.0. The jitter is the mean of the square + * differences relative to the lowest delay sample. If no + * acceptable samples remain in the shift register, quietly + * tiptoe home leaving only the dispersion. + */ + jit = 0; + peer->disp = 0; + k = ord[0]; + for (i = NTP_SHIFT - 1; i >= 0; i--) { + + j = ord[i]; + peer->disp = NTP_FWEIGHT * (peer->disp + + peer->filter_disp[j]); + if (i < m) + jit += DIFF(peer->filter_offset[j], + peer->filter_offset[k]); + } + + /* + * If no acceptable samples remain in the shift register, + * quietly tiptoe home leaving only the dispersion. Otherwise, + * save the offset, delay and jitter average. Note the jitter + * must not be less than the system precision. + */ + if (m == 0) + return; + etemp = fabs(peer->offset - peer->filter_offset[k]); + dtemp = sqrt(peer->jitter); + peer->offset = peer->filter_offset[k]; + peer->delay = peer->filter_delay[k]; + if (m > 1) + jit /= m - 1; + peer->jitter = max(jit, SQUARE(LOGTOD(sys_precision))); + + /* + * A new sample is useful only if it is younger than the last + * one used, but only if the sucker has been synchronized. + */ + if (peer->filter_epoch[k] <= peer->epoch && sys_leap != + LEAP_NOTINSYNC) { +#ifdef DEBUG + if (debug) + printf("clock_filter: discard %lu\n", + peer->epoch - peer->filter_epoch[k]); +#endif + return; + } + + /* + * If the difference between the last offset and the current one + * exceeds the jitter by CLOCK_SGATE and the interval since the + * last update is less than twice the system poll interval, + * consider the update a popcorn spike and ignore it. + */ + if (m > 1 && etemp > CLOCK_SGATE * dtemp && + (long)(peer->filter_epoch[k] - peer->epoch) < (1 << (sys_poll + + 1))) { +#ifdef DEBUG + if (debug) + printf("clock_filter: popcorn %.6f %.6f\n", + etemp, dtemp); +#endif + return; + } + + /* + * The mitigated sample statistics are saved for later + * processing. + */ + peer->epoch = peer->filter_epoch[k]; +#ifdef DEBUG + if (debug) + printf( + "clock_filter: n %d off %.6f del %.6f dsp %.6f jit %.6f, age %lu\n", + m, peer->offset, peer->delay, peer->disp, + SQRT(peer->jitter), peer->update - peer->epoch); +#endif +} + + +/* + * clock_select - find the pick-of-the-litter clock + * + * LOCKCLOCK: If the local clock is the prefer peer, it will always be + * enabled, even if declared falseticker, (2) only the prefer peer can + * be selected as the system peer, (3) if the external source is down, + * the system leap bits are set to 11 and the stratum set to infinity. + */ +void +clock_select(void) +{ + struct peer *peer; + int i, j, k, n; + int nlist, nl3; + + double d, e, f; + int allow, sw, osurv; + double high, low; + double synch[NTP_MAXCLOCK], error[NTP_MAXCLOCK]; + struct peer *osys_peer; + struct peer *typeacts = NULL; + struct peer *typelocal = NULL; + struct peer *typepps = NULL; + struct peer *typesystem = NULL; + + static int list_alloc = 0; + static struct endpoint *endpoint = NULL; + static int *indx = NULL; + static struct peer **peer_list = NULL; + static u_int endpoint_size = 0; + static u_int indx_size = 0; + static u_int peer_list_size = 0; + + /* + * Initialize and create endpoint, index and peer lists big + * enough to handle all associations. + */ + osys_peer = sys_peer; + sys_peer = NULL; + osurv = sys_survivors; + sys_survivors = 0; + sys_prefer = NULL; +#ifdef LOCKCLOCK + sys_leap = LEAP_NOTINSYNC; + sys_stratum = STRATUM_UNSPEC; + memcpy(&sys_refid, "DOWN", 4); +#endif /* LOCKCLOCK */ + nlist = 0; + for (n = 0; n < HASH_SIZE; n++) + nlist += peer_hash_count[n]; + if (nlist > list_alloc) { + if (list_alloc > 0) { + free(endpoint); + free(indx); + free(peer_list); + } + while (list_alloc < nlist) { + list_alloc += 5; + endpoint_size += 5 * 3 * sizeof(*endpoint); + indx_size += 5 * 3 * sizeof(*indx); + peer_list_size += 5 * sizeof(*peer_list); + } + endpoint = emalloc(endpoint_size); + indx = emalloc(indx_size); + peer_list = emalloc(peer_list_size); + } + + /* + * Initially, we populate the island with all the rifraff peers + * that happen to be lying around. Those with seriously + * defective clocks are immediately booted off the island. Then, + * the falsetickers are culled and put to sea. The truechimers + * remaining are subject to repeated rounds where the most + * unpopular at each round is kicked off. When the population + * has dwindled to sys_minclock, the survivors split a million + * bucks and collectively crank the chimes. + */ + nlist = nl3 = 0; /* none yet */ + for (n = 0; n < HASH_SIZE; n++) { + for (peer = peer_hash[n]; peer != NULL; peer = + peer->next) { + peer->flags &= ~FLAG_SYSPEER; + peer->status = CTL_PST_SEL_REJECT; + + /* + * Leave the island immediately if the peer is + * unfit to synchronize. + */ + if (peer_unfit(peer)) + continue; + + /* + * Don't allow the local clock or modem drivers + * in the kitchen at this point, unless the + * prefer peer. Do that later, but only if + * nobody else is around. These guys are all + * configured, so we never throw them away. + */ + if (peer->refclktype == REFCLK_LOCALCLOCK +#if defined(VMS) && defined(VMS_LOCALUNIT) + /* wjm: VMS_LOCALUNIT taken seriously */ + && REFCLOCKUNIT(&peer->srcadr) != + VMS_LOCALUNIT +#endif /* VMS && VMS_LOCALUNIT */ + ) { + typelocal = peer; + if (!(peer->flags & FLAG_PREFER)) + continue; /* no local clock */ +#ifdef LOCKCLOCK + else + sys_prefer = peer; +#endif /* LOCKCLOCK */ + } + if (peer->sstclktype == CTL_SST_TS_TELEPHONE) { + typeacts = peer; + if (!(peer->flags & FLAG_PREFER)) + continue; /* no acts */ + } + + /* + * If we get this far, the peer can stay on the + * island, but does not yet have the immunity + * idol. + */ + peer->status = CTL_PST_SEL_SANE; + peer_list[nlist++] = peer; + + /* + * Insert each interval endpoint on the sorted + * list. + */ + e = peer->offset; /* Upper end */ + f = root_distance(peer); + e = e + f; + for (i = nl3 - 1; i >= 0; i--) { + if (e >= endpoint[indx[i]].val) + break; + indx[i + 3] = indx[i]; + } + indx[i + 3] = nl3; + endpoint[nl3].type = 1; + endpoint[nl3++].val = e; + + e = e - f; /* Center point */ + for (; i >= 0; i--) { + if (e >= endpoint[indx[i]].val) + break; + indx[i + 2] = indx[i]; + } + indx[i + 2] = nl3; + endpoint[nl3].type = 0; + endpoint[nl3++].val = e; + + e = e - f; /* Lower end */ + for (; i >= 0; i--) { + if (e >= endpoint[indx[i]].val) + break; + indx[i + 1] = indx[i]; + } + indx[i + 1] = nl3; + endpoint[nl3].type = -1; + endpoint[nl3++].val = e; + } + } +#ifdef DEBUG + if (debug > 2) + for (i = 0; i < nl3; i++) + printf("select: endpoint %2d %.6f\n", + endpoint[indx[i]].type, + endpoint[indx[i]].val); +#endif + /* + * This is the actual algorithm that cleaves the truechimers + * from the falsetickers. The original algorithm was described + * in Keith Marzullo's dissertation, but has been modified for + * better accuracy. + * + * Briefly put, we first assume there are no falsetickers, then + * scan the candidate list first from the low end upwards and + * then from the high end downwards. The scans stop when the + * number of intersections equals the number of candidates less + * the number of falsetickers. If this doesn't happen for a + * given number of falsetickers, we bump the number of + * falsetickers and try again. If the number of falsetickers + * becomes equal to or greater than half the number of + * candidates, the Albanians have won the Byzantine wars and + * correct synchronization is not possible. + * + * Here, nlist is the number of candidates and allow is the + * number of falsetickers. + */ + low = 1e9; + high = -1e9; + for (allow = 0; 2 * allow < nlist; allow++) { + int found; + + /* + * Bound the interval (low, high) as the largest + * interval containing points from presumed truechimers. + */ + found = 0; + n = 0; + for (i = 0; i < nl3; i++) { + low = endpoint[indx[i]].val; + n -= endpoint[indx[i]].type; + if (n >= nlist - allow) + break; + if (endpoint[indx[i]].type == 0) + found++; + } + n = 0; + for (j = nl3 - 1; j >= 0; j--) { + high = endpoint[indx[j]].val; + n += endpoint[indx[j]].type; + if (n >= nlist - allow) + break; + if (endpoint[indx[j]].type == 0) + found++; + } + + /* + * If the number of candidates found outside the + * interval is greater than the number of falsetickers, + * then at least one truechimer is outside the interval, + * so go around again. This is what makes this algorithm + * different than Marzullo's. + */ + if (found > allow) + continue; + + /* + * If an interval containing truechimers is found, stop. + * If not, increase the number of falsetickers and go + * around again. + */ + if (high > low) + break; + } + + /* + * If no survivors remain at this point, check if the local + * clock or modem drivers have been found. If so, nominate one + * of them as the only survivor. Otherwise, give up and leave + * the island to the rats. + */ + if (high <= low) { + if (typeacts != 0) { + typeacts->status = CTL_PST_SEL_SANE; + peer_list[0] = typeacts; + nlist = 1; + } else if (typelocal != 0) { + typelocal->status = CTL_PST_SEL_SANE; + peer_list[0] = typelocal; + nlist = 1; + } else { + if (osys_peer != NULL) { + sys_poll = NTP_MINPOLL; + NLOG(NLOG_SYNCSTATUS) + msyslog(LOG_INFO, + "no servers reachable"); + report_event(EVNT_PEERSTCHG, NULL); + } + if (osurv > 0) + resetmanycast(); + return; + } + } + + /* + * We can only trust the survivors if the number of candidates + * sys_minsane is at least the number required to detect and + * cast out one falsticker. For the Byzantine agreement + * algorithm used here, that number is 4; however, the default + * sys_minsane is 1 to speed initial synchronization. Careful + * operators will tinker the value to 4 and use at least that + * number of synchronization sources. + */ + if (nlist < sys_minsane) + return; + + /* + * Clustering algorithm. Construct candidate list in order first + * by stratum then by root distance, but keep only the best + * NTP_MAXCLOCK of them. Scan the list to find falsetickers, who + * leave the island immediately. If a falseticker is not + * configured, his association raft is drowned as well, but only + * if at at least eight poll intervals have gone. We must leave + * at least one peer to collect the million bucks. + * + * Note the hysteresis gimmick that increases the effective + * distance for those rascals that have not made the final cut. + * This is to discourage clockhopping. Note also the prejudice + * against lower stratum peers if the floor is elevated. + */ + j = 0; + for (i = 0; i < nlist; i++) { + peer = peer_list[i]; + if (nlist > 1 && (peer->offset <= low || peer->offset >= + high)) { + if (!(peer->flags & FLAG_CONFIG)) + unpeer(peer); + continue; + } + peer->status = CTL_PST_SEL_DISTSYSPEER; + d = peer->stratum; + if (d < sys_floor) + d += sys_floor; + if (d > sys_ceiling) + d = STRATUM_UNSPEC; + d = root_distance(peer) + d * MAXDISTANCE; + d *= 1. - peer->hyst; + if (j >= NTP_MAXCLOCK) { + if (d >= synch[j - 1]) + continue; + else + j--; + } + for (k = j; k > 0; k--) { + if (d >= synch[k - 1]) + break; + peer_list[k] = peer_list[k - 1]; + error[k] = error[k - 1]; + synch[k] = synch[k - 1]; + } + peer_list[k] = peer; + error[k] = peer->jitter; + synch[k] = d; + j++; + } + nlist = j; + if (nlist == 0) { +#ifdef DEBUG + if (debug) + printf("clock_select: empty intersection interval\n"); +#endif + return; + } + for (i = 0; i < nlist; i++) { + peer_list[i]->status = CTL_PST_SEL_SELCAND; + +#ifdef DEBUG + if (debug > 2) + printf("select: %s distance %.6f jitter %.6f\n", + ntoa(&peer_list[i]->srcadr), synch[i], + SQRT(error[i])); +#endif + } + + /* + * Now, vote outlyers off the island by select jitter weighted + * by root dispersion. Continue voting as long as there are more + * than sys_minclock survivors and the minimum select jitter + * squared is greater than the maximum peer jitter squared. Stop + * if we are about to discard a prefer peer, who of course has + * the immunity idol. + */ + while (1) { + d = 1e9; + e = -1e9; + k = 0; + for (i = 0; i < nlist; i++) { + if (error[i] < d) + d = error[i]; + f = 0; + if (nlist > 1) { + for (j = 0; j < nlist; j++) + f += DIFF(peer_list[j]->offset, + peer_list[i]->offset); + f /= nlist - 1; + } + if (f * synch[i] > e) { + sys_selerr = f; + e = f * synch[i]; + k = i; + } + } + f = max(sys_selerr, SQUARE(LOGTOD(sys_precision))); + if (nlist <= sys_minclock || f <= d || + peer_list[k]->flags & FLAG_PREFER) + break; +#ifdef DEBUG + if (debug > 2) + printf( + "select: drop %s select %.6f jitter %.6f\n", + ntoa(&peer_list[k]->srcadr), + SQRT(sys_selerr), SQRT(d)); +#endif + if (!(peer_list[k]->flags & FLAG_CONFIG) && + peer_list[k]->hmode == MODE_CLIENT) + unpeer(peer_list[k]); + for (j = k + 1; j < nlist; j++) { + peer_list[j - 1] = peer_list[j]; + error[j - 1] = error[j]; + } + nlist--; + } + + /* + * What remains is a list usually not greater than sys_minclock + * peers. We want only a peer at the lowest stratum to become + * the system peer, although all survivors are eligible for the + * combining algorithm. First record their order, diddle the + * flags and clamp the poll intervals. Then, consider each peer + * in turn and OR the leap bits on the assumption that, if some + * of them honk nonzero bits, they must know what they are + * doing. Check for prefer and pps peers at any stratum. Check + * if the old system peer is among the peers at the lowest + * stratum. Note that the head of the list is at the lowest + * stratum and that unsynchronized peers cannot survive this + * far. + * + * Fiddle for hysteresis. Pump it up for a peer only if the peer + * stratum is at least the floor and there are enough survivors. + * This minimizes the pain when tossing out rascals beneath the + * floorboard. Don't count peers with stratum above the ceiling. + * Manycast is sooo complicated. + */ + leap_consensus = 0; + for (i = nlist - 1; i >= 0; i--) { + peer = peer_list[i]; + leap_consensus |= peer->leap; + peer->status = CTL_PST_SEL_SYNCCAND; + peer->rank++; + peer->flags |= FLAG_SYSPEER; + if (peer->stratum >= sys_floor && osurv >= sys_minclock) + peer->hyst = HYST; + else + peer->hyst = 0; + if (peer->stratum <= sys_ceiling) + sys_survivors++; + if (peer->flags & FLAG_PREFER) + sys_prefer = peer; + if (peer->refclktype == REFCLK_ATOM_PPS && + peer->stratum < STRATUM_UNSPEC) + typepps = peer; + if (peer->stratum == peer_list[0]->stratum && peer == + osys_peer) + typesystem = peer; + } + + /* + * In manycast client mode we may have spooked a sizeable number + * of peers that we don't need. If there are at least + * sys_minclock of them, the manycast message will be turned + * off. By the time we get here we nay be ready to prune some of + * them back, but we want to make sure all the candicates have + * had a chance. If they didn't pass the sanity and intersection + * tests, they have already been voted off the island. + */ + if (sys_survivors < sys_minclock && osurv >= sys_minclock) + resetmanycast(); + + /* + * Mitigation rules of the game. There are several types of + * peers that make a difference here: (1) prefer local peers + * (type REFCLK_LOCALCLOCK with FLAG_PREFER) or prefer modem + * peers (type REFCLK_NIST_ATOM etc with FLAG_PREFER), (2) pps + * peers (type REFCLK_ATOM_PPS), (3) remaining prefer peers + * (flag FLAG_PREFER), (4) the existing system peer, if any, (5) + * the head of the survivor list. Note that only one peer can be + * declared prefer. The order of preference is in the order + * stated. Note that all of these must be at the lowest stratum, + * i.e., the stratum of the head of the survivor list. + */ + if (sys_prefer) + sw = sys_prefer->refclktype == REFCLK_LOCALCLOCK || + sys_prefer->sstclktype == CTL_SST_TS_TELEPHONE || + !typepps; + else + sw = 0; + if (sw) { + sys_peer = sys_prefer; + sys_peer->status = CTL_PST_SEL_SYSPEER; + sys_offset = sys_peer->offset; + sys_syserr = sys_peer->jitter; +#ifdef DEBUG + if (debug > 1) + printf("select: prefer offset %.6f\n", + sys_offset); +#endif + } +#ifndef LOCKCLOCK + else if (typepps) { + sys_peer = typepps; + sys_peer->status = CTL_PST_SEL_PPS; + sys_offset = sys_peer->offset; + sys_syserr = sys_peer->jitter; + if (!pps_control) + NLOG(NLOG_SYSEVENT) + msyslog(LOG_INFO, "pps sync enabled"); + pps_control = current_time; +#ifdef DEBUG + if (debug > 1) + printf("select: pps offset %.6f\n", + sys_offset); +#endif + } else { + if (typesystem) + sys_peer = osys_peer; + else + sys_peer = peer_list[0]; + sys_peer->status = CTL_PST_SEL_SYSPEER; + sys_peer->rank++; + sys_offset = clock_combine(peer_list, nlist); + sys_syserr = sys_peer->jitter + sys_selerr; +#ifdef DEBUG + if (debug > 1) + printf("select: combine offset %.6f\n", + sys_offset); +#endif + } +#endif /* LOCKCLOCK */ + if (osys_peer != sys_peer) { + char *src; + + if (sys_peer == NULL) + sys_peer_refid = 0; + else + sys_peer_refid = addr2refid(&sys_peer->srcadr); + report_event(EVNT_PEERSTCHG, NULL); + +#ifdef REFCLOCK + if (ISREFCLOCKADR(&sys_peer->srcadr)) + src = refnumtoa(&sys_peer->srcadr); + else +#endif + src = ntoa(&sys_peer->srcadr); + NLOG(NLOG_SYNCSTATUS) + msyslog(LOG_INFO, "synchronized to %s, stratum=%d", src, + sys_peer->stratum); + } + clock_update(); +} + +/* + * clock_combine - combine offsets from selected peers + */ +static double +clock_combine( + struct peer **peers, + int npeers + ) +{ + int i; + double x, y, z; + + y = z = 0; + for (i = 0; i < npeers; i++) { + x = root_distance(peers[i]); + y += 1. / x; + z += peers[i]->offset / x; + } + return (z / y); +} + +/* + * root_distance - compute synchronization distance from peer to root + */ +static double +root_distance( + struct peer *peer + ) +{ + /* + * Careful squeak here. The value returned must be greater than + * zero blamed on the peer jitter, which must be at least the + * square of sys_precision. + */ + return ((peer->rootdelay + peer->delay) / 2 + + peer->rootdispersion + peer->disp + clock_phi * + (current_time - peer->update) + SQRT(peer->jitter)); +} + +/* + * peer_xmit - send packet for persistent association. + */ +static void +peer_xmit( + struct peer *peer /* peer structure pointer */ + ) +{ + struct pkt xpkt; /* transmit packet */ + int sendlen, authlen; + keyid_t xkeyid = 0; /* transmit key ID */ + l_fp xmt_tx; + + /* + * Initialize transmit packet header fields. + */ + xpkt.li_vn_mode = PKT_LI_VN_MODE(sys_leap, peer->version, + peer->hmode); + xpkt.stratum = STRATUM_TO_PKT(sys_stratum); + xpkt.ppoll = peer->hpoll; + xpkt.precision = sys_precision; + xpkt.rootdelay = HTONS_FP(DTOFP(sys_rootdelay)); + xpkt.rootdispersion = HTONS_FP(DTOUFP(sys_rootdispersion)); + xpkt.refid = sys_refid; + HTONL_FP(&sys_reftime, &xpkt.reftime); + HTONL_FP(&peer->org, &xpkt.org); + HTONL_FP(&peer->rec, &xpkt.rec); + + /* + * If the received packet contains a MAC, the transmitted packet + * is authenticated and contains a MAC. If not, the transmitted + * packet is not authenticated. + * + * In the current I/O semantics the default interface is set + * until after receiving a packet and setting the right + * interface. So, the first packet goes out unauthenticated. + * That's why the really icky test next is here. + */ + sendlen = LEN_PKT_NOMAC; + if (!(peer->flags & FLAG_AUTHENABLE)) { + get_systime(&peer->xmt); + HTONL_FP(&peer->xmt, &xpkt.xmt); + sendpkt(&peer->srcadr, peer->dstadr, sys_ttl[peer->ttl], + &xpkt, sendlen); + peer->sent++; +#ifdef DEBUG + if (debug) + printf("transmit: at %ld %s->%s mode %d\n", + current_time, stoa(&peer->dstadr->sin), + stoa(&peer->srcadr), peer->hmode); +#endif + return; + } + + /* + * The received packet contains a MAC, so the transmitted packet + * must be authenticated. If autokey is enabled, fuss with the + * various modes; otherwise, private key cryptography is used. + */ +#ifdef OPENSSL + if (crypto_flags && (peer->flags & FLAG_SKEY)) { + struct exten *exten; /* extension field */ + u_int opcode; + + /* + * The Public Key Dance (PKD): Cryptographic credentials + * are contained in extension fields, each including a + * 4-octet length/code word followed by a 4-octet + * association ID and optional additional data. Optional + * data includes a 4-octet data length field followed by + * the data itself. Request messages are sent from a + * configured association; response messages can be sent + * from a configured association or can take the fast + * path without ever matching an association. Response + * messages have the same code as the request, but have + * a response bit and possibly an error bit set. In this + * implementation, a message may contain no more than + * one command and no more than one response. + * + * Cryptographic session keys include both a public and + * a private componet. Request and response messages + * using extension fields are always sent with the + * private component set to zero. Packets without + * extension fields indlude the private component when + * the session key is generated. + */ + while (1) { + + /* + * Allocate and initialize a keylist if not + * already done. Then, use the list in inverse + * order, discarding keys once used. Keep the + * latest key around until the next one, so + * clients can use client/server packets to + * compute propagation delay. + * + * Note that once a key is used from the list, + * it is retained in the key cache until the + * next key is used. This is to allow a client + * to retrieve the encrypted session key + * identifier to verify authenticity. + * + * If for some reason a key is no longer in the + * key cache, a birthday has happened and the + * pseudo-random sequence is probably broken. In + * that case, purge the keylist and regenerate + * it. + */ + if (peer->keynumber == 0) + make_keylist(peer, peer->dstadr); + else + peer->keynumber--; + xkeyid = peer->keylist[peer->keynumber]; + if (authistrusted(xkeyid)) + break; + else + key_expire(peer); + } + peer->keyid = xkeyid; + switch (peer->hmode) { + + /* + * In broadcast server mode the autokey values are + * required by the broadcast clients. Push them when a + * new keylist is generated; otherwise, push the + * association message so the client can request them at + * other times. + */ + case MODE_BROADCAST: + if (peer->flags & FLAG_ASSOC) + exten = crypto_args(peer, CRYPTO_AUTO | + CRYPTO_RESP, NULL); + else + exten = crypto_args(peer, CRYPTO_ASSOC | + CRYPTO_RESP, NULL); + sendlen += crypto_xmit(&xpkt, &peer->srcadr, + sendlen, exten, 0); + free(exten); + break; + + /* + * In symmetric modes the digest, certificate, agreement + * parameters, cookie and autokey values are required. + * The leapsecond table is optional. But, a passive peer + * will not believe the active peer until the latter has + * synchronized, so the agreement must be postponed + * until then. In any case, if a new keylist is + * generated, the autokey values are pushed. + */ + case MODE_ACTIVE: + case MODE_PASSIVE: + if (peer->cmmd != NULL) { + peer->cmmd->associd = + htonl(peer->associd); + sendlen += crypto_xmit(&xpkt, + &peer->srcadr, sendlen, peer->cmmd, + 0); + free(peer->cmmd); + peer->cmmd = NULL; + } + exten = NULL; + if (!peer->crypto) + exten = crypto_args(peer, CRYPTO_ASSOC, + sys_hostname); + else if (!(peer->crypto & CRYPTO_FLAG_VALID)) + exten = crypto_args(peer, CRYPTO_CERT, + peer->issuer); + + /* + * Identity. Note we have to sign the + * certificate before the cookie to avoid a + * deadlock when the passive peer is walking the + * certificate trail. Awesome. + */ + else if ((opcode = crypto_ident(peer)) != 0) + exten = crypto_args(peer, opcode, NULL); + else if (sys_leap != LEAP_NOTINSYNC && + !(peer->crypto & CRYPTO_FLAG_SIGN)) + exten = crypto_args(peer, CRYPTO_SIGN, + sys_hostname); + + /* + * Autokey. We request the cookie only when the + * server and client are synchronized and + * signatures work both ways. On the other hand, + * the active peer needs the autokey values + * before then and when the passive peer is + * waiting for the active peer to synchronize. + * Any time we regenerate the key list, we offer + * the autokey values without being asked. + */ + else if (sys_leap != LEAP_NOTINSYNC && + peer->leap != LEAP_NOTINSYNC && + !(peer->crypto & CRYPTO_FLAG_AGREE)) + exten = crypto_args(peer, CRYPTO_COOK, + NULL); + else if (peer->flags & FLAG_ASSOC) + exten = crypto_args(peer, CRYPTO_AUTO | + CRYPTO_RESP, NULL); + else if (!(peer->crypto & CRYPTO_FLAG_AUTO)) + exten = crypto_args(peer, CRYPTO_AUTO, + NULL); + + /* + * Postamble. We trade leapseconds only when the + * server and client are synchronized. + */ + else if (sys_leap != LEAP_NOTINSYNC && + peer->leap != LEAP_NOTINSYNC && + peer->crypto & CRYPTO_FLAG_TAI && + !(peer->crypto & CRYPTO_FLAG_LEAP)) + exten = crypto_args(peer, CRYPTO_TAI, + NULL); + if (exten != NULL) { + sendlen += crypto_xmit(&xpkt, + &peer->srcadr, sendlen, exten, 0); + free(exten); + } + break; + + /* + * In client mode the digest, certificate, agreement + * parameters and cookie are required. The leapsecond + * table is optional. If broadcast client mode, the + * autokey values are required as well. In broadcast + * client mode, these values must be acquired during the + * client/server exchange to avoid having to wait until + * the next key list regeneration. Otherwise, the poor + * dude may die a lingering death until becoming + * unreachable and attempting rebirth. + * + * If neither the server or client have the agreement + * parameters, the protocol transmits the cookie in the + * clear. If the server has the parameters, the client + * requests them and the protocol blinds it using the + * agreed key. It is a protocol error if the client has + * the parameters but the server does not. + */ + case MODE_CLIENT: + if (peer->cmmd != NULL) { + peer->cmmd->associd = + htonl(peer->associd); + sendlen += crypto_xmit(&xpkt, + &peer->srcadr, sendlen, peer->cmmd, + 0); + free(peer->cmmd); + peer->cmmd = NULL; + } + exten = NULL; + if (!peer->crypto) + exten = crypto_args(peer, CRYPTO_ASSOC, + sys_hostname); + else if (!(peer->crypto & CRYPTO_FLAG_VALID)) + exten = crypto_args(peer, CRYPTO_CERT, + peer->issuer); + + /* + * Identity. + */ + else if ((opcode = crypto_ident(peer)) != 0) + exten = crypto_args(peer, opcode, NULL); + + /* + * Autokey + */ + else if (!(peer->crypto & CRYPTO_FLAG_AGREE)) + exten = crypto_args(peer, CRYPTO_COOK, + NULL); + else if (!(peer->crypto & CRYPTO_FLAG_AUTO) && + (peer->cast_flags & MDF_BCLNT)) + exten = crypto_args(peer, CRYPTO_AUTO, + NULL); + + /* + * Postamble. We can sign the certificate here, + * since there is no chance of deadlock. + */ + else if (sys_leap != LEAP_NOTINSYNC && + !(peer->crypto & CRYPTO_FLAG_SIGN)) + exten = crypto_args(peer, CRYPTO_SIGN, + sys_hostname); + else if (sys_leap != LEAP_NOTINSYNC && + peer->crypto & CRYPTO_FLAG_TAI && + !(peer->crypto & CRYPTO_FLAG_LEAP)) + exten = crypto_args(peer, CRYPTO_TAI, + NULL); + if (exten != NULL) { + sendlen += crypto_xmit(&xpkt, + &peer->srcadr, sendlen, exten, 0); + free(exten); + } + break; + } + + /* + * If extension fields are present, we must use a + * private value of zero and force min poll interval. + * Most intricate. + */ + if (sendlen > LEN_PKT_NOMAC) + session_key(&peer->dstadr->sin, &peer->srcadr, + xkeyid, 0, 2); + } +#endif /* OPENSSL */ + xkeyid = peer->keyid; + get_systime(&peer->xmt); + L_ADD(&peer->xmt, &sys_authdelay); + HTONL_FP(&peer->xmt, &xpkt.xmt); + authlen = authencrypt(xkeyid, (u_int32 *)&xpkt, sendlen); + if (authlen == 0) { + msyslog(LOG_INFO, + "transmit: encryption key %d not found", xkeyid); + if (peer->flags & FLAG_CONFIG) + peer_clear(peer, "NKEY"); + else + unpeer(peer); + return; + } + sendlen += authlen; +#ifdef OPENSSL + if (xkeyid > NTP_MAXKEY) + authtrust(xkeyid, 0); +#endif /* OPENSSL */ + get_systime(&xmt_tx); + if (sendlen > sizeof(xpkt)) { + msyslog(LOG_ERR, "buffer overflow %u", sendlen); + exit (-1); + } + sendpkt(&peer->srcadr, peer->dstadr, sys_ttl[peer->ttl], &xpkt, + sendlen); + + /* + * Calculate the encryption delay. Keep the minimum over + * the latest two samples. + */ + L_SUB(&xmt_tx, &peer->xmt); + L_ADD(&xmt_tx, &sys_authdelay); + sys_authdly[1] = sys_authdly[0]; + sys_authdly[0] = xmt_tx.l_uf; + if (sys_authdly[0] < sys_authdly[1]) + sys_authdelay.l_uf = sys_authdly[0]; + else + sys_authdelay.l_uf = sys_authdly[1]; + peer->sent++; +#ifdef OPENSSL +#ifdef DEBUG + if (debug) + printf( + "transmit: at %ld %s->%s mode %d keyid %08x len %d mac %d index %d\n", + current_time, ntoa(&peer->dstadr->sin), + ntoa(&peer->srcadr), peer->hmode, xkeyid, sendlen - + authlen, authlen, peer->keynumber); +#endif +#else +#ifdef DEBUG + if (debug) + printf( + "transmit: at %ld %s->%s mode %d keyid %08x len %d mac %d\n", + current_time, ntoa(&peer->dstadr->sin), + ntoa(&peer->srcadr), peer->hmode, xkeyid, sendlen - + authlen, authlen); +#endif +#endif /* OPENSSL */ +} + + +/* + * fast_xmit - Send packet for nonpersistent association. Note that + * neither the source or destination can be a broadcast address. + */ +static void +fast_xmit( + struct recvbuf *rbufp, /* receive packet pointer */ + int xmode, /* transmit mode */ + keyid_t xkeyid, /* transmit key ID */ + int mask /* restrict mask */ + ) +{ + struct pkt xpkt; /* transmit packet structure */ + struct pkt *rpkt; /* receive packet structure */ + l_fp xmt_ts; /* timestamp */ + l_fp xmt_tx; /* timestamp after authent */ + int sendlen, authlen; +#ifdef OPENSSL + u_int32 temp32; +#endif + + /* + * Initialize transmit packet header fields from the receive + * buffer provided. We leave some fields intact as received. If + * the gazinta was from a multicast address, the gazouta must go + * out another way. + */ + rpkt = &rbufp->recv_pkt; + if (rbufp->dstadr->flags & INT_MULTICAST) + rbufp->dstadr = findinterface(&rbufp->recv_srcadr); + + /* + * If the packet has picked up a restriction due to either + * access denied or rate exceeded, decide what to do with it. + */ + if (mask & (RES_DONTTRUST | RES_LIMITED)) { + char *code = "????"; + + if (mask & RES_LIMITED) { + sys_limitrejected++; + code = "RATE"; + } else if (mask & RES_DONTTRUST) { + sys_restricted++; + code = "DENY"; + } + + /* + * Here we light up a kiss-of-death packet. Note the + * rate limit on these packets. Once a second initialize + * a bucket counter. Every packet sent decrements the + * counter until reaching zero. If the counter is zero, + * drop the kod. + */ + if (sys_kod == 0 || !(mask & RES_DEMOBILIZE)) + return; + + sys_kod--; + memcpy(&xpkt.refid, code, 4); + xpkt.li_vn_mode = PKT_LI_VN_MODE(LEAP_NOTINSYNC, + PKT_VERSION(rpkt->li_vn_mode), xmode); + xpkt.stratum = STRATUM_UNSPEC; + } else { + xpkt.li_vn_mode = PKT_LI_VN_MODE(sys_leap, + PKT_VERSION(rpkt->li_vn_mode), xmode); + xpkt.stratum = STRATUM_TO_PKT(sys_stratum); + xpkt.refid = sys_refid; + } + xpkt.ppoll = rpkt->ppoll; + xpkt.precision = sys_precision; + xpkt.rootdelay = HTONS_FP(DTOFP(sys_rootdelay)); + xpkt.rootdispersion = + HTONS_FP(DTOUFP(sys_rootdispersion)); + HTONL_FP(&sys_reftime, &xpkt.reftime); + xpkt.org = rpkt->xmt; + HTONL_FP(&rbufp->recv_time, &xpkt.rec); + + /* + * If the received packet contains a MAC, the transmitted packet + * is authenticated and contains a MAC. If not, the transmitted + * packet is not authenticated. + */ + sendlen = LEN_PKT_NOMAC; + if (rbufp->recv_length == sendlen) { + get_systime(&xmt_ts); + HTONL_FP(&xmt_ts, &xpkt.xmt); + sendpkt(&rbufp->recv_srcadr, rbufp->dstadr, 0, &xpkt, + sendlen); +#ifdef DEBUG + if (debug) + printf("transmit: at %ld %s->%s mode %d\n", + current_time, stoa(&rbufp->dstadr->sin), + stoa(&rbufp->recv_srcadr), xmode); +#endif + return; + } + + /* + * The received packet contains a MAC, so the transmitted packet + * must be authenticated. For private-key cryptography, use the + * predefined private keys to generate the cryptosum. For + * autokey cryptography, use the server private value to + * generate the cookie, which is unique for every source- + * destination-key ID combination. + */ +#ifdef OPENSSL + if (xkeyid > NTP_MAXKEY) { + keyid_t cookie; + + /* + * The only way to get here is a reply to a legitimate + * client request message, so the mode must be + * MODE_SERVER. If an extension field is present, there + * can be only one and that must be a command. Do what + * needs, but with private value of zero so the poor + * jerk can decode it. If no extension field is present, + * use the cookie to generate the session key. + */ + cookie = session_key(&rbufp->recv_srcadr, + &rbufp->dstadr->sin, 0, sys_private, 0); + if (rbufp->recv_length >= (int)(sendlen + MAX_MAC_LEN + 2 * + sizeof(u_int32))) { + session_key(&rbufp->dstadr->sin, + &rbufp->recv_srcadr, xkeyid, 0, 2); + temp32 = CRYPTO_RESP; + rpkt->exten[0] |= htonl(temp32); + sendlen += crypto_xmit(&xpkt, + &rbufp->recv_srcadr, sendlen, + (struct exten *)rpkt->exten, cookie); + } else { + session_key(&rbufp->dstadr->sin, + &rbufp->recv_srcadr, xkeyid, cookie, 2); + } + } +#endif /* OPENSSL */ + get_systime(&xmt_ts); + L_ADD(&xmt_ts, &sys_authdelay); + HTONL_FP(&xmt_ts, &xpkt.xmt); + authlen = authencrypt(xkeyid, (u_int32 *)&xpkt, sendlen); + sendlen += authlen; +#ifdef OPENSSL + if (xkeyid > NTP_MAXKEY) + authtrust(xkeyid, 0); +#endif /* OPENSSL */ + get_systime(&xmt_tx); + if (sendlen > sizeof(xpkt)) { + msyslog(LOG_ERR, "buffer overflow %u", sendlen); + exit (-1); + } + sendpkt(&rbufp->recv_srcadr, rbufp->dstadr, 0, &xpkt, sendlen); + + /* + * Calculate the encryption delay. Keep the minimum over the + * latest two samples. + */ + L_SUB(&xmt_tx, &xmt_ts); + L_ADD(&xmt_tx, &sys_authdelay); + sys_authdly[1] = sys_authdly[0]; + sys_authdly[0] = xmt_tx.l_uf; + if (sys_authdly[0] < sys_authdly[1]) + sys_authdelay.l_uf = sys_authdly[0]; + else + sys_authdelay.l_uf = sys_authdly[1]; +#ifdef DEBUG + if (debug) + printf( + "transmit: at %ld %s->%s mode %d keyid %08x len %d mac %d\n", + current_time, ntoa(&rbufp->dstadr->sin), + ntoa(&rbufp->recv_srcadr), xmode, xkeyid, sendlen - + authlen, authlen); +#endif +} + + +#ifdef OPENSSL +/* + * key_expire - purge the key list + */ +void +key_expire( + struct peer *peer /* peer structure pointer */ + ) +{ + int i; + + if (peer->keylist != NULL) { + for (i = 0; i <= peer->keynumber; i++) + authtrust(peer->keylist[i], 0); + free(peer->keylist); + peer->keylist = NULL; + } + value_free(&peer->sndval); + peer->keynumber = 0; +#ifdef DEBUG + if (debug) + printf("key_expire: at %lu\n", current_time); +#endif +} +#endif /* OPENSSL */ + + +/* + * Determine if the peer is unfit for synchronization + * + * A peer is unfit for synchronization if + * > not reachable + * > a synchronization loop would form + * > never been synchronized + * > stratum undefined or too high + * > too long without synchronization + * > designated noselect + */ +static int /* 0 if no, 1 if yes */ +peer_unfit( + struct peer *peer /* peer structure pointer */ + ) +{ + return (!peer->reach || (peer->stratum > 1 && peer->refid == + peer->dstadr->addr_refid) || peer->leap == LEAP_NOTINSYNC || + peer->stratum >= STRATUM_UNSPEC || root_distance(peer) >= + MAXDISTANCE + 2. * clock_phi * ULOGTOD(sys_poll) || + peer->flags & FLAG_NOSELECT ); +} + + +/* + * Find the precision of this particular machine + */ +#define MINSTEP 100e-9 /* minimum clock increment (s) */ +#define MAXSTEP 20e-3 /* maximum clock increment (s) */ +#define MINLOOPS 5 /* minimum number of step samples */ + +/* + * This routine calculates the system precision, defined as the minimum + * of a sequency of differences between successive readings of the + * system clock. However, if the system clock can be read more than once + * during a tick interval, the difference can be zero or one LSB unit, + * where the LSB corresponds to one nanosecond or one microsecond. + * Conceivably, if some other process preempts this one and reads the + * clock, the difference can be more than one LSB unit. + * + * For hardware clock frequencies of 10 MHz or less, we assume the + * logical clock advances only at the hardware clock tick. For higher + * frequencies, we assume the logical clock can advance no more than 100 + * nanoseconds between ticks. + */ +int +default_get_precision(void) +{ + l_fp val; /* current seconds fraction */ + l_fp last; /* last seconds fraction */ + l_fp diff; /* difference */ + double tick; /* computed tick value */ + double dtemp; /* scratch */ + int i; /* log2 precision */ + + /* + * Loop to find tick value in nanoseconds. Toss out outlyer + * values less than the minimun tick value. In wacky cases, use + * the default maximum value. + */ + get_systime(&last); + tick = MAXSTEP; + for (i = 0; i < MINLOOPS;) { + get_systime(&val); + diff = val; + L_SUB(&diff, &last); + last = val; + LFPTOD(&diff, dtemp); + if (dtemp < MINSTEP) + continue; + i++; + if (dtemp < tick) + tick = dtemp; + } + + /* + * Find the nearest power of two. + */ + NLOG(NLOG_SYSEVENT) + msyslog(LOG_INFO, "precision = %.3f usec", tick * 1e6); + for (i = 0; tick <= 1; i++) + tick *= 2; + if (tick - 1. > 1. - tick / 2) + i--; + return (-i); +} + + +/* + * kod_proto - called once per second to limit kiss-of-death packets + */ +void +kod_proto(void) +{ + sys_kod = sys_kod_rate; +} + + +/* + * init_proto - initialize the protocol module's data + */ +void +init_proto(void) +{ + l_fp dummy; + int i; + + /* + * Fill in the sys_* stuff. Default is don't listen to + * broadcasting, authenticate. + */ + sys_leap = LEAP_NOTINSYNC; + sys_stratum = STRATUM_UNSPEC; + memcpy(&sys_refid, "INIT", 4); + sys_precision = (s_char)default_get_precision(); + sys_jitter = LOGTOD(sys_precision); + sys_rootdelay = 0; + sys_rootdispersion = 0; + L_CLR(&sys_reftime); + sys_peer = NULL; + sys_survivors = 0; + get_systime(&dummy); + sys_manycastserver = 0; + sys_bclient = 0; + sys_bdelay = DEFBROADDELAY; + sys_calldelay = BURST_DELAY; + sys_authenticate = 1; + L_CLR(&sys_authdelay); + sys_authdly[0] = sys_authdly[1] = 0; + sys_stattime = 0; + proto_clr_stats(); + for (i = 0; i < MAX_TTL; i++) { + sys_ttl[i] = (u_char)((i * 256) / MAX_TTL); + sys_ttlmax = i; + } +#ifdef OPENSSL + sys_automax = 1 << NTP_AUTOMAX; +#endif /* OPENSSL */ + + /* + * Default these to enable + */ + ntp_enable = 1; +#ifndef KERNEL_FLL_BUG + kern_enable = 1; +#endif + pps_enable = 0; + stats_control = 1; +} + + +/* + * proto_config - configure the protocol module + */ +void +proto_config( + int item, + u_long value, + double dvalue, + struct sockaddr_storage* svalue + ) +{ + /* + * Figure out what he wants to change, then do it + */ + switch (item) { + + /* + * Turn on/off kernel discipline. + */ + case PROTO_KERNEL: + kern_enable = (int)value; + break; + + /* + * Turn on/off clock discipline. + */ + case PROTO_NTP: + ntp_enable = (int)value; + break; + + /* + * Turn on/off monitoring. + */ + case PROTO_MONITOR: + if (value) + mon_start(MON_ON); + else + mon_stop(MON_ON); + break; + + /* + * Turn on/off statistics. + */ + case PROTO_FILEGEN: + stats_control = (int)value; + break; + + /* + * Turn on/off facility to listen to broadcasts. + */ + case PROTO_BROADCLIENT: + sys_bclient = (int)value; + if (value) + io_setbclient(); + else + io_unsetbclient(); + break; + + /* + * Add muliticast group address. + */ + case PROTO_MULTICAST_ADD: + if (svalue) + io_multicast_add(*svalue); + break; + + /* + * Delete multicast group address. + */ + case PROTO_MULTICAST_DEL: + if (svalue) + io_multicast_del(*svalue); + break; + + /* + * Set default broadcast delay. + */ + case PROTO_BROADDELAY: + sys_bdelay = dvalue; + break; + + /* + * Set modem call delay. + */ + case PROTO_CALLDELAY: + sys_calldelay = (int)value; + break; + + /* + * Require authentication to mobilize ephemeral associations. + */ + case PROTO_AUTHENTICATE: + sys_authenticate = (int)value; + break; + + /* + * Turn on/off PPS discipline. + */ + case PROTO_PPS: + pps_enable = (int)value; + break; + + /* + * Set the minimum number of survivors. + */ + case PROTO_MINCLOCK: + sys_minclock = (int)dvalue; + break; + + /* + * Set the minimum number of candidates. + */ + case PROTO_MINSANE: + sys_minsane = (int)dvalue; + break; + + /* + * Set the stratum floor. + */ + case PROTO_FLOOR: + sys_floor = (int)dvalue; + break; + + /* + * Set the stratum ceiling. + */ + case PROTO_CEILING: + sys_ceiling = (int)dvalue; + break; + + /* + * Set the cohort switch. + */ + case PROTO_COHORT: + sys_cohort= (int)dvalue; + break; + /* + * Set the adjtime() resolution (s). + */ + case PROTO_ADJ: + sys_tick = dvalue; + break; + +#ifdef REFCLOCK + /* + * Turn on/off refclock calibrate + */ + case PROTO_CAL: + cal_enable = (int)value; + break; +#endif + default: + + /* + * Log this error. + */ + msyslog(LOG_INFO, + "proto_config: illegal item %d, value %ld", + item, value); + } +} + + +/* + * proto_clr_stats - clear protocol stat counters + */ +void +proto_clr_stats(void) +{ + sys_stattime = current_time; + sys_received = 0; + sys_processed = 0; + sys_newversionpkt = 0; + sys_oldversionpkt = 0; + sys_unknownversion = 0; + sys_restricted = 0; + sys_badlength = 0; + sys_badauth = 0; + sys_limitrejected = 0; +} diff --git a/ntpd/ntp_refclock.c b/ntpd/ntp_refclock.c new file mode 100644 index 0000000..172fbda --- /dev/null +++ b/ntpd/ntp_refclock.c @@ -0,0 +1,1129 @@ +/* + * ntp_refclock - processing support for reference clocks + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_tty.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +#ifdef REFCLOCK + +#ifdef TTYCLK +# ifdef HAVE_SYS_CLKDEFS_H +# include <sys/clkdefs.h> +# include <stropts.h> +# endif +# ifdef HAVE_SYS_SIO_H +# include <sys/sio.h> +# endif +#endif /* TTYCLK */ + +#ifdef HAVE_PPSCLOCK_H +#include <sys/ppsclock.h> +#endif /* HAVE_PPSCLOCK_H */ + +#ifdef KERNEL_PLL +#include "ntp_syscall.h" +#endif /* KERNEL_PLL */ + +/* + * Reference clock support is provided here by maintaining the fiction + * that the clock is actually a peer. As no packets are exchanged with a + * reference clock, however, we replace the transmit, receive and packet + * procedures with separate code to simulate them. Routines + * refclock_transmit() and refclock_receive() maintain the peer + * variables in a state analogous to an actual peer and pass reference + * clock data on through the filters. Routines refclock_peer() and + * refclock_unpeer() are called to initialize and terminate reference + * clock associations. A set of utility routines is included to open + * serial devices, process sample data, edit input lines to extract + * embedded timestamps and to peform various debugging functions. + * + * The main interface used by these routines is the refclockproc + * structure, which contains for most drivers the decimal equivalants of + * the year, day, month, hour, second and millisecond/microsecond + * decoded from the ASCII timecode. Additional information includes the + * receive timestamp, exception report, statistics tallies, etc. In + * addition, there may be a driver-specific unit structure used for + * local control of the device. + * + * The support routines are passed a pointer to the peer structure, + * which is used for all peer-specific processing and contains a pointer + * to the refclockproc structure, which in turn containes a pointer to + * the unit structure, if used. The peer structure is identified by an + * interface address in the dotted quad form 127.127.t.u (for now only IPv4 + * addresses are used, so we need to be sure the address is it), where t is + * the clock type and u the unit. Some legacy drivers derive the + * refclockproc structure pointer from the table typeunit[type][unit]. + * This interface is strongly discouraged and may be abandoned in + * future. + */ +#define MAXUNIT 4 /* max units */ +#define FUDGEFAC .1 /* fudge correction factor */ + +int fdpps; /* pps file descriptor */ +int cal_enable; /* enable refclock calibrate */ + +/* + * Type/unit peer index. Used to find the peer structure for control and + * debugging. When all clock drivers have been converted to new style, + * this dissapears. + */ +static struct peer *typeunit[REFCLK_MAX + 1][MAXUNIT]; + +/* + * Forward declarations + */ +#ifdef QSORT_USES_VOID_P +static int refclock_cmpl_fp P((const void *, const void *)); +#else +static int refclock_cmpl_fp P((const double *, const double *)); +#endif /* QSORT_USES_VOID_P */ +static int refclock_sample P((struct refclockproc *)); + +/* + * refclock_report - note the occurance of an event + * + * This routine presently just remembers the report and logs it, but + * does nothing heroic for the trap handler. It tries to be a good + * citizen and bothers the system log only if things change. + */ +void +refclock_report( + struct peer *peer, + int code + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + if (pp == NULL) + return; + if (code == CEVNT_BADREPLY) + pp->badformat++; + if (code == CEVNT_BADTIME) + pp->baddata++; + if (code == CEVNT_TIMEOUT) + pp->noreply++; + if (pp->currentstatus != code) { + pp->currentstatus = (u_char)code; + pp->lastevent = (u_char)code; + if (code == CEVNT_FAULT) + msyslog(LOG_ERR, + "clock %s event '%s' (0x%02x)", + refnumtoa(&peer->srcadr), + ceventstr(code), code); + else { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_INFO, + "clock %s event '%s' (0x%02x)", + refnumtoa(&peer->srcadr), + ceventstr(code), code); + } + } +#ifdef DEBUG + if (debug) + printf("clock %s event '%s' (0x%02x)\n", + refnumtoa(&peer->srcadr), + ceventstr(code), code); +#endif +} + + +/* + * init_refclock - initialize the reference clock drivers + * + * This routine calls each of the drivers in turn to initialize internal + * variables, if necessary. Most drivers have nothing to say at this + * point. + */ +void +init_refclock(void) +{ + int i, j; + + for (i = 0; i < (int)num_refclock_conf; i++) { + if (refclock_conf[i]->clock_init != noentry) + (refclock_conf[i]->clock_init)(); + for (j = 0; j < MAXUNIT; j++) + typeunit[i][j] = 0; + } +} + + +/* + * refclock_newpeer - initialize and start a reference clock + * + * This routine allocates and initializes the interface structure which + * supports a reference clock in the form of an ordinary NTP peer. A + * driver-specific support routine completes the initialization, if + * used. Default peer variables which identify the clock and establish + * its reference ID and stratum are set here. It returns one if success + * and zero if the clock address is invalid or already running, + * insufficient resources are available or the driver declares a bum + * rap. + */ +int +refclock_newpeer( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + u_char clktype; + int unit; + + /* + * Check for valid clock address. If already running, shut it + * down first. + */ + if (peer->srcadr.ss_family != AF_INET) { + msyslog(LOG_ERR, + "refclock_newpeer: clock address %s invalid, address family not implemented for refclock", + stoa(&peer->srcadr)); + return (0); + } + if (!ISREFCLOCKADR(&peer->srcadr)) { + msyslog(LOG_ERR, + "refclock_newpeer: clock address %s invalid", + stoa(&peer->srcadr)); + return (0); + } + clktype = (u_char)REFCLOCKTYPE(&peer->srcadr); + unit = REFCLOCKUNIT(&peer->srcadr); + if (clktype >= num_refclock_conf || unit >= MAXUNIT || + refclock_conf[clktype]->clock_start == noentry) { + msyslog(LOG_ERR, + "refclock_newpeer: clock type %d invalid\n", + clktype); + return (0); + } + + /* + * Allocate and initialize interface structure + */ + pp = (struct refclockproc *)emalloc(sizeof(struct refclockproc)); + if (pp == NULL) + return (0); + memset((char *)pp, 0, sizeof(struct refclockproc)); + typeunit[clktype][unit] = peer; + peer->procptr = pp; + + /* + * Initialize structures + */ + peer->refclktype = clktype; + peer->refclkunit = (u_char)unit; + peer->flags |= FLAG_REFCLOCK; + peer->maxpoll = peer->minpoll; + peer->stratum = STRATUM_REFCLOCK; + pp->type = clktype; + pp->timestarted = current_time; + + /* + * Set peer.pmode based on the hmode. For appearances only. + */ + switch (peer->hmode) { + case MODE_ACTIVE: + peer->pmode = MODE_PASSIVE; + break; + + default: + peer->pmode = MODE_SERVER; + break; + } + + /* + * Do driver dependent initialization. The above defaults + * can be wiggled, then finish up for consistency. + */ + if (!((refclock_conf[clktype]->clock_start)(unit, peer))) { + refclock_unpeer(peer); + return (0); + } + peer->hpoll = peer->minpoll; + peer->ppoll = peer->maxpoll; + peer->refid = pp->refid; + return (1); +} + + +/* + * refclock_unpeer - shut down a clock + */ +void +refclock_unpeer( + struct peer *peer /* peer structure pointer */ + ) +{ + u_char clktype; + int unit; + + /* + * Wiggle the driver to release its resources, then give back + * the interface structure. + */ + if (!peer->procptr) + return; + clktype = peer->refclktype; + unit = peer->refclkunit; + if (refclock_conf[clktype]->clock_shutdown != noentry) + (refclock_conf[clktype]->clock_shutdown)(unit, peer); + free(peer->procptr); + peer->procptr = 0; +} + + +/* + * refclock_transmit - simulate the transmit procedure + * + * This routine implements the NTP transmit procedure for a reference + * clock. This provides a mechanism to call the driver at the NTP poll + * interval, as well as provides a reachability mechanism to detect a + * broken radio or other madness. + */ +void +refclock_transmit( + struct peer *peer /* peer structure pointer */ + ) +{ + u_char clktype; + int unit; + u_long next; + + clktype = peer->refclktype; + unit = peer->refclkunit; + peer->sent++; + + /* + * This is a ripoff of the peer transmit routine, but + * specialized for reference clocks. We do a little less + * protocol here and call the driver-specific transmit routine. + */ + next = peer->outdate; + if (peer->burst == 0) { + u_char oreach; +#ifdef DEBUG + if (debug) + printf("refclock_transmit: at %ld %s\n", + current_time, stoa(&(peer->srcadr))); +#endif + + /* + * Update reachability and poll variables like the + * network code. + */ + oreach = peer->reach; + peer->reach <<= 1; + if (!peer->reach) { + if (oreach) { + report_event(EVNT_UNREACH, peer); + peer->timereachable = current_time; + peer_clear(peer, "NONE"); + } + } else { + if (!(oreach & 0x03)) { + clock_filter(peer, 0., 0., MAXDISPERSE); + clock_select(); + } + if (peer->flags & FLAG_BURST) + peer->burst = NSTAGE; + } + next = current_time; + } + get_systime(&peer->xmt); + if (refclock_conf[clktype]->clock_poll != noentry) + (refclock_conf[clktype]->clock_poll)(unit, peer); + peer->outdate = next; + if (peer->burst > 0) + peer->burst--; + poll_update(peer, 0); +} + + +/* + * Compare two doubles - used with qsort() + */ +#ifdef QSORT_USES_VOID_P +static int +refclock_cmpl_fp( + const void *p1, + const void *p2 + ) +{ + const double *dp1 = (const double *)p1; + const double *dp2 = (const double *)p2; + + if (*dp1 < *dp2) + return (-1); + if (*dp1 > *dp2) + return (1); + return (0); +} +#else +static int +refclock_cmpl_fp( + const double *dp1, + const double *dp2 + ) +{ + if (*dp1 < *dp2) + return (-1); + if (*dp1 > *dp2) + return (1); + return (0); +} +#endif /* QSORT_USES_VOID_P */ + + +/* + * refclock_process_offset - update median filter + * + * This routine uses the given offset and timestamps to construct a new + * entry in the median filter circular buffer. Samples that overflow the + * filter are quietly discarded. + */ +void +refclock_process_offset( + struct refclockproc *pp, /* refclock structure pointer */ + l_fp lasttim, /* last timecode timestamp */ + l_fp lastrec, /* last receive timestamp */ + double fudge + ) +{ + l_fp lftemp; + double doffset; + + pp->lastrec = lastrec; + lftemp = lasttim; + L_SUB(&lftemp, &lastrec); + LFPTOD(&lftemp, doffset); + SAMPLE(doffset + fudge); +} + +/* + * refclock_process - process a sample from the clock + * + * This routine converts the timecode in the form days, hours, minutes, + * seconds and milliseconds/microseconds to internal timestamp format, + * then constructs a new entry in the median filter circular buffer. + * Return success (1) if the data are correct and consistent with the + * converntional calendar. +*/ +int +refclock_process( + struct refclockproc *pp /* refclock structure pointer */ + ) +{ + l_fp offset, ltemp; + + /* + * Compute the timecode timestamp from the days, hours, minutes, + * seconds and milliseconds/microseconds of the timecode. Use + * clocktime() for the aggregate seconds and the msec/usec for + * the fraction, when present. Note that this code relies on the + * filesystem time for the years and does not use the years of + * the timecode. + */ + if (!clocktime(pp->day, pp->hour, pp->minute, pp->second, GMT, + pp->lastrec.l_ui, &pp->yearstart, &offset.l_ui)) + return (0); + offset.l_uf = 0; + DTOLFP(pp->nsec / 1e9, <emp); + L_ADD(&offset, <emp); + refclock_process_offset(pp, offset, pp->lastrec, + pp->fudgetime1); + return (1); +} + +/* + * refclock_sample - process a pile of samples from the clock + * + * This routine implements a recursive median filter to suppress spikes + * in the data, as well as determine a performance statistic. It + * calculates the mean offset and jitter (squares). A time adjustment + * fudgetime1 can be added to the final offset to compensate for various + * systematic errors. The routine returns the number of samples + * processed, which could be zero. + */ +static int +refclock_sample( + struct refclockproc *pp /* refclock structure pointer */ + ) +{ + int i, j, k, m, n; + double offset; + double off[MAXSTAGE]; + + /* + * Copy the raw offsets and sort into ascending order. Don't do + * anything if the buffer is empty. + */ + n = 0; + while (pp->codeproc != pp->coderecv) { + pp->codeproc = (pp->codeproc + 1) % MAXSTAGE; + off[n] = pp->filter[pp->codeproc]; + n++; + } + if (n == 0) + return (0); + if (n > 1) + qsort((char *)off, (size_t)n, sizeof(double), refclock_cmpl_fp); + + /* + * Reject the furthest from the median of the samples until + * approximately 60 percent of the samples remain. + */ + i = 0; j = n; + m = n - (n * 2) / NSTAGE; + while ((j - i) > m) { + offset = off[(j + i) / 2]; + if (off[j - 1] - offset < offset - off[i]) + i++; /* reject low end */ + else + j--; /* reject high end */ + } + + /* + * Determine the offset and jitter. + */ + offset = 0; + for (k = i; k < j; k++) + offset += off[k]; + pp->offset = offset / m; + if (m > 1) + pp->jitter = SQUARE(off[i] - off[j - 1]); + else + pp->jitter = 0; +#ifdef DEBUG + if (debug) + printf( + "refclock_sample: n %d offset %.6f disp %.6f jitter %.6f\n", + n, pp->offset, pp->disp, SQRT(pp->jitter)); +#endif + return (n); +} + + +/* + * refclock_receive - simulate the receive and packet procedures + * + * This routine simulates the NTP receive and packet procedures for a + * reference clock. This provides a mechanism in which the ordinary NTP + * filter, selection and combining algorithms can be used to suppress + * misbehaving radios and to mitigate between them when more than one is + * available for backup. + */ +void +refclock_receive( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + +#ifdef DEBUG + if (debug) + printf("refclock_receive: at %lu %s\n", + current_time, stoa(&peer->srcadr)); +#endif + + /* + * Do a little sanity dance and update the peer structure. Groom + * the median filter samples and give the data to the clock + * filter. + */ + peer->received++; + pp = peer->procptr; + peer->processed++; + peer->timereceived = current_time; + peer->leap = pp->leap; + if (peer->leap == LEAP_NOTINSYNC) { + refclock_report(peer, CEVNT_FAULT); + return; + } + if (!peer->reach) + report_event(EVNT_REACH, peer); + peer->reach |= 1; + peer->reftime = pp->lastref; + peer->org = pp->lastrec; + peer->rootdispersion = pp->disp; + get_systime(&peer->rec); + if (!refclock_sample(pp)) + return; + clock_filter(peer, pp->offset, 0., pp->jitter); + clock_select(); + record_peer_stats(&peer->srcadr, ctlpeerstatus(peer), + peer->offset, peer->delay, clock_phi * (current_time - + peer->epoch), SQRT(peer->jitter)); + if (cal_enable && last_offset < MINDISPERSE) { +#ifdef KERNEL_PLL + if (peer != sys_peer || pll_status & STA_PPSTIME) +#else + if (peer != sys_peer) +#endif /* KERNEL_PLL */ + pp->fudgetime1 -= pp->offset * FUDGEFAC; + else + pp->fudgetime1 -= pp->fudgetime1 * FUDGEFAC; + } +} + +/* + * refclock_gtlin - groom next input line and extract timestamp + * + * This routine processes the timecode received from the clock and + * removes the parity bit and control characters. If a timestamp is + * present in the timecode, as produced by the tty_clk STREAMS module, + * it returns that as the timestamp; otherwise, it returns the buffer + * timestamp. The routine return code is the number of characters in + * the line. + */ +int +refclock_gtlin( + struct recvbuf *rbufp, /* receive buffer pointer */ + char *lineptr, /* current line pointer */ + int bmax, /* remaining characters in line */ + l_fp *tsptr /* pointer to timestamp returned */ + ) +{ + char *dpt, *dpend, *dp; + int i; + l_fp trtmp, tstmp; + char c; + + /* + * Check for the presence of a timestamp left by the tty_clock + * module and, if present, use that instead of the buffer + * timestamp captured by the I/O routines. We recognize a + * timestamp by noting its value is earlier than the buffer + * timestamp, but not more than one second earlier. + */ + dpt = (char *)rbufp->recv_buffer; + dpend = dpt + rbufp->recv_length; + trtmp = rbufp->recv_time; + + if (dpend >= dpt + 8) { + if (buftvtots(dpend - 8, &tstmp)) { + L_SUB(&trtmp, &tstmp); + if (trtmp.l_ui == 0) { +#ifdef DEBUG + if (debug > 1) { + printf( + "refclock_gtlin: fd %d ldisc %s", + rbufp->fd, lfptoa(&trtmp, 6)); + get_systime(&trtmp); + L_SUB(&trtmp, &tstmp); + printf(" sigio %s\n", lfptoa(&trtmp, 6)); + } +#endif + dpend -= 8; + trtmp = tstmp; + } else + trtmp = rbufp->recv_time; + } + } + + /* + * Edit timecode to remove control chars. Don't monkey with the + * line buffer if the input buffer contains no ASCII printing + * characters. + */ + if (dpend - dpt > bmax - 1) + dpend = dpt + bmax - 1; + for (dp = lineptr; dpt < dpend; dpt++) { + c = (char) (*dpt & 0x7f); + if (c >= ' ') + *dp++ = c; + } + i = dp - lineptr; + if (i > 0) + *dp = '\0'; +#ifdef DEBUG + if (debug > 1) { + if (i > 0) + printf("refclock_gtlin: fd %d time %s timecode %d %s\n", + rbufp->fd, ulfptoa(&trtmp, 6), i, lineptr); + else + printf("refclock_gtlin: fd %d time %s\n", + rbufp->fd, ulfptoa(&trtmp, 6)); + } +#endif + *tsptr = trtmp; + return (i); +} + +/* + * The following code does not apply to WINNT & VMS ... + */ +#if !defined SYS_VXWORKS && !defined SYS_WINNT +#if defined(HAVE_TERMIOS) || defined(HAVE_SYSV_TTYS) || defined(HAVE_BSD_TTYS) + +/* + * refclock_open - open serial port for reference clock + * + * This routine opens a serial port for I/O and sets default options. It + * returns the file descriptor if success and zero if failure. + */ +int +refclock_open( + char *dev, /* device name pointer */ + int speed, /* serial port speed (code) */ + int lflags /* line discipline flags */ + ) +{ + int fd, i; + int flags; + TTY ttyb, *ttyp; +#ifdef TIOCMGET + u_long ltemp; +#endif /* TIOCMGET */ + int omode; + + /* + * Open serial port and set default options + */ + flags = lflags; + + omode = O_RDWR; +#ifdef O_NONBLOCK + omode |= O_NONBLOCK; +#endif +#ifdef O_NOCTTY + omode |= O_NOCTTY; +#endif + + fd = open(dev, omode, 0777); + + if (fd < 0) { + msyslog(LOG_ERR, "refclock_open: %s: %m", dev); + return (0); + } + + /* + * This little jewel lights up the PPS file descriptor if the + * device name matches the name in the pps line in the + * configuration file. This is so the atom driver can glom onto + * the right device. Very silly. + */ + if (strcmp(dev, pps_device) == 0) + fdpps = fd; + + /* + * The following sections initialize the serial line port in + * canonical (line-oriented) mode and set the specified line + * speed, 8 bits and no parity. The modem control, break, erase + * and kill functions are normally disabled. There is a + * different section for each terminal interface, as selected at + * compile time. + */ + ttyp = &ttyb; + +#ifdef HAVE_TERMIOS + /* + * POSIX serial line parameters (termios interface) + */ + if (tcgetattr(fd, ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d tcgetattr: %m", fd); + return (0); + } + + /* + * Set canonical mode and local connection; set specified speed, + * 8 bits and no parity; map CR to NL; ignore break. + */ + ttyp->c_iflag = IGNBRK | IGNPAR | ICRNL; + ttyp->c_oflag = 0; + ttyp->c_cflag = CS8 | CLOCAL | CREAD; + (void)cfsetispeed(&ttyb, (u_int)speed); + (void)cfsetospeed(&ttyb, (u_int)speed); + ttyp->c_lflag = ICANON; + for (i = 0; i < NCCS; ++i) + { + ttyp->c_cc[i] = '\0'; + } + + /* + * Some special cases + */ + if (flags & LDISC_RAW) { + ttyp->c_iflag = 0; + ttyp->c_lflag = 0; + ttyp->c_cc[VMIN] = 1; + } +#if defined(TIOCMGET) && !defined(SCO5_CLOCK) + /* + * If we have modem control, check to see if modem leads are + * active; if so, set remote connection. This is necessary for + * the kernel pps mods to work. + */ + ltemp = 0; + if (ioctl(fd, TIOCMGET, (char *)<emp) < 0) + msyslog(LOG_ERR, + "refclock_open: fd %d TIOCMGET failed: %m", fd); +#ifdef DEBUG + if (debug) + printf("refclock_open: fd %d modem status 0x%lx\n", + fd, ltemp); +#endif + if (ltemp & TIOCM_DSR) + ttyp->c_cflag &= ~CLOCAL; +#endif /* TIOCMGET */ + if (tcsetattr(fd, TCSANOW, ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d TCSANOW failed: %m", fd); + return (0); + } + if (tcflush(fd, TCIOFLUSH) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d TCIOFLUSH failed: %m", fd); + return (0); + } +#endif /* HAVE_TERMIOS */ + +#ifdef HAVE_SYSV_TTYS + + /* + * System V serial line parameters (termio interface) + * + */ + if (ioctl(fd, TCGETA, ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d TCGETA failed: %m", fd); + return (0); + } + + /* + * Set canonical mode and local connection; set specified speed, + * 8 bits and no parity; map CR to NL; ignore break. + */ + ttyp->c_iflag = IGNBRK | IGNPAR | ICRNL; + ttyp->c_oflag = 0; + ttyp->c_cflag = speed | CS8 | CLOCAL | CREAD; + ttyp->c_lflag = ICANON; + ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0'; + + /* + * Some special cases + */ + if (flags & LDISC_RAW) { + ttyp->c_iflag = 0; + ttyp->c_lflag = 0; + } +#ifdef TIOCMGET + /* + * If we have modem control, check to see if modem leads are + * active; if so, set remote connection. This is necessary for + * the kernel pps mods to work. + */ + ltemp = 0; + if (ioctl(fd, TIOCMGET, (char *)<emp) < 0) + msyslog(LOG_ERR, + "refclock_open: fd %d TIOCMGET failed: %m", fd); +#ifdef DEBUG + if (debug) + printf("refclock_open: fd %d modem status %lx\n", + fd, ltemp); +#endif + if (ltemp & TIOCM_DSR) + ttyp->c_cflag &= ~CLOCAL; +#endif /* TIOCMGET */ + if (ioctl(fd, TCSETA, ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d TCSETA failed: %m", fd); + return (0); + } +#endif /* HAVE_SYSV_TTYS */ + +#ifdef HAVE_BSD_TTYS + + /* + * 4.3bsd serial line parameters (sgttyb interface) + */ + if (ioctl(fd, TIOCGETP, (char *)ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: fd %d TIOCGETP %m", fd); + return (0); + } + ttyp->sg_ispeed = ttyp->sg_ospeed = speed; + ttyp->sg_flags = EVENP | ODDP | CRMOD; + if (ioctl(fd, TIOCSETP, (char *)ttyp) < 0) { + msyslog(LOG_ERR, + "refclock_open: TIOCSETP failed: %m"); + return (0); + } +#endif /* HAVE_BSD_TTYS */ + if (!refclock_ioctl(fd, flags)) { + (void)close(fd); + msyslog(LOG_ERR, + "refclock_open: fd %d ioctl failed: %m", fd); + return (0); + } + return (fd); +} +#endif /* HAVE_TERMIOS || HAVE_SYSV_TTYS || HAVE_BSD_TTYS */ +#endif /* SYS_VXWORKS SYS_WINNT */ + +/* + * refclock_ioctl - set serial port control functions + * + * This routine attempts to hide the internal, system-specific details + * of serial ports. It can handle POSIX (termios), SYSV (termio) and BSD + * (sgtty) interfaces with varying degrees of success. The routine sets + * up optional features such as tty_clk. The routine returns 1 if + * success and 0 if failure. + */ +int +refclock_ioctl( + int fd, /* file descriptor */ + int flags /* line discipline flags */ + ) +{ + /* simply return 1 if no UNIX line discipline is supported */ +#if !defined SYS_VXWORKS && !defined SYS_WINNT +#if defined(HAVE_TERMIOS) || defined(HAVE_SYSV_TTYS) || defined(HAVE_BSD_TTYS) + +#ifdef TTYCLK + TTY ttyb, *ttyp; +#endif /* TTYCLK */ + +#ifdef DEBUG + if (debug) + printf("refclock_ioctl: fd %d flags 0x%x\n", fd, flags); +#endif + if (flags == 0) + return (1); +#if !(defined(HAVE_TERMIOS) || defined(HAVE_BSD_TTYS)) + if (flags & (LDISC_CLK | LDISC_PPS | LDISC_ACTS)) { + msyslog(LOG_ERR, + "refclock_ioctl: unsupported terminal interface"); + return (0); + } +#endif /* HAVE_TERMIOS HAVE_BSD_TTYS */ +#ifdef TTYCLK + ttyp = &ttyb; +#endif /* TTYCLK */ + + /* + * The following features may or may not require System V + * STREAMS support, depending on the particular implementation. + */ +#if defined(TTYCLK) + /* + * The TTYCLK option provides timestamping at the driver level. + * It requires the tty_clk streams module and System V STREAMS + * support. If not available, don't complain. + */ + if (flags & (LDISC_CLK | LDISC_CLKPPS | LDISC_ACTS)) { + int rval = 0; + + if (ioctl(fd, I_PUSH, "clk") < 0) { + msyslog(LOG_NOTICE, + "refclock_ioctl: I_PUSH clk failed: %m"); + } else { + char *str; + + if (flags & LDISC_CLKPPS) + str = "\377"; + else if (flags & LDISC_ACTS) + str = "*"; + else + str = "\n"; +#ifdef CLK_SETSTR + if ((rval = ioctl(fd, CLK_SETSTR, str)) < 0) + msyslog(LOG_ERR, + "refclock_ioctl: CLK_SETSTR failed: %m"); + if (debug) + printf("refclock_ioctl: fd %d CLK_SETSTR %d str %s\n", + fd, rval, str); +#endif + } + } +#endif /* TTYCLK */ +#endif /* HAVE_TERMIOS || HAVE_SYSV_TTYS || HAVE_BSD_TTYS */ +#endif /* SYS_VXWORKS SYS_WINNT */ + return (1); +} + +/* + * refclock_control - set and/or return clock values + * + * This routine is used mainly for debugging. It returns designated + * values from the interface structure that can be displayed using + * ntpdc and the clockstat command. It can also be used to initialize + * configuration variables, such as fudgetimes, fudgevalues, reference + * ID and stratum. + */ +void +refclock_control( + struct sockaddr_storage *srcadr, + struct refclockstat *in, + struct refclockstat *out + ) +{ + struct peer *peer; + struct refclockproc *pp; + u_char clktype; + int unit; + + /* + * Check for valid address and running peer + */ + if (srcadr->ss_family != AF_INET) + return; + if (!ISREFCLOCKADR(srcadr)) + return; + clktype = (u_char)REFCLOCKTYPE(srcadr); + unit = REFCLOCKUNIT(srcadr); + if (clktype >= num_refclock_conf || unit >= MAXUNIT) + return; + peer = typeunit[clktype][unit]; + if (peer == NULL) + return; + if (peer->procptr == NULL) + return; + pp = peer->procptr; + + /* + * Initialize requested data + */ + if (in != 0) { + if (in->haveflags & CLK_HAVETIME1) + pp->fudgetime1 = in->fudgetime1; + if (in->haveflags & CLK_HAVETIME2) + pp->fudgetime2 = in->fudgetime2; + if (in->haveflags & CLK_HAVEVAL1) + pp->stratum = (u_char) in->fudgeval1; + if (in->haveflags & CLK_HAVEVAL2) + pp->refid = in->fudgeval2; + peer->stratum = pp->stratum; + if (peer->stratum == STRATUM_REFCLOCK || peer->stratum == + STRATUM_UNSPEC) + peer->refid = pp->refid; + else + peer->refid = ((struct + sockaddr_in*)&peer->srcadr)->sin_addr.s_addr; + if (in->haveflags & CLK_HAVEFLAG1) { + pp->sloppyclockflag &= ~CLK_FLAG1; + pp->sloppyclockflag |= in->flags & CLK_FLAG1; + } + if (in->haveflags & CLK_HAVEFLAG2) { + pp->sloppyclockflag &= ~CLK_FLAG2; + pp->sloppyclockflag |= in->flags & CLK_FLAG2; + } + if (in->haveflags & CLK_HAVEFLAG3) { + pp->sloppyclockflag &= ~CLK_FLAG3; + pp->sloppyclockflag |= in->flags & CLK_FLAG3; + } + if (in->haveflags & CLK_HAVEFLAG4) { + pp->sloppyclockflag &= ~CLK_FLAG4; + pp->sloppyclockflag |= in->flags & CLK_FLAG4; + } + } + + /* + * Readback requested data + */ + if (out != 0) { + out->haveflags = CLK_HAVETIME1 | CLK_HAVEVAL1 | + CLK_HAVEVAL2 | CLK_HAVEFLAG4; + out->fudgetime1 = pp->fudgetime1; + out->fudgetime2 = pp->fudgetime2; + out->fudgeval1 = pp->stratum; + out->fudgeval2 = pp->refid; + out->flags = (u_char) pp->sloppyclockflag; + + out->timereset = current_time - pp->timestarted; + out->polls = pp->polls; + out->noresponse = pp->noreply; + out->badformat = pp->badformat; + out->baddata = pp->baddata; + + out->lastevent = pp->lastevent; + out->currentstatus = pp->currentstatus; + out->type = pp->type; + out->clockdesc = pp->clockdesc; + out->lencode = pp->lencode; + out->p_lastcode = pp->a_lastcode; + } + + /* + * Give the stuff to the clock + */ + if (refclock_conf[clktype]->clock_control != noentry) + (refclock_conf[clktype]->clock_control)(unit, in, out, peer); +} + + +/* + * refclock_buginfo - return debugging info + * + * This routine is used mainly for debugging. It returns designated + * values from the interface structure that can be displayed using + * ntpdc and the clkbug command. + */ +void +refclock_buginfo( + struct sockaddr_storage *srcadr, /* clock address */ + struct refclockbug *bug /* output structure */ + ) +{ + struct peer *peer; + struct refclockproc *pp; + u_char clktype; + int unit; + int i; + + /* + * Check for valid address and peer structure + */ + if (srcadr->ss_family != AF_INET) + return; + if (!ISREFCLOCKADR(srcadr)) + return; + clktype = (u_char) REFCLOCKTYPE(srcadr); + unit = REFCLOCKUNIT(srcadr); + if (clktype >= num_refclock_conf || unit >= MAXUNIT) + return; + peer = typeunit[clktype][unit]; + if (peer == NULL) + return; + pp = peer->procptr; + + /* + * Copy structure values + */ + bug->nvalues = 8; + bug->svalues = 0x0000003f; + bug->values[0] = pp->year; + bug->values[1] = pp->day; + bug->values[2] = pp->hour; + bug->values[3] = pp->minute; + bug->values[4] = pp->second; + bug->values[5] = pp->nsec; + bug->values[6] = pp->yearstart; + bug->values[7] = pp->coderecv; + bug->stimes = 0xfffffffc; + bug->times[0] = pp->lastref; + bug->times[1] = pp->lastrec; + for (i = 2; i < (int)bug->ntimes; i++) + DTOLFP(pp->filter[i - 2], &bug->times[i]); + + /* + * Give the stuff to the clock + */ + if (refclock_conf[clktype]->clock_buginfo != noentry) + (refclock_conf[clktype]->clock_buginfo)(unit, bug, peer); +} + +#endif /* REFCLOCK */ diff --git a/ntpd/ntp_request.c b/ntpd/ntp_request.c new file mode 100644 index 0000000..eacba28 --- /dev/null +++ b/ntpd/ntp_request.c @@ -0,0 +1,2756 @@ +/* + * ntp_request.c - respond to information requests + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_request.h" +#include "ntp_control.h" +#include "ntp_refclock.h" +#include "ntp_if.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <stddef.h> +#include <signal.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "recvbuff.h" + +#ifdef KERNEL_PLL +#include "ntp_syscall.h" +#endif /* KERNEL_PLL */ + +/* + * Structure to hold request procedure information + */ +#define NOAUTH 0 +#define AUTH 1 + +#define NO_REQUEST (-1) +/* + * Because we now have v6 addresses in the messages, we need to compensate + * for the larger size. Therefore, we introduce the alternate size to + * keep us friendly with older implementations. A little ugly. + */ +static int client_v6_capable = 0; /* the client can handle longer messages */ + +#define v6sizeof(type) (client_v6_capable ? sizeof(type) : v4sizeof(type)) + +struct req_proc { + short request_code; /* defined request code */ + short needs_auth; /* true when authentication needed */ + short sizeofitem; /* size of request data item (older size)*/ + short v6_sizeofitem; /* size of request data item (new size)*/ + void (*handler) P((struct sockaddr_storage *, struct interface *, + struct req_pkt *)); /* routine to handle request */ +}; + +/* + * Universal request codes + */ +static struct req_proc univ_codes[] = { + { NO_REQUEST, NOAUTH, 0, 0 } +}; + +static void req_ack P((struct sockaddr_storage *, struct interface *, struct req_pkt *, int)); +static char * prepare_pkt P((struct sockaddr_storage *, struct interface *, struct req_pkt *, u_int)); +static char * more_pkt P((void)); +static void flush_pkt P((void)); +static void peer_list P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void peer_list_sum P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void peer_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void peer_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void sys_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void sys_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void mem_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void io_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void timer_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void loop_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_conf P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_unconf P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void set_sys_flag P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void clr_sys_flag P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void setclr_flags P((struct sockaddr_storage *, struct interface *, struct req_pkt *, u_long)); +static void list_restrict P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_resaddflags P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_ressubflags P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_unrestrict P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_restrict P((struct sockaddr_storage *, struct interface *, struct req_pkt *, int)); +static void mon_getlist_0 P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void mon_getlist_1 P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void reset_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void reset_peer P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_key_reread P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void trust_key P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void untrust_key P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_trustkey P((struct sockaddr_storage *, struct interface *, struct req_pkt *, u_long)); +static void get_auth_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void reset_auth_stats P((void)); +static void req_get_traps P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void req_set_trap P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void req_clr_trap P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void do_setclr_trap P((struct sockaddr_storage *, struct interface *, struct req_pkt *, int)); +static void set_request_keyid P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void set_control_keyid P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void get_ctl_stats P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +#ifdef KERNEL_PLL +static void get_kernel_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +#endif /* KERNEL_PLL */ +#ifdef REFCLOCK +static void get_clock_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +static void set_clock_fudge P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +#endif /* REFCLOCK */ +#ifdef REFCLOCK +static void get_clkbug_info P((struct sockaddr_storage *, struct interface *, struct req_pkt *)); +#endif /* REFCLOCK */ + +/* + * ntpd request codes + */ +static struct req_proc ntp_codes[] = { + { REQ_PEER_LIST, NOAUTH, 0, 0, peer_list }, + { REQ_PEER_LIST_SUM, NOAUTH, 0, 0, peer_list_sum }, + { REQ_PEER_INFO, NOAUTH, v4sizeof(struct info_peer_list), + sizeof(struct info_peer_list), peer_info}, + { REQ_PEER_STATS, NOAUTH, v4sizeof(struct info_peer_list), + sizeof(struct info_peer_list), peer_stats}, + { REQ_SYS_INFO, NOAUTH, 0, 0, sys_info }, + { REQ_SYS_STATS, NOAUTH, 0, 0, sys_stats }, + { REQ_IO_STATS, NOAUTH, 0, 0, io_stats }, + { REQ_MEM_STATS, NOAUTH, 0, 0, mem_stats }, + { REQ_LOOP_INFO, NOAUTH, 0, 0, loop_info }, + { REQ_TIMER_STATS, NOAUTH, 0, 0, timer_stats }, + { REQ_CONFIG, AUTH, v4sizeof(struct conf_peer), + sizeof(struct conf_peer), do_conf }, + { REQ_UNCONFIG, AUTH, v4sizeof(struct conf_unpeer), + sizeof(struct conf_unpeer), do_unconf }, + { REQ_SET_SYS_FLAG, AUTH, sizeof(struct conf_sys_flags), + sizeof(struct conf_sys_flags), set_sys_flag }, + { REQ_CLR_SYS_FLAG, AUTH, sizeof(struct conf_sys_flags), + sizeof(struct conf_sys_flags), clr_sys_flag }, + { REQ_GET_RESTRICT, NOAUTH, 0, 0, list_restrict }, + { REQ_RESADDFLAGS, AUTH, v4sizeof(struct conf_restrict), + sizeof(struct conf_restrict), do_resaddflags }, + { REQ_RESSUBFLAGS, AUTH, v4sizeof(struct conf_restrict), + sizeof(struct conf_restrict), do_ressubflags }, + { REQ_UNRESTRICT, AUTH, v4sizeof(struct conf_restrict), + sizeof(struct conf_restrict), do_unrestrict }, + { REQ_MON_GETLIST, NOAUTH, 0, 0, mon_getlist_0 }, + { REQ_MON_GETLIST_1, NOAUTH, 0, 0, mon_getlist_1 }, + { REQ_RESET_STATS, AUTH, sizeof(struct reset_flags), 0, reset_stats }, + { REQ_RESET_PEER, AUTH, v4sizeof(struct conf_unpeer), + sizeof(struct conf_unpeer), reset_peer }, + { REQ_REREAD_KEYS, AUTH, 0, 0, do_key_reread }, + { REQ_TRUSTKEY, AUTH, sizeof(u_long), sizeof(u_long), trust_key }, + { REQ_UNTRUSTKEY, AUTH, sizeof(u_long), sizeof(u_long), untrust_key }, + { REQ_AUTHINFO, NOAUTH, 0, 0, get_auth_info }, + { REQ_TRAPS, NOAUTH, 0, 0, req_get_traps }, + { REQ_ADD_TRAP, AUTH, v4sizeof(struct conf_trap), + sizeof(struct conf_trap), req_set_trap }, + { REQ_CLR_TRAP, AUTH, v4sizeof(struct conf_trap), + sizeof(struct conf_trap), req_clr_trap }, + { REQ_REQUEST_KEY, AUTH, sizeof(u_long), sizeof(u_long), + set_request_keyid }, + { REQ_CONTROL_KEY, AUTH, sizeof(u_long), sizeof(u_long), + set_control_keyid }, + { REQ_GET_CTLSTATS, NOAUTH, 0, 0, get_ctl_stats }, +#ifdef KERNEL_PLL + { REQ_GET_KERNEL, NOAUTH, 0, 0, get_kernel_info }, +#endif +#ifdef REFCLOCK + { REQ_GET_CLOCKINFO, NOAUTH, sizeof(u_int32), sizeof(u_int32), + get_clock_info }, + { REQ_SET_CLKFUDGE, AUTH, sizeof(struct conf_fudge), + sizeof(struct conf_fudge), set_clock_fudge }, + { REQ_GET_CLKBUGINFO, NOAUTH, sizeof(u_int32), sizeof(u_int32), + get_clkbug_info }, +#endif + { NO_REQUEST, NOAUTH, 0, 0, 0 } +}; + + +/* + * Authentication keyid used to authenticate requests. Zero means we + * don't allow writing anything. + */ +keyid_t info_auth_keyid; + +/* + * Statistic counters to keep track of requests and responses. + */ +u_long numrequests; /* number of requests we've received */ +u_long numresppkts; /* number of resp packets sent with data */ + +u_long errorcounter[INFO_ERR_AUTH+1]; /* lazy way to count errors, indexed */ +/* by the error code */ + +/* + * A hack. To keep the authentication module clear of ntp-ism's, we + * include a time reset variable for its stats here. + */ +static u_long auth_timereset; + +/* + * Response packet used by these routines. Also some state information + * so that we can handle packet formatting within a common set of + * subroutines. Note we try to enter data in place whenever possible, + * but the need to set the more bit correctly means we occasionally + * use the extra buffer and copy. + */ +static struct resp_pkt rpkt; +static int reqver; +static int seqno; +static int nitems; +static int itemsize; +static int databytes; +static char exbuf[RESP_DATA_SIZE]; +static int usingexbuf; +static struct sockaddr_storage *toaddr; +static struct interface *frominter; + +/* + * init_request - initialize request data + */ +void +init_request (void) +{ + int i; + + numrequests = 0; + numresppkts = 0; + auth_timereset = 0; + info_auth_keyid = 0; /* by default, can't do this */ + + for (i = 0; i < sizeof(errorcounter)/sizeof(errorcounter[0]); i++) + errorcounter[i] = 0; +} + + +/* + * req_ack - acknowledge request with no data + */ +static void +req_ack( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt, + int errcode + ) +{ + /* + * fill in the fields + */ + rpkt.rm_vn_mode = RM_VN_MODE(RESP_BIT, 0, reqver); + rpkt.auth_seq = AUTH_SEQ(0, 0); + rpkt.implementation = inpkt->implementation; + rpkt.request = inpkt->request; + rpkt.err_nitems = ERR_NITEMS(errcode, 0); + rpkt.mbz_itemsize = MBZ_ITEMSIZE(0); + + /* + * send packet and bump counters + */ + sendpkt(srcadr, inter, -1, (struct pkt *)&rpkt, RESP_HEADER_SIZE); + errorcounter[errcode]++; +} + + +/* + * prepare_pkt - prepare response packet for transmission, return pointer + * to storage for data item. + */ +static char * +prepare_pkt( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *pkt, + u_int structsize + ) +{ +#ifdef DEBUG + if (debug > 3) + printf("request: preparing pkt\n"); +#endif + + /* + * Fill in the implementation, request and itemsize fields + * since these won't change. + */ + rpkt.implementation = pkt->implementation; + rpkt.request = pkt->request; + rpkt.mbz_itemsize = MBZ_ITEMSIZE(structsize); + + /* + * Compute the static data needed to carry on. + */ + toaddr = srcadr; + frominter = inter; + seqno = 0; + nitems = 0; + itemsize = structsize; + databytes = 0; + usingexbuf = 0; + + /* + * return the beginning of the packet buffer. + */ + return &rpkt.data[0]; +} + + +/* + * more_pkt - return a data pointer for a new item. + */ +static char * +more_pkt(void) +{ + /* + * If we were using the extra buffer, send the packet. + */ + if (usingexbuf) { +#ifdef DEBUG + if (debug > 2) + printf("request: sending pkt\n"); +#endif + rpkt.rm_vn_mode = RM_VN_MODE(RESP_BIT, MORE_BIT, reqver); + rpkt.auth_seq = AUTH_SEQ(0, seqno); + rpkt.err_nitems = htons((u_short)nitems); + sendpkt(toaddr, frominter, -1, (struct pkt *)&rpkt, + RESP_HEADER_SIZE+databytes); + numresppkts++; + + /* + * Copy data out of exbuf into the packet. + */ + memmove(&rpkt.data[0], exbuf, (unsigned)itemsize); + seqno++; + databytes = 0; + nitems = 0; + usingexbuf = 0; + } + + databytes += itemsize; + nitems++; + if (databytes + itemsize <= RESP_DATA_SIZE) { +#ifdef DEBUG + if (debug > 3) + printf("request: giving him more data\n"); +#endif + /* + * More room in packet. Give him the + * next address. + */ + return &rpkt.data[databytes]; + } else { + /* + * No room in packet. Give him the extra + * buffer unless this was the last in the sequence. + */ +#ifdef DEBUG + if (debug > 3) + printf("request: into extra buffer\n"); +#endif + if (seqno == MAXSEQ) + return (char *)0; + else { + usingexbuf = 1; + return exbuf; + } + } +} + + +/* + * flush_pkt - we're done, return remaining information. + */ +static void +flush_pkt(void) +{ +#ifdef DEBUG + if (debug > 2) + printf("request: flushing packet, %d items\n", nitems); +#endif + /* + * Must send the last packet. If nothing in here and nothing + * has been sent, send an error saying no data to be found. + */ + if (seqno == 0 && nitems == 0) + req_ack(toaddr, frominter, (struct req_pkt *)&rpkt, + INFO_ERR_NODATA); + else { + rpkt.rm_vn_mode = RM_VN_MODE(RESP_BIT, 0, reqver); + rpkt.auth_seq = AUTH_SEQ(0, seqno); + rpkt.err_nitems = htons((u_short)nitems); + sendpkt(toaddr, frominter, -1, (struct pkt *)&rpkt, + RESP_HEADER_SIZE+databytes); + numresppkts++; + } +} + + + +/* + * process_private - process private mode (7) packets + */ +void +process_private( + struct recvbuf *rbufp, + int mod_okay + ) +{ + struct req_pkt *inpkt; + struct req_pkt_tail *tailinpkt; + struct sockaddr_storage *srcadr; + struct interface *inter; + struct req_proc *proc; + int ec; + short temp_size; + + /* + * Initialize pointers, for convenience + */ + inpkt = (struct req_pkt *)&rbufp->recv_pkt; + srcadr = &rbufp->recv_srcadr; + inter = rbufp->dstadr; + +#ifdef DEBUG + if (debug > 2) + printf("process_private: impl %d req %d\n", + inpkt->implementation, inpkt->request); +#endif + + /* + * Do some sanity checks on the packet. Return a format + * error if it fails. + */ + ec = 0; + if ( (++ec, ISRESPONSE(inpkt->rm_vn_mode)) + || (++ec, ISMORE(inpkt->rm_vn_mode)) + || (++ec, INFO_VERSION(inpkt->rm_vn_mode) > NTP_VERSION) + || (++ec, INFO_VERSION(inpkt->rm_vn_mode) < NTP_OLDVERSION) + || (++ec, INFO_SEQ(inpkt->auth_seq) != 0) + || (++ec, INFO_ERR(inpkt->err_nitems) != 0) + || (++ec, INFO_MBZ(inpkt->mbz_itemsize) != 0) + || (++ec, rbufp->recv_length < REQ_LEN_HDR) + ) { + msyslog(LOG_ERR, "process_private: INFO_ERR_FMT: test %d failed, pkt from %s", ec, stoa(srcadr)); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + reqver = INFO_VERSION(inpkt->rm_vn_mode); + + /* + * Get the appropriate procedure list to search. + */ + if (inpkt->implementation == IMPL_UNIV) + proc = univ_codes; + else if ((inpkt->implementation == IMPL_XNTPD) || + (inpkt->implementation == IMPL_XNTPD_OLD)) + proc = ntp_codes; + else { + req_ack(srcadr, inter, inpkt, INFO_ERR_IMPL); + return; + } + + /* + * Search the list for the request codes. If it isn't one + * we know, return an error. + */ + while (proc->request_code != NO_REQUEST) { + if (proc->request_code == (short) inpkt->request) + break; + proc++; + } + if (proc->request_code == NO_REQUEST) { + req_ack(srcadr, inter, inpkt, INFO_ERR_REQ); + return; + } + +#ifdef DEBUG + if (debug > 3) + printf("found request in tables\n"); +#endif + + /* + * If we need data, check to see if we have some. If we + * don't, check to see that there is none (picky, picky). + */ + + /* This part is a bit tricky, we want to be sure that the size + * returned is either the old or the new size. We also can find + * out if the client can accept both types of messages this way. + * + * Handle the exception of REQ_CONFIG. It can have two data sizes. + */ + temp_size = INFO_ITEMSIZE(inpkt->mbz_itemsize); + if ((temp_size != proc->sizeofitem && + temp_size != proc->v6_sizeofitem) && + !(inpkt->implementation == IMPL_XNTPD && + inpkt->request == REQ_CONFIG && + temp_size == sizeof(struct old_conf_peer))) { + if (debug > 2) + printf("process_private: wrong item size, received %d, should be %d or %d\n", + temp_size, proc->sizeofitem, proc->v6_sizeofitem); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + if ((proc->sizeofitem != 0) && + ((temp_size * INFO_NITEMS(inpkt->err_nitems)) > + (rbufp->recv_length - REQ_LEN_HDR))) { + if (debug > 2) + printf("process_private: not enough data\n"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + switch (inpkt->implementation) { + case IMPL_XNTPD: + client_v6_capable = 1; + break; + case IMPL_XNTPD_OLD: + client_v6_capable = 0; + break; + default: + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + /* + * If we need to authenticate, do so. Note that an + * authenticatable packet must include a mac field, must + * have used key info_auth_keyid and must have included + * a time stamp in the appropriate field. The time stamp + * must be within INFO_TS_MAXSKEW of the receive + * time stamp. + */ + if (proc->needs_auth && sys_authenticate) { + l_fp ftmp; + double dtemp; + + if (rbufp->recv_length < (int)((REQ_LEN_HDR + + (INFO_ITEMSIZE(inpkt->mbz_itemsize) * + INFO_NITEMS(inpkt->err_nitems)) + + sizeof(struct req_pkt_tail)))) { + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + } + tailinpkt = (struct req_pkt_tail *)((char *)&rbufp->recv_pkt + + rbufp->recv_length - sizeof(struct req_pkt_tail)); + + /* + * If this guy is restricted from doing this, don't let him + * If wrong key was used, or packet doesn't have mac, return. + */ + if (!INFO_IS_AUTH(inpkt->auth_seq) || info_auth_keyid == 0 + || ntohl(tailinpkt->keyid) != info_auth_keyid) { +#ifdef DEBUG + if (debug > 4) + printf("failed auth %d info_auth_keyid %lu pkt keyid %lu\n", + INFO_IS_AUTH(inpkt->auth_seq), + (u_long)info_auth_keyid, + (u_long)ntohl(tailinpkt->keyid)); + msyslog(LOG_DEBUG, + "process_private: failed auth %d info_auth_keyid %lu pkt keyid %lu\n", + INFO_IS_AUTH(inpkt->auth_seq), + (u_long)info_auth_keyid, + (u_long)ntohl(tailinpkt->keyid)); +#endif + req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH); + return; + } + if (rbufp->recv_length > REQ_LEN_MAC) { +#ifdef DEBUG + if (debug > 4) + printf("bad pkt length %d\n", + rbufp->recv_length); +#endif + msyslog(LOG_ERR, "process_private: bad pkt length %d", + rbufp->recv_length); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + if (!mod_okay || !authhavekey(info_auth_keyid)) { +#ifdef DEBUG + if (debug > 4) + printf("failed auth mod_okay %d\n", mod_okay); + msyslog(LOG_DEBUG, + "process_private: failed auth mod_okay %d\n", + mod_okay); +#endif + req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH); + return; + } + + /* + * calculate absolute time difference between xmit time stamp + * and receive time stamp. If too large, too bad. + */ + NTOHL_FP(&tailinpkt->tstamp, &ftmp); + L_SUB(&ftmp, &rbufp->recv_time); + LFPTOD(&ftmp, dtemp); + if (fabs(dtemp) >= INFO_TS_MAXSKEW) { + /* + * He's a loser. Tell him. + */ +#ifdef DEBUG + if (debug > 4) + printf("xmit/rcv timestamp delta > INFO_TS_MAXSKEW\n"); +#endif + req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH); + return; + } + + /* + * So far so good. See if decryption works out okay. + */ + if (!authdecrypt(info_auth_keyid, (u_int32 *)inpkt, + rbufp->recv_length - sizeof(struct req_pkt_tail) + + REQ_LEN_HDR, sizeof(struct req_pkt_tail) - REQ_LEN_HDR)) { +#ifdef DEBUG + if (debug > 4) + printf("authdecrypt failed\n"); +#endif + req_ack(srcadr, inter, inpkt, INFO_ERR_AUTH); + return; + } + } + +#ifdef DEBUG + if (debug > 3) + printf("process_private: all okay, into handler\n"); +#endif + + /* + * Packet is okay. Call the handler to send him data. + */ + (proc->handler)(srcadr, inter, inpkt); +} + + +/* + * peer_list - send a list of the peers + */ +static void +peer_list( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_peer_list *ip; + register struct peer *pp; + register int i; + register int skip = 0; + + ip = (struct info_peer_list *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_peer_list)); + for (i = 0; i < HASH_SIZE && ip != 0; i++) { + pp = peer_hash[i]; + while (pp != 0 && ip != 0) { + if (pp->srcadr.ss_family == AF_INET6) { + if (client_v6_capable) { + ip->addr6 = GET_INADDR6(pp->srcadr); + ip->v6_flag = 1; + skip = 0; + } else { + skip = 1; + break; + } + } else { + ip->addr = GET_INADDR(pp->srcadr); + if (client_v6_capable) + ip->v6_flag = 0; + skip = 0; + } + + if(!skip) { + ip->port = NSRCPORT(&pp->srcadr); + ip->hmode = pp->hmode; + ip->flags = 0; + if (pp->flags & FLAG_CONFIG) + ip->flags |= INFO_FLAG_CONFIG; + if (pp == sys_peer) + ip->flags |= INFO_FLAG_SYSPEER; + if (pp->status == CTL_PST_SEL_SYNCCAND) + ip->flags |= INFO_FLAG_SEL_CANDIDATE; + if (pp->status >= CTL_PST_SEL_SYSPEER) + ip->flags |= INFO_FLAG_SHORTLIST; + ip = (struct info_peer_list *)more_pkt(); + } + pp = pp->next; + } + } + flush_pkt(); +} + + +/* + * peer_list_sum - return extended peer list + */ +static void +peer_list_sum( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_peer_summary *ips; + register struct peer *pp; + register int i; + l_fp ltmp; + register int skip; + +#ifdef DEBUG + if (debug > 2) + printf("wants peer list summary\n"); +#endif + ips = (struct info_peer_summary *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_peer_summary)); + for (i = 0; i < HASH_SIZE && ips != 0; i++) { + pp = peer_hash[i]; + while (pp != 0 && ips != 0) { +#ifdef DEBUG + if (debug > 3) + printf("sum: got one\n"); +#endif + /* + * Be careful here not to return v6 peers when we + * want only v4. + */ + if (pp->srcadr.ss_family == AF_INET6) { + if (client_v6_capable) { + ips->srcadr6 = GET_INADDR6(pp->srcadr); + ips->v6_flag = 1; + ips->dstadr6 = GET_INADDR6(pp->dstadr->sin); + skip = 0; + } else { + skip = 1; + break; + } + } else { + ips->srcadr = GET_INADDR(pp->srcadr); + if (client_v6_capable) + ips->v6_flag = 0; +/* XXX PDM This code is buggy. Need to replace with a straightforward assignment */ + ips->dstadr = (pp->processed) ? + pp->cast_flags == MDF_BCAST ? + GET_INADDR(pp->dstadr->bcast): + pp->cast_flags ? + GET_INADDR(pp->dstadr->sin) ? + GET_INADDR(pp->dstadr->sin): + GET_INADDR(pp->dstadr->bcast): + 1 : GET_INADDR(pp->dstadr->sin); + + skip = 0; + } + if (!skip){ + ips->srcport = NSRCPORT(&pp->srcadr); + ips->stratum = pp->stratum; + ips->hpoll = pp->hpoll; + ips->ppoll = pp->ppoll; + ips->reach = pp->reach; + ips->flags = 0; + if (pp == sys_peer) + ips->flags |= INFO_FLAG_SYSPEER; + if (pp->flags & FLAG_CONFIG) + ips->flags |= INFO_FLAG_CONFIG; + if (pp->flags & FLAG_REFCLOCK) + ips->flags |= INFO_FLAG_REFCLOCK; + if (pp->flags & FLAG_AUTHENABLE) + ips->flags |= INFO_FLAG_AUTHENABLE; + if (pp->flags & FLAG_PREFER) + ips->flags |= INFO_FLAG_PREFER; + if (pp->flags & FLAG_BURST) + ips->flags |= INFO_FLAG_BURST; + if (pp->status == CTL_PST_SEL_SYNCCAND) + ips->flags |= INFO_FLAG_SEL_CANDIDATE; + if (pp->status >= CTL_PST_SEL_SYSPEER) + ips->flags |= INFO_FLAG_SHORTLIST; + ips->hmode = pp->hmode; + ips->delay = HTONS_FP(DTOFP(pp->delay)); + DTOLFP(pp->offset, <mp); + HTONL_FP(<mp, &ips->offset); + ips->dispersion = HTONS_FP(DTOUFP(pp->disp)); + } + pp = pp->next; + ips = (struct info_peer_summary *)more_pkt(); + } + } + flush_pkt(); +} + + +/* + * peer_info - send information for one or more peers + */ +static void +peer_info ( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_peer_list *ipl; + register struct peer *pp; + register struct info_peer *ip; + register int items; + register int i, j; + struct sockaddr_storage addr; + extern struct peer *sys_peer; + l_fp ltmp; + + memset((char *)&addr, 0, sizeof addr); + items = INFO_NITEMS(inpkt->err_nitems); + ipl = (struct info_peer_list *) inpkt->data; + + ip = (struct info_peer *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_peer)); + while (items-- > 0 && ip != 0) { + memset((char *)&addr, 0, sizeof(addr)); + NSRCPORT(&addr) = ipl->port; + if (client_v6_capable && ipl->v6_flag != 0) { + addr.ss_family = AF_INET6; + GET_INADDR6(addr) = ipl->addr6; + } else { + addr.ss_family = AF_INET; + GET_INADDR(addr) = ipl->addr; + } +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + addr.ss_len = SOCKLEN(&addr); +#endif + ipl++; + if ((pp = findexistingpeer(&addr, (struct peer *)0, -1)) == 0) + continue; + if (pp->srcadr.ss_family == AF_INET6) { + ip->dstadr6 = pp->cast_flags == MDF_BCAST ? + GET_INADDR6(pp->dstadr->bcast) : + GET_INADDR6(pp->dstadr->sin); + ip->srcadr6 = GET_INADDR6(pp->srcadr); + ip->v6_flag = 1; + } else { +/* XXX PDM This code is buggy. Need to replace with a straightforward assignment */ + ip->dstadr = (pp->processed) ? + pp->cast_flags == MDF_BCAST ? + GET_INADDR(pp->dstadr->bcast): + pp->cast_flags ? + GET_INADDR(pp->dstadr->sin) ? + GET_INADDR(pp->dstadr->sin): + GET_INADDR(pp->dstadr->bcast): + 2 : GET_INADDR(pp->dstadr->sin); + + ip->srcadr = GET_INADDR(pp->srcadr); + if (client_v6_capable) + ip->v6_flag = 0; + } + ip->srcport = NSRCPORT(&pp->srcadr); + ip->flags = 0; + if (pp == sys_peer) + ip->flags |= INFO_FLAG_SYSPEER; + if (pp->flags & FLAG_CONFIG) + ip->flags |= INFO_FLAG_CONFIG; + if (pp->flags & FLAG_REFCLOCK) + ip->flags |= INFO_FLAG_REFCLOCK; + if (pp->flags & FLAG_AUTHENABLE) + ip->flags |= INFO_FLAG_AUTHENABLE; + if (pp->flags & FLAG_PREFER) + ip->flags |= INFO_FLAG_PREFER; + if (pp->flags & FLAG_BURST) + ip->flags |= INFO_FLAG_BURST; + if (pp->status == CTL_PST_SEL_SYNCCAND) + ip->flags |= INFO_FLAG_SEL_CANDIDATE; + if (pp->status >= CTL_PST_SEL_SYSPEER) + ip->flags |= INFO_FLAG_SHORTLIST; + ip->leap = pp->leap; + ip->hmode = pp->hmode; + ip->keyid = pp->keyid; + ip->stratum = pp->stratum; + ip->ppoll = pp->ppoll; + ip->hpoll = pp->hpoll; + ip->precision = pp->precision; + ip->version = pp->version; + ip->reach = pp->reach; + ip->unreach = (u_char) pp->unreach; + ip->flash = (u_char)pp->flash; + ip->flash2 = (u_short) pp->flash; + ip->estbdelay = HTONS_FP(DTOFP(pp->estbdelay)); + ip->ttl = pp->ttl; + ip->associd = htons(pp->associd); + ip->rootdelay = HTONS_FP(DTOUFP(pp->rootdelay)); + ip->rootdispersion = HTONS_FP(DTOUFP(pp->rootdispersion)); + ip->refid = pp->refid; + HTONL_FP(&pp->reftime, &ip->reftime); + HTONL_FP(&pp->org, &ip->org); + HTONL_FP(&pp->rec, &ip->rec); + HTONL_FP(&pp->xmt, &ip->xmt); + j = pp->filter_nextpt - 1; + for (i = 0; i < NTP_SHIFT; i++, j--) { + if (j < 0) + j = NTP_SHIFT-1; + ip->filtdelay[i] = HTONS_FP(DTOFP(pp->filter_delay[j])); + DTOLFP(pp->filter_offset[j], <mp); + HTONL_FP(<mp, &ip->filtoffset[i]); + ip->order[i] = (u_char)((pp->filter_nextpt+NTP_SHIFT-1) + - pp->filter_order[i]); + if (ip->order[i] >= NTP_SHIFT) + ip->order[i] -= NTP_SHIFT; + } + DTOLFP(pp->offset, <mp); + HTONL_FP(<mp, &ip->offset); + ip->delay = HTONS_FP(DTOFP(pp->delay)); + ip->dispersion = HTONS_FP(DTOUFP(SQRT(pp->disp))); + ip->selectdisp = HTONS_FP(DTOUFP(SQRT(pp->jitter))); + ip = (struct info_peer *)more_pkt(); + } + flush_pkt(); +} + + +/* + * peer_stats - send statistics for one or more peers + */ +static void +peer_stats ( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_peer_list *ipl; + register struct peer *pp; + register struct info_peer_stats *ip; + register int items; + struct sockaddr_storage addr; + extern struct peer *sys_peer; + + printf("peer_stats: called\n"); + items = INFO_NITEMS(inpkt->err_nitems); + ipl = (struct info_peer_list *) inpkt->data; + ip = (struct info_peer_stats *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_peer_stats)); + while (items-- > 0 && ip != 0) { + memset((char *)&addr, 0, sizeof(addr)); + NSRCPORT(&addr) = ipl->port; + if (client_v6_capable && ipl->v6_flag) { + addr.ss_family = AF_INET6; + GET_INADDR6(addr) = ipl->addr6; + } else { + addr.ss_family = AF_INET; + GET_INADDR(addr) = ipl->addr; + } +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + addr.ss_len = SOCKLEN(&addr); +#endif + printf("peer_stats: looking for %s, %d, %d\n", stoa(&addr), + ipl->port, ((struct sockaddr_in6 *)&addr)->sin6_port); + ipl = (struct info_peer_list *)((char *)ipl + + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + + if ((pp = findexistingpeer(&addr, (struct peer *)0, -1)) == 0) + continue; + printf("peer_stats: found %s\n", stoa(&addr)); + if (pp->srcadr.ss_family == AF_INET) { + ip->dstadr = (pp->processed) ? + pp->cast_flags == MDF_BCAST ? + GET_INADDR(pp->dstadr->bcast): + pp->cast_flags ? + GET_INADDR(pp->dstadr->sin) ? + GET_INADDR(pp->dstadr->sin): + GET_INADDR(pp->dstadr->bcast): + 3 : 7; + ip->srcadr = GET_INADDR(pp->srcadr); + if (client_v6_capable) + ip->v6_flag = 0; + } else { + ip->dstadr6 = pp->cast_flags == MDF_BCAST ? + GET_INADDR6(pp->dstadr->bcast): + GET_INADDR6(pp->dstadr->sin); + ip->srcadr6 = GET_INADDR6(pp->srcadr); + ip->v6_flag = 1; + } + ip->srcport = NSRCPORT(&pp->srcadr); + ip->flags = 0; + if (pp == sys_peer) + ip->flags |= INFO_FLAG_SYSPEER; + if (pp->flags & FLAG_CONFIG) + ip->flags |= INFO_FLAG_CONFIG; + if (pp->flags & FLAG_REFCLOCK) + ip->flags |= INFO_FLAG_REFCLOCK; + if (pp->flags & FLAG_AUTHENABLE) + ip->flags |= INFO_FLAG_AUTHENABLE; + if (pp->flags & FLAG_PREFER) + ip->flags |= INFO_FLAG_PREFER; + if (pp->flags & FLAG_BURST) + ip->flags |= INFO_FLAG_BURST; + if (pp->status == CTL_PST_SEL_SYNCCAND) + ip->flags |= INFO_FLAG_SEL_CANDIDATE; + if (pp->status >= CTL_PST_SEL_SYSPEER) + ip->flags |= INFO_FLAG_SHORTLIST; + ip->timereceived = htonl((u_int32)(current_time - pp->timereceived)); + ip->timetosend = htonl(pp->nextdate - current_time); + ip->timereachable = htonl((u_int32)(current_time - pp->timereachable)); + ip->sent = htonl((u_int32)(pp->sent)); + ip->processed = htonl((u_int32)(pp->processed)); + ip->badauth = htonl((u_int32)(pp->badauth)); + ip->bogusorg = htonl((u_int32)(pp->bogusorg)); + ip->oldpkt = htonl((u_int32)(pp->oldpkt)); + ip->seldisp = htonl((u_int32)(pp->seldisptoolarge)); + ip->selbroken = htonl((u_int32)(pp->selbroken)); + ip->candidate = pp->status; + ip = (struct info_peer_stats *)more_pkt(); + } + flush_pkt(); +} + + +/* + * sys_info - return system info + */ +static void +sys_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_sys *is; + + /* + * Importations from the protocol module + */ + extern u_char sys_leap; + extern u_char sys_stratum; + extern s_char sys_precision; + extern double sys_rootdelay; + extern double sys_rootdispersion; + extern u_int32 sys_refid; + extern l_fp sys_reftime; + extern u_char sys_poll; + extern struct peer *sys_peer; + extern int sys_bclient; + extern double sys_bdelay; + extern l_fp sys_authdelay; + extern double clock_stability; + extern double sys_jitter; + + is = (struct info_sys *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_sys)); + + if (sys_peer != 0) { + if (sys_peer->srcadr.ss_family == AF_INET) { + is->peer = GET_INADDR(sys_peer->srcadr); + if (client_v6_capable) + is->v6_flag = 0; + } else if (client_v6_capable) { + is->peer6 = GET_INADDR6(sys_peer->srcadr); + is->v6_flag = 1; + } + is->peer_mode = sys_peer->hmode; + } else { + is->peer = 0; + if (client_v6_capable) { + is->v6_flag = 0; + } + is->peer_mode = 0; + } + + is->leap = sys_leap; + is->stratum = sys_stratum; + is->precision = sys_precision; + is->rootdelay = htonl(DTOFP(sys_rootdelay)); + is->rootdispersion = htonl(DTOUFP(sys_rootdispersion)); + is->frequency = htonl(DTOFP(sys_jitter)); + is->stability = htonl(DTOUFP(clock_stability * 1e6)); + is->refid = sys_refid; + HTONL_FP(&sys_reftime, &is->reftime); + + is->poll = sys_poll; + + is->flags = 0; + if (sys_authenticate) + is->flags |= INFO_FLAG_AUTHENTICATE; + if (sys_bclient) + is->flags |= INFO_FLAG_BCLIENT; +#ifdef REFCLOCK + if (cal_enable) + is->flags |= INFO_FLAG_CAL; +#endif /* REFCLOCK */ + if (kern_enable) + is->flags |= INFO_FLAG_KERNEL; + if (mon_enabled != MON_OFF) + is->flags |= INFO_FLAG_MONITOR; + if (ntp_enable) + is->flags |= INFO_FLAG_NTP; + if (pps_enable) + is->flags |= INFO_FLAG_PPS_SYNC; + if (stats_control) + is->flags |= INFO_FLAG_FILEGEN; + is->bdelay = HTONS_FP(DTOFP(sys_bdelay)); + HTONL_UF(sys_authdelay.l_f, &is->authdelay); + + (void) more_pkt(); + flush_pkt(); +} + + +/* + * sys_stats - return system statistics + */ +static void +sys_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_sys_stats *ss; + + /* + * Importations from the protocol module + */ + ss = (struct info_sys_stats *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_sys_stats)); + ss->timeup = htonl((u_int32)current_time); + ss->timereset = htonl((u_int32)(current_time - sys_stattime)); + ss->denied = htonl((u_int32)sys_restricted); + ss->oldversionpkt = htonl((u_int32)sys_oldversionpkt); + ss->newversionpkt = htonl((u_int32)sys_newversionpkt); + ss->unknownversion = htonl((u_int32)sys_unknownversion); + ss->badlength = htonl((u_int32)sys_badlength); + ss->processed = htonl((u_int32)sys_processed); + ss->badauth = htonl((u_int32)sys_badauth); + ss->limitrejected = htonl((u_int32)sys_limitrejected); + ss->received = htonl((u_int32)sys_received); + (void) more_pkt(); + flush_pkt(); +} + + +/* + * mem_stats - return memory statistics + */ +static void +mem_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_mem_stats *ms; + register int i; + + /* + * Importations from the peer module + */ + extern int peer_hash_count[HASH_SIZE]; + extern int peer_free_count; + extern u_long peer_timereset; + extern u_long findpeer_calls; + extern u_long peer_allocations; + extern u_long peer_demobilizations; + extern int total_peer_structs; + + ms = (struct info_mem_stats *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_mem_stats)); + + ms->timereset = htonl((u_int32)(current_time - peer_timereset)); + ms->totalpeermem = htons((u_short)total_peer_structs); + ms->freepeermem = htons((u_short)peer_free_count); + ms->findpeer_calls = htonl((u_int32)findpeer_calls); + ms->allocations = htonl((u_int32)peer_allocations); + ms->demobilizations = htonl((u_int32)peer_demobilizations); + + for (i = 0; i < HASH_SIZE; i++) { + if (peer_hash_count[i] > 255) + ms->hashcount[i] = 255; + else + ms->hashcount[i] = (u_char)peer_hash_count[i]; + } + + (void) more_pkt(); + flush_pkt(); +} + + +/* + * io_stats - return io statistics + */ +static void +io_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_io_stats *io; + + /* + * Importations from the io module + */ + extern u_long io_timereset; + + io = (struct info_io_stats *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_io_stats)); + + io->timereset = htonl((u_int32)(current_time - io_timereset)); + io->totalrecvbufs = htons((u_short) total_recvbuffs()); + io->freerecvbufs = htons((u_short) free_recvbuffs()); + io->fullrecvbufs = htons((u_short) full_recvbuffs()); + io->lowwater = htons((u_short) lowater_additions()); + io->dropped = htonl((u_int32)packets_dropped); + io->ignored = htonl((u_int32)packets_ignored); + io->received = htonl((u_int32)packets_received); + io->sent = htonl((u_int32)packets_sent); + io->notsent = htonl((u_int32)packets_notsent); + io->interrupts = htonl((u_int32)handler_calls); + io->int_received = htonl((u_int32)handler_pkts); + + (void) more_pkt(); + flush_pkt(); +} + + +/* + * timer_stats - return timer statistics + */ +static void +timer_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_timer_stats *ts; + + /* + * Importations from the timer module + */ + extern u_long timer_timereset; + extern u_long timer_overflows; + extern u_long timer_xmtcalls; + + ts = (struct info_timer_stats *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_timer_stats)); + + ts->timereset = htonl((u_int32)(current_time - timer_timereset)); + ts->alarms = htonl((u_int32)alarm_overflow); + ts->overflows = htonl((u_int32)timer_overflows); + ts->xmtcalls = htonl((u_int32)timer_xmtcalls); + + (void) more_pkt(); + flush_pkt(); +} + + +/* + * loop_info - return the current state of the loop filter + */ +static void +loop_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_loop *li; + l_fp ltmp; + + /* + * Importations from the loop filter module + */ + extern double last_offset; + extern double drift_comp; + extern int tc_counter; + extern u_long last_time; + + li = (struct info_loop *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_loop)); + + DTOLFP(last_offset, <mp); + HTONL_FP(<mp, &li->last_offset); + DTOLFP(drift_comp * 1e6, <mp); + HTONL_FP(<mp, &li->drift_comp); + li->compliance = htonl((u_int32)(tc_counter)); + li->watchdog_timer = htonl((u_int32)(current_time - last_time)); + + (void) more_pkt(); + flush_pkt(); +} + + +/* + * do_conf - add a peer to the configuration list + */ +static void +do_conf( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + int items; + u_int fl; + struct conf_peer *cp; + struct conf_peer temp_cp; + struct sockaddr_storage peeraddr; + struct sockaddr_in tmp_clock; + + /* + * Do a check of everything to see that it looks + * okay. If not, complain about it. Note we are + * very picky here. + */ + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_peer *)inpkt->data; + memset(&temp_cp, 0, sizeof(struct conf_peer)); + memcpy(&temp_cp, (char *)cp, INFO_ITEMSIZE(inpkt->mbz_itemsize)); + fl = 0; + while (items-- > 0 && !fl) { + if (((temp_cp.version) > NTP_VERSION) + || ((temp_cp.version) < NTP_OLDVERSION)) + fl = 1; + if (temp_cp.hmode != MODE_ACTIVE + && temp_cp.hmode != MODE_CLIENT + && temp_cp.hmode != MODE_BROADCAST) + fl = 1; + if (temp_cp.flags & ~(CONF_FLAG_AUTHENABLE | CONF_FLAG_PREFER + | CONF_FLAG_BURST | CONF_FLAG_SKEY)) + fl = 1; + cp = (struct conf_peer *) + ((char *)cp + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + if (fl) { + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + /* + * Looks okay, try it out + */ + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_peer *)inpkt->data; + + while (items-- > 0) { + memset(&temp_cp, 0, sizeof(struct conf_peer)); + memcpy(&temp_cp, (char *)cp, INFO_ITEMSIZE(inpkt->mbz_itemsize)); + memset((char *)&peeraddr, 0, sizeof(struct sockaddr_storage)); + + fl = 0; + if (temp_cp.flags & CONF_FLAG_AUTHENABLE) + fl |= FLAG_AUTHENABLE; + if (temp_cp.flags & CONF_FLAG_PREFER) + fl |= FLAG_PREFER; + if (temp_cp.flags & CONF_FLAG_BURST) + fl |= FLAG_BURST; + if (temp_cp.flags & CONF_FLAG_SKEY) + fl |= FLAG_SKEY; + if (client_v6_capable && temp_cp.v6_flag != 0) { + peeraddr.ss_family = AF_INET6; + GET_INADDR6(peeraddr) = temp_cp.peeraddr6; + } else { + peeraddr.ss_family = AF_INET; + GET_INADDR(peeraddr) = temp_cp.peeraddr; + /* + * Make sure the address is valid + */ + tmp_clock = *CAST_V4(peeraddr); + if ( +#ifdef REFCLOCK + !ISREFCLOCKADR(&tmp_clock) && +#endif + ISBADADR(&tmp_clock)) { + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + } + NSRCPORT(&peeraddr) = htons(NTP_PORT); +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + peeraddr.ss_len = SOCKLEN(&peeraddr); +#endif + + /* XXX W2DO? minpoll/maxpoll arguments ??? */ + if (peer_config(&peeraddr, (struct interface *)0, + temp_cp.hmode, temp_cp.version, temp_cp.minpoll, + temp_cp.maxpoll, fl, temp_cp.ttl, temp_cp.keyid, + NULL) == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + cp = (struct conf_peer *) + ((char *)cp + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + +#if 0 +/* XXX */ +/* + * dns_a - Snarf DNS info for an association ID + */ +static void +dns_a( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_dns_assoc *dp; + register int items; + struct sockaddr_in peeraddr; + + /* + * Do a check of everything to see that it looks + * okay. If not, complain about it. Note we are + * very picky here. + */ + items = INFO_NITEMS(inpkt->err_nitems); + dp = (struct info_dns_assoc *)inpkt->data; + + /* + * Looks okay, try it out + */ + items = INFO_NITEMS(inpkt->err_nitems); + dp = (struct info_dns_assoc *)inpkt->data; + memset((char *)&peeraddr, 0, sizeof(struct sockaddr_in)); + peeraddr.sin_family = AF_INET; + peeraddr.sin_port = htons(NTP_PORT); + + /* + * Make sure the address is valid + */ + if ( +#ifdef REFCLOCK + !ISREFCLOCKADR(&peeraddr) && +#endif + ISBADADR(&peeraddr)) { +#ifdef REFCLOCK + msyslog(LOG_ERR, "dns_a: !ISREFCLOCK && ISBADADR"); +#else + msyslog(LOG_ERR, "dns_a: ISBADADR"); +#endif + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + while (items-- > 0) { + associd_t associd; + size_t hnl; + struct peer *peer; + int bogon = 0; + + associd = dp->associd; + peer = findpeerbyassoc(associd); + if (peer == 0 || peer->flags & FLAG_REFCLOCK) { + msyslog(LOG_ERR, "dns_a: %s", + (peer == 0) + ? "peer == 0" + : "peer->flags & FLAG_REFCLOCK"); + ++bogon; + } + peeraddr.sin_addr.s_addr = dp->peeraddr; + for (hnl = 0; dp->hostname[hnl] && hnl < sizeof dp->hostname; ++hnl) ; + if (hnl >= sizeof dp->hostname) { + msyslog(LOG_ERR, "dns_a: hnl (%ld) >= %ld", + (long)hnl, (long)sizeof dp->hostname); + ++bogon; + } + + msyslog(LOG_INFO, "dns_a: <%s> for %s, AssocID %d, bogon %d", + dp->hostname, + stoa((struct sockaddr_storage *)&peeraddr), associd, + bogon); + + if (bogon) { + /* If it didn't work */ + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } else { +#if 0 +#ifdef PUBKEY + crypto_public(peer, dp->hostname); +#endif /* PUBKEY */ +#endif + } + + dp++; + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} +#endif /* 0 */ + +/* + * do_unconf - remove a peer from the configuration list + */ +static void +do_unconf( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct conf_unpeer *cp; + struct conf_unpeer temp_cp; + register int items; + register struct peer *peer; + struct sockaddr_storage peeraddr; + int bad, found; + + /* + * This is a bit unstructured, but I like to be careful. + * We check to see that every peer exists and is actually + * configured. If so, we remove them. If not, we return + * an error. + */ + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_unpeer *)inpkt->data; + + bad = 0; + while (items-- > 0 && !bad) { + memset(&temp_cp, 0, sizeof(temp_cp)); + memset(&peeraddr, 0, sizeof(peeraddr)); + memcpy(&temp_cp, cp, INFO_ITEMSIZE(inpkt->mbz_itemsize)); + if (client_v6_capable && temp_cp.v6_flag != 0) { + peeraddr.ss_family = AF_INET6; + GET_INADDR6(peeraddr) = temp_cp.peeraddr6; + } else { + peeraddr.ss_family = AF_INET; + GET_INADDR(peeraddr) = temp_cp.peeraddr; + } + NSRCPORT(&peeraddr) = htons(NTP_PORT); +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + peeraddr.ss_len = SOCKLEN(&peeraddr); +#endif + found = 0; + peer = (struct peer *)0; + printf("searching for %s\n", stoa(&peeraddr)); + while (!found) { + peer = findexistingpeer(&peeraddr, peer, -1); + if (peer == (struct peer *)0) + break; + if (peer->flags & FLAG_CONFIG) + found = 1; + } + if (!found) + bad = 1; + cp = (struct conf_unpeer *) + ((char *)cp + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + if (bad) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + /* + * Now do it in earnest. + */ + + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_unpeer *)inpkt->data; + while (items-- > 0) { + memset(&temp_cp, 0, sizeof(temp_cp)); + memset(&peeraddr, 0, sizeof(peeraddr)); + memcpy(&temp_cp, cp, INFO_ITEMSIZE(inpkt->mbz_itemsize)); + if (client_v6_capable && temp_cp.v6_flag != 0) { + peeraddr.ss_family = AF_INET6; + GET_INADDR6(peeraddr) = temp_cp.peeraddr6; + } else { + peeraddr.ss_family = AF_INET; + GET_INADDR(peeraddr) = temp_cp.peeraddr; + } + NSRCPORT(&peeraddr) = htons(NTP_PORT); +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + peeraddr.ss_len = SOCKLEN(&peeraddr); +#endif + peer_unconfig(&peeraddr, (struct interface *)0, -1); + cp = (struct conf_unpeer *) + ((char *)cp + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * set_sys_flag - set system flags + */ +static void +set_sys_flag( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + setclr_flags(srcadr, inter, inpkt, 1); +} + + +/* + * clr_sys_flag - clear system flags + */ +static void +clr_sys_flag( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + setclr_flags(srcadr, inter, inpkt, 0); +} + + +/* + * setclr_flags - do the grunge work of flag setting/clearing + */ +static void +setclr_flags( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt, + u_long set + ) +{ + register u_int flags; + + if (INFO_NITEMS(inpkt->err_nitems) > 1) { + msyslog(LOG_ERR, "setclr_flags: err_nitems > 1"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + flags = ((struct conf_sys_flags *)inpkt->data)->flags; + + if (flags & ~(SYS_FLAG_BCLIENT | SYS_FLAG_PPS | + SYS_FLAG_NTP | SYS_FLAG_KERNEL | SYS_FLAG_MONITOR | + SYS_FLAG_FILEGEN | SYS_FLAG_AUTH | SYS_FLAG_CAL)) { + msyslog(LOG_ERR, "setclr_flags: extra flags: %#x", + flags & ~(SYS_FLAG_BCLIENT | SYS_FLAG_PPS | + SYS_FLAG_NTP | SYS_FLAG_KERNEL | + SYS_FLAG_MONITOR | SYS_FLAG_FILEGEN | + SYS_FLAG_AUTH | SYS_FLAG_CAL)); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + if (flags & SYS_FLAG_BCLIENT) + proto_config(PROTO_BROADCLIENT, set, 0., NULL); + if (flags & SYS_FLAG_PPS) + proto_config(PROTO_PPS, set, 0., NULL); + if (flags & SYS_FLAG_NTP) + proto_config(PROTO_NTP, set, 0., NULL); + if (flags & SYS_FLAG_KERNEL) + proto_config(PROTO_KERNEL, set, 0., NULL); + if (flags & SYS_FLAG_MONITOR) + proto_config(PROTO_MONITOR, set, 0., NULL); + if (flags & SYS_FLAG_FILEGEN) + proto_config(PROTO_FILEGEN, set, 0., NULL); + if (flags & SYS_FLAG_AUTH) + proto_config(PROTO_AUTHENTICATE, set, 0., NULL); + if (flags & SYS_FLAG_CAL) + proto_config(PROTO_CAL, set, 0., NULL); + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * list_restrict - return the restrict list + */ +static void +list_restrict( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_restrict *ir; + register struct restrictlist *rl; + register struct restrictlist6 *rl6; + +#ifdef DEBUG + if (debug > 2) + printf("wants restrict list summary\n"); +#endif + + ir = (struct info_restrict *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_restrict)); + + for (rl = restrictlist; rl != 0 && ir != 0; rl = rl->next) { + ir->addr = htonl(rl->addr); + if (client_v6_capable) + ir->v6_flag = 0; + ir->mask = htonl(rl->mask); + ir->count = htonl((u_int32)rl->count); + ir->flags = htons(rl->flags); + ir->mflags = htons(rl->mflags); + ir = (struct info_restrict *)more_pkt(); + } + if (client_v6_capable) + for (rl6 = restrictlist6; rl6 != 0 && ir != 0; rl6 = rl6->next) { + ir->addr6 = rl6->addr6; + ir->mask6 = rl6->mask6; + ir->v6_flag = 1; + ir->count = htonl((u_int32)rl6->count); + ir->flags = htons(rl6->flags); + ir->mflags = htons(rl6->mflags); + ir = (struct info_restrict *)more_pkt(); + } + flush_pkt(); +} + + + +/* + * do_resaddflags - add flags to a restrict entry (or create one) + */ +static void +do_resaddflags( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_restrict(srcadr, inter, inpkt, RESTRICT_FLAGS); +} + + + +/* + * do_ressubflags - remove flags from a restrict entry + */ +static void +do_ressubflags( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_restrict(srcadr, inter, inpkt, RESTRICT_UNFLAG); +} + + +/* + * do_unrestrict - remove a restrict entry from the list + */ +static void +do_unrestrict( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_restrict(srcadr, inter, inpkt, RESTRICT_REMOVE); +} + + + + + +/* + * do_restrict - do the dirty stuff of dealing with restrictions + */ +static void +do_restrict( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt, + int op + ) +{ + register struct conf_restrict *cr; + register int items; + struct sockaddr_storage matchaddr; + struct sockaddr_storage matchmask; + int bad; + + /* + * Do a check of the flags to make sure that only + * the NTPPORT flag is set, if any. If not, complain + * about it. Note we are very picky here. + */ + items = INFO_NITEMS(inpkt->err_nitems); + cr = (struct conf_restrict *)inpkt->data; + + bad = 0; + while (items-- > 0 && !bad) { + if (cr->mflags & ~(RESM_NTPONLY)) + bad |= 1; + if (cr->flags & ~(RES_ALLFLAGS)) + bad |= 2; + if (cr->mask != htonl(INADDR_ANY)) { + if (client_v6_capable && cr->v6_flag != 0) { + if (IN6_IS_ADDR_UNSPECIFIED(&cr->addr6)) + bad |= 4; + } else + if (cr->addr == htonl(INADDR_ANY)) + bad |= 8; + } + cr = (struct conf_restrict *)((char *)cr + + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + if (bad) { + msyslog(LOG_ERR, "do_restrict: bad = %#x", bad); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + /* + * Looks okay, try it out + */ + items = INFO_NITEMS(inpkt->err_nitems); + cr = (struct conf_restrict *)inpkt->data; + memset((char *)&matchaddr, 0, sizeof(struct sockaddr_storage)); + memset((char *)&matchmask, 0, sizeof(struct sockaddr_storage)); + + while (items-- > 0) { + if (client_v6_capable && cr->v6_flag != 0) { + GET_INADDR6(matchaddr) = cr->addr6; + GET_INADDR6(matchmask) = cr->mask6; + matchaddr.ss_family = AF_INET6; + matchmask.ss_family = AF_INET6; + } else { + GET_INADDR(matchaddr) = cr->addr; + GET_INADDR(matchmask) = cr->mask; + matchaddr.ss_family = AF_INET; + matchmask.ss_family = AF_INET; + } + hack_restrict(op, &matchaddr, &matchmask, cr->mflags, + cr->flags); + cr++; + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * mon_getlist - return monitor data + */ +static void +mon_getlist_0( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_monitor *im; + register struct mon_data *md; + extern struct mon_data mon_mru_list; + extern int mon_enabled; + +#ifdef DEBUG + if (debug > 2) + printf("wants monitor 0 list\n"); +#endif + if (!mon_enabled) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + im = (struct info_monitor *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_monitor)); + for (md = mon_mru_list.mru_next; md != &mon_mru_list && im != 0; + md = md->mru_next) { + im->lasttime = htonl((u_int32)md->avg_interval); + im->firsttime = htonl((u_int32)(current_time - md->lasttime)); + im->lastdrop = htonl((u_int32)md->drop_count); + im->count = htonl((u_int32)(md->count)); + if (md->rmtadr.ss_family == AF_INET6) { + if (!client_v6_capable) + continue; + im->addr6 = GET_INADDR6(md->rmtadr); + im->v6_flag = 1; + } else { + im->addr = GET_INADDR(md->rmtadr); + if (client_v6_capable) + im->v6_flag = 0; + } + im->port = md->rmtport; + im->mode = md->mode; + im->version = md->version; + im = (struct info_monitor *)more_pkt(); + } + flush_pkt(); +} + +/* + * mon_getlist - return monitor data + */ +static void +mon_getlist_1( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_monitor_1 *im; + register struct mon_data *md; + extern struct mon_data mon_mru_list; + extern int mon_enabled; + + if (!mon_enabled) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + im = (struct info_monitor_1 *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_monitor_1)); + for (md = mon_mru_list.mru_next; md != &mon_mru_list && im != 0; + md = md->mru_next) { + im->lasttime = htonl((u_int32)md->avg_interval); + im->firsttime = htonl((u_int32)(current_time - md->lasttime)); + im->lastdrop = htonl((u_int32)md->drop_count); + im->count = htonl((u_int32)md->count); + if (md->rmtadr.ss_family == AF_INET6) { + if (!client_v6_capable) + continue; + im->addr6 = GET_INADDR6(md->rmtadr); + im->v6_flag = 1; + im->daddr6 = GET_INADDR6(md->interface->sin); + } else { + im->addr = GET_INADDR(md->rmtadr); + if (client_v6_capable) + im->v6_flag = 0; + im->daddr = (md->cast_flags == MDF_BCAST) + ? GET_INADDR(md->interface->bcast) + : (md->cast_flags + ? (GET_INADDR(md->interface->sin) + ? GET_INADDR(md->interface->sin) + : GET_INADDR(md->interface->bcast)) + : 4); + } + im->flags = md->cast_flags; + im->port = md->rmtport; + im->mode = md->mode; + im->version = md->version; + im = (struct info_monitor_1 *)more_pkt(); + } + flush_pkt(); +} + +/* + * Module entry points and the flags they correspond with + */ +struct reset_entry { + int flag; /* flag this corresponds to */ + void (*handler) P((void)); /* routine to handle request */ +}; + +struct reset_entry reset_entries[] = { + { RESET_FLAG_ALLPEERS, peer_all_reset }, + { RESET_FLAG_IO, io_clr_stats }, + { RESET_FLAG_SYS, proto_clr_stats }, + { RESET_FLAG_MEM, peer_clr_stats }, + { RESET_FLAG_TIMER, timer_clr_stats }, + { RESET_FLAG_AUTH, reset_auth_stats }, + { RESET_FLAG_CTL, ctl_clr_stats }, + { 0, 0 } +}; + +/* + * reset_stats - reset statistic counters here and there + */ +static void +reset_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + u_long flags; + struct reset_entry *rent; + + if (INFO_NITEMS(inpkt->err_nitems) > 1) { + msyslog(LOG_ERR, "reset_stats: err_nitems > 1"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + flags = ((struct reset_flags *)inpkt->data)->flags; + + if (flags & ~RESET_ALLFLAGS) { + msyslog(LOG_ERR, "reset_stats: reset leaves %#lx", + flags & ~RESET_ALLFLAGS); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + for (rent = reset_entries; rent->flag != 0; rent++) { + if (flags & rent->flag) + (rent->handler)(); + } + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * reset_peer - clear a peer's statistics + */ +static void +reset_peer( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct conf_unpeer *cp; + register int items; + register struct peer *peer; + struct sockaddr_storage peeraddr; + int bad; + + /* + * We check first to see that every peer exists. If not, + * we return an error. + */ + + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_unpeer *)inpkt->data; + + bad = 0; + while (items-- > 0 && !bad) { + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + if (client_v6_capable && cp->v6_flag != 0) { + GET_INADDR6(peeraddr) = cp->peeraddr6; + peeraddr.ss_family = AF_INET6; + } else { + GET_INADDR(peeraddr) = cp->peeraddr; + peeraddr.ss_family = AF_INET; + } + NSRCPORT(&peeraddr) = htons(NTP_PORT); +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + peeraddr.ss_len = SOCKLEN(&peeraddr); +#endif + peer = findexistingpeer(&peeraddr, (struct peer *)0, -1); + if (peer == (struct peer *)0) + bad++; + cp = (struct conf_unpeer *)((char *)cp + + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + if (bad) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + /* + * Now do it in earnest. + */ + + items = INFO_NITEMS(inpkt->err_nitems); + cp = (struct conf_unpeer *)inpkt->data; + while (items-- > 0) { + memset((char *)&peeraddr, 0, sizeof(peeraddr)); + if (client_v6_capable && cp->v6_flag != 0) { + GET_INADDR6(peeraddr) = cp->peeraddr6; + peeraddr.ss_family = AF_INET6; + } else { + GET_INADDR(peeraddr) = cp->peeraddr; + peeraddr.ss_family = AF_INET; + } +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + peeraddr.ss_len = SOCKLEN(&peeraddr); +#endif + peer = findexistingpeer(&peeraddr, (struct peer *)0, -1); + while (peer != 0) { + peer_reset(peer); + peer = findexistingpeer(&peeraddr, (struct peer *)peer, -1); + } + cp = (struct conf_unpeer *)((char *)cp + + INFO_ITEMSIZE(inpkt->mbz_itemsize)); + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * do_key_reread - reread the encryption key file + */ +static void +do_key_reread( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + rereadkeys(); + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * trust_key - make one or more keys trusted + */ +static void +trust_key( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_trustkey(srcadr, inter, inpkt, 1); +} + + +/* + * untrust_key - make one or more keys untrusted + */ +static void +untrust_key( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_trustkey(srcadr, inter, inpkt, 0); +} + + +/* + * do_trustkey - make keys either trustable or untrustable + */ +static void +do_trustkey( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt, + u_long trust + ) +{ + register u_long *kp; + register int items; + + items = INFO_NITEMS(inpkt->err_nitems); + kp = (u_long *)inpkt->data; + while (items-- > 0) { + authtrust(*kp, trust); + kp++; + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + +/* + * get_auth_info - return some stats concerning the authentication module + */ +static void +get_auth_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_auth *ia; + + /* + * Importations from the authentication module + */ + extern u_long authnumkeys; + extern int authnumfreekeys; + extern u_long authkeylookups; + extern u_long authkeynotfound; + extern u_long authencryptions; + extern u_long authdecryptions; + extern u_long authkeyuncached; + extern u_long authkeyexpired; + + ia = (struct info_auth *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_auth)); + + ia->numkeys = htonl((u_int32)authnumkeys); + ia->numfreekeys = htonl((u_int32)authnumfreekeys); + ia->keylookups = htonl((u_int32)authkeylookups); + ia->keynotfound = htonl((u_int32)authkeynotfound); + ia->encryptions = htonl((u_int32)authencryptions); + ia->decryptions = htonl((u_int32)authdecryptions); + ia->keyuncached = htonl((u_int32)authkeyuncached); + ia->expired = htonl((u_int32)authkeyexpired); + ia->timereset = htonl((u_int32)(current_time - auth_timereset)); + + (void) more_pkt(); + flush_pkt(); +} + + + +/* + * reset_auth_stats - reset the authentication stat counters. Done here + * to keep ntp-isms out of the authentication module + */ +static void +reset_auth_stats(void) +{ + /* + * Importations from the authentication module + */ + extern u_long authkeylookups; + extern u_long authkeynotfound; + extern u_long authencryptions; + extern u_long authdecryptions; + extern u_long authkeyuncached; + + authkeylookups = 0; + authkeynotfound = 0; + authencryptions = 0; + authdecryptions = 0; + authkeyuncached = 0; + auth_timereset = current_time; +} + + +/* + * req_get_traps - return information about current trap holders + */ +static void +req_get_traps( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_trap *it; + register struct ctl_trap *tr; + register int i; + + /* + * Imported from the control module + */ + extern struct ctl_trap ctl_trap[]; + extern int num_ctl_traps; + + if (num_ctl_traps == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + it = (struct info_trap *)prepare_pkt(srcadr, inter, inpkt, + v6sizeof(struct info_trap)); + + for (i = 0, tr = ctl_trap; i < CTL_MAXTRAPS; i++, tr++) { + if (tr->tr_flags & TRAP_INUSE) { + if (tr->tr_addr.ss_family == AF_INET) { + if (tr->tr_localaddr == any_interface) + it->local_address = 0; + else + it->local_address + = GET_INADDR(tr->tr_localaddr->sin); + it->trap_address = GET_INADDR(tr->tr_addr); + if (client_v6_capable) + it->v6_flag = 0; + } else { + if (!client_v6_capable) + continue; + it->local_address6 + = GET_INADDR6(tr->tr_localaddr->sin); + it->trap_address6 = GET_INADDR6(tr->tr_addr); + it->v6_flag = 1; + } + it->trap_port = NSRCPORT(&tr->tr_addr); + it->sequence = htons(tr->tr_sequence); + it->settime = htonl((u_int32)(current_time - tr->tr_settime)); + it->origtime = htonl((u_int32)(current_time - tr->tr_origtime)); + it->resets = htonl((u_int32)tr->tr_resets); + it->flags = htonl((u_int32)tr->tr_flags); + it = (struct info_trap *)more_pkt(); + } + } + flush_pkt(); +} + + +/* + * req_set_trap - configure a trap + */ +static void +req_set_trap( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_setclr_trap(srcadr, inter, inpkt, 1); +} + + + +/* + * req_clr_trap - unconfigure a trap + */ +static void +req_clr_trap( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + do_setclr_trap(srcadr, inter, inpkt, 0); +} + + + +/* + * do_setclr_trap - do the grunge work of (un)configuring a trap + */ +static void +do_setclr_trap( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt, + int set + ) +{ + register struct conf_trap *ct; + register struct interface *linter; + int res; + struct sockaddr_storage laddr; + + /* + * Prepare sockaddr_storage structure + */ + memset((char *)&laddr, 0, sizeof laddr); + laddr.ss_family = srcadr->ss_family; + NSRCPORT(&laddr) = ntohs(NTP_PORT); + + /* + * Restrict ourselves to one item only. This eliminates + * the error reporting problem. + */ + if (INFO_NITEMS(inpkt->err_nitems) > 1) { + msyslog(LOG_ERR, "do_setclr_trap: err_nitems > 1"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + ct = (struct conf_trap *)inpkt->data; + + /* + * Look for the local interface. If none, use the default. + */ + if (ct->local_address == 0) { + linter = any_interface; + } else { + if (laddr.ss_family == AF_INET) + GET_INADDR(laddr) = ct->local_address; + else + GET_INADDR6(laddr) = ct->local_address6; + linter = findinterface(&laddr); + if (linter == NULL) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + } + + if (laddr.ss_family == AF_INET) + GET_INADDR(laddr) = ct->trap_address; + else + GET_INADDR6(laddr) = ct->trap_address6; + if (ct->trap_port != 0) + NSRCPORT(&laddr) = ct->trap_port; + else + NSRCPORT(&laddr) = htons(TRAPPORT); + + if (set) { + res = ctlsettrap(&laddr, linter, 0, + INFO_VERSION(inpkt->rm_vn_mode)); + } else { + res = ctlclrtrap(&laddr, linter, 0); + } + + if (!res) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + } else { + req_ack(srcadr, inter, inpkt, INFO_OKAY); + } + return; +} + + + +/* + * set_request_keyid - set the keyid used to authenticate requests + */ +static void +set_request_keyid( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + keyid_t keyid; + + /* + * Restrict ourselves to one item only. + */ + if (INFO_NITEMS(inpkt->err_nitems) > 1) { + msyslog(LOG_ERR, "set_request_keyid: err_nitems > 1"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + keyid = ntohl(*((u_int32 *)(inpkt->data))); + info_auth_keyid = keyid; + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + + +/* + * set_control_keyid - set the keyid used to authenticate requests + */ +static void +set_control_keyid( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + keyid_t keyid; + extern keyid_t ctl_auth_keyid; + + /* + * Restrict ourselves to one item only. + */ + if (INFO_NITEMS(inpkt->err_nitems) > 1) { + msyslog(LOG_ERR, "set_control_keyid: err_nitems > 1"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + keyid = ntohl(*((u_int32 *)(inpkt->data))); + ctl_auth_keyid = keyid; + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} + + + +/* + * get_ctl_stats - return some stats concerning the control message module + */ +static void +get_ctl_stats( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_control *ic; + + /* + * Importations from the control module + */ + extern u_long ctltimereset; + extern u_long numctlreq; + extern u_long numctlbadpkts; + extern u_long numctlresponses; + extern u_long numctlfrags; + extern u_long numctlerrors; + extern u_long numctltooshort; + extern u_long numctlinputresp; + extern u_long numctlinputfrag; + extern u_long numctlinputerr; + extern u_long numctlbadoffset; + extern u_long numctlbadversion; + extern u_long numctldatatooshort; + extern u_long numctlbadop; + extern u_long numasyncmsgs; + + ic = (struct info_control *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_control)); + + ic->ctltimereset = htonl((u_int32)(current_time - ctltimereset)); + ic->numctlreq = htonl((u_int32)numctlreq); + ic->numctlbadpkts = htonl((u_int32)numctlbadpkts); + ic->numctlresponses = htonl((u_int32)numctlresponses); + ic->numctlfrags = htonl((u_int32)numctlfrags); + ic->numctlerrors = htonl((u_int32)numctlerrors); + ic->numctltooshort = htonl((u_int32)numctltooshort); + ic->numctlinputresp = htonl((u_int32)numctlinputresp); + ic->numctlinputfrag = htonl((u_int32)numctlinputfrag); + ic->numctlinputerr = htonl((u_int32)numctlinputerr); + ic->numctlbadoffset = htonl((u_int32)numctlbadoffset); + ic->numctlbadversion = htonl((u_int32)numctlbadversion); + ic->numctldatatooshort = htonl((u_int32)numctldatatooshort); + ic->numctlbadop = htonl((u_int32)numctlbadop); + ic->numasyncmsgs = htonl((u_int32)numasyncmsgs); + + (void) more_pkt(); + flush_pkt(); +} + + +#ifdef KERNEL_PLL +/* + * get_kernel_info - get kernel pll/pps information + */ +static void +get_kernel_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_kernel *ik; + struct timex ntx; + + if (!pll_control) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + memset((char *)&ntx, 0, sizeof(ntx)); + if (ntp_adjtime(&ntx) < 0) + msyslog(LOG_ERR, "get_kernel_info: ntp_adjtime() failed: %m"); + ik = (struct info_kernel *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_kernel)); + + /* + * pll variables + */ + ik->offset = htonl((u_int32)ntx.offset); + ik->freq = htonl((u_int32)ntx.freq); + ik->maxerror = htonl((u_int32)ntx.maxerror); + ik->esterror = htonl((u_int32)ntx.esterror); + ik->status = htons(ntx.status); + ik->constant = htonl((u_int32)ntx.constant); + ik->precision = htonl((u_int32)ntx.precision); + ik->tolerance = htonl((u_int32)ntx.tolerance); + + /* + * pps variables + */ + ik->ppsfreq = htonl((u_int32)ntx.ppsfreq); + ik->jitter = htonl((u_int32)ntx.jitter); + ik->shift = htons(ntx.shift); + ik->stabil = htonl((u_int32)ntx.stabil); + ik->jitcnt = htonl((u_int32)ntx.jitcnt); + ik->calcnt = htonl((u_int32)ntx.calcnt); + ik->errcnt = htonl((u_int32)ntx.errcnt); + ik->stbcnt = htonl((u_int32)ntx.stbcnt); + + (void) more_pkt(); + flush_pkt(); +} +#endif /* KERNEL_PLL */ + + +#ifdef REFCLOCK +/* + * get_clock_info - get info about a clock + */ +static void +get_clock_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct info_clock *ic; + register u_int32 *clkaddr; + register int items; + struct refclockstat clock_stat; + struct sockaddr_storage addr; + struct sockaddr_in tmp_clock; + l_fp ltmp; + + memset((char *)&addr, 0, sizeof addr); + addr.ss_family = AF_INET; +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + addr.ss_len = SOCKLEN(&addr); +#endif + NSRCPORT(&addr) = htons(NTP_PORT); + items = INFO_NITEMS(inpkt->err_nitems); + clkaddr = (u_int32 *) inpkt->data; + + ic = (struct info_clock *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_clock)); + + while (items-- > 0) { + tmp_clock.sin_addr.s_addr = *clkaddr++; + CAST_V4(addr)->sin_addr = tmp_clock.sin_addr; + if (!ISREFCLOCKADR(&tmp_clock) || + findexistingpeer(&addr, (struct peer *)0, -1) == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + clock_stat.kv_list = (struct ctl_var *)0; + + refclock_control(&addr, (struct refclockstat *)0, &clock_stat); + + ic->clockadr = tmp_clock.sin_addr.s_addr; + ic->type = clock_stat.type; + ic->flags = clock_stat.flags; + ic->lastevent = clock_stat.lastevent; + ic->currentstatus = clock_stat.currentstatus; + ic->polls = htonl((u_int32)clock_stat.polls); + ic->noresponse = htonl((u_int32)clock_stat.noresponse); + ic->badformat = htonl((u_int32)clock_stat.badformat); + ic->baddata = htonl((u_int32)clock_stat.baddata); + ic->timestarted = htonl((u_int32)clock_stat.timereset); + DTOLFP(clock_stat.fudgetime1, <mp); + HTONL_FP(<mp, &ic->fudgetime1); + DTOLFP(clock_stat.fudgetime2, <mp); + HTONL_FP(<mp, &ic->fudgetime2); + ic->fudgeval1 = htonl((u_int32)clock_stat.fudgeval1); + ic->fudgeval2 = htonl((u_int32)clock_stat.fudgeval2); + + free_varlist(clock_stat.kv_list); + + ic = (struct info_clock *)more_pkt(); + } + flush_pkt(); +} + + + +/* + * set_clock_fudge - get a clock's fudge factors + */ +static void +set_clock_fudge( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register struct conf_fudge *cf; + register int items; + struct refclockstat clock_stat; + struct sockaddr_storage addr; + struct sockaddr_in tmp_clock; + l_fp ltmp; + + memset((char *)&addr, 0, sizeof addr); + memset((char *)&clock_stat, 0, sizeof clock_stat); + items = INFO_NITEMS(inpkt->err_nitems); + cf = (struct conf_fudge *) inpkt->data; + + while (items-- > 0) { + tmp_clock.sin_addr.s_addr = cf->clockadr; + *CAST_V4(addr) = tmp_clock; + addr.ss_family = AF_INET; +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + addr.ss_len = SOCKLEN(&addr); +#endif + NSRCPORT(&addr) = htons(NTP_PORT); + if (!ISREFCLOCKADR(&tmp_clock) || + findexistingpeer(&addr, (struct peer *)0, -1) == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + switch(ntohl(cf->which)) { + case FUDGE_TIME1: + NTOHL_FP(&cf->fudgetime, <mp); + LFPTOD(<mp, clock_stat.fudgetime1); + clock_stat.haveflags = CLK_HAVETIME1; + break; + case FUDGE_TIME2: + NTOHL_FP(&cf->fudgetime, <mp); + LFPTOD(<mp, clock_stat.fudgetime2); + clock_stat.haveflags = CLK_HAVETIME2; + break; + case FUDGE_VAL1: + clock_stat.fudgeval1 = ntohl(cf->fudgeval_flags); + clock_stat.haveflags = CLK_HAVEVAL1; + break; + case FUDGE_VAL2: + clock_stat.fudgeval2 = ntohl(cf->fudgeval_flags); + clock_stat.haveflags = CLK_HAVEVAL2; + break; + case FUDGE_FLAGS: + clock_stat.flags = (u_char) (ntohl(cf->fudgeval_flags) & 0xf); + clock_stat.haveflags = + (CLK_HAVEFLAG1|CLK_HAVEFLAG2|CLK_HAVEFLAG3|CLK_HAVEFLAG4); + break; + default: + msyslog(LOG_ERR, "set_clock_fudge: default!"); + req_ack(srcadr, inter, inpkt, INFO_ERR_FMT); + return; + } + + refclock_control(&addr, &clock_stat, (struct refclockstat *)0); + } + + req_ack(srcadr, inter, inpkt, INFO_OKAY); +} +#endif + +#ifdef REFCLOCK +/* + * get_clkbug_info - get debugging info about a clock + */ +static void +get_clkbug_info( + struct sockaddr_storage *srcadr, + struct interface *inter, + struct req_pkt *inpkt + ) +{ + register int i; + register struct info_clkbug *ic; + register u_int32 *clkaddr; + register int items; + struct refclockbug bug; + struct sockaddr_storage addr; + struct sockaddr_in tmp_clock; + + memset((char *)&addr, 0, sizeof addr); + addr.ss_family = AF_INET; +#ifdef HAVE_SA_LEN_IN_STRUCT_SOCKADDR + addr.ss_len = SOCKLEN(&addr); +#endif + NSRCPORT(&addr) = htons(NTP_PORT); + items = INFO_NITEMS(inpkt->err_nitems); + clkaddr = (u_int32 *) inpkt->data; + + ic = (struct info_clkbug *)prepare_pkt(srcadr, inter, inpkt, + sizeof(struct info_clkbug)); + + while (items-- > 0) { + tmp_clock.sin_addr.s_addr = *clkaddr++; + GET_INADDR(addr) = tmp_clock.sin_addr.s_addr; + if (!ISREFCLOCKADR(&tmp_clock) || + findexistingpeer(&addr, (struct peer *)0, -1) == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + memset((char *)&bug, 0, sizeof bug); + refclock_buginfo(&addr, &bug); + if (bug.nvalues == 0 && bug.ntimes == 0) { + req_ack(srcadr, inter, inpkt, INFO_ERR_NODATA); + return; + } + + ic->clockadr = tmp_clock.sin_addr.s_addr; + i = bug.nvalues; + if (i > NUMCBUGVALUES) + i = NUMCBUGVALUES; + ic->nvalues = (u_char)i; + ic->svalues = htons((u_short) (bug.svalues & ((1<<i)-1))); + while (--i >= 0) + ic->values[i] = htonl(bug.values[i]); + + i = bug.ntimes; + if (i > NUMCBUGTIMES) + i = NUMCBUGTIMES; + ic->ntimes = (u_char)i; + ic->stimes = htonl(bug.stimes); + while (--i >= 0) { + HTONL_FP(&bug.times[i], &ic->times[i]); + } + + ic = (struct info_clkbug *)more_pkt(); + } + flush_pkt(); +} +#endif diff --git a/ntpd/ntp_restrict.c b/ntpd/ntp_restrict.c new file mode 100644 index 0000000..ede4225 --- /dev/null +++ b/ntpd/ntp_restrict.c @@ -0,0 +1,586 @@ +/* + * ntp_restrict.c - determine host restrictions + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <sys/types.h> + +#include "ntpd.h" +#include "ntp_if.h" +#include "ntp_stdlib.h" + +/* + * This code keeps a simple address-and-mask list of hosts we want + * to place restrictions on (or remove them from). The restrictions + * are implemented as a set of flags which tell you what the host + * can't do. There is a subroutine entry to return the flags. The + * list is kept sorted to reduce the average number of comparisons + * and make sure you get the set of restrictions most specific to + * the address. + * + * The algorithm is that, when looking up a host, it is first assumed + * that the default set of restrictions will apply. It then searches + * down through the list. Whenever it finds a match it adopts the + * match's flags instead. When you hit the point where the sorted + * address is greater than the target, you return with the last set of + * flags you found. Because of the ordering of the list, the most + * specific match will provide the final set of flags. + * + * This was originally intended to restrict you from sync'ing to your + * own broadcasts when you are doing that, by restricting yourself from + * your own interfaces. It was also thought it would sometimes be useful + * to keep a misbehaving host or two from abusing your primary clock. It + * has been expanded, however, to suit the needs of those with more + * restrictive access policies. + */ +/* + * We will use two lists, one for IPv4 addresses and one for IPv6 + * addresses. This is not protocol-independant but for now I can't + * find a way to respect this. We'll check this later... JFB 07/2001 + */ +#define SET_IPV6_ADDR_MASK(dst, src, msk) \ + do { \ + int idx; \ + for (idx = 0; idx < 16; idx++) { \ + (dst)->s6_addr[idx] = \ + (u_char) ((src)->s6_addr[idx] & (msk)->s6_addr[idx]); \ + } \ + } while (0) + +/* + * Memory allocation parameters. We allocate INITRESLIST entries + * initially, and add INCRESLIST entries to the free list whenever + * we run out. + */ +#define INITRESLIST 10 +#define INCRESLIST 5 + +#define RES_AVG 8. /* interpacket averaging factor */ + +/* + * The restriction list + */ +struct restrictlist *restrictlist; +struct restrictlist6 *restrictlist6; +static int restrictcount; /* count of entries in the res list */ +static int restrictcount6; /* count of entries in the res list 2*/ + +/* + * The free list and associated counters. Also some uninteresting + * stat counters. + */ +static struct restrictlist *resfree; +static struct restrictlist6 *resfree6; +static int numresfree; /* number of structures on free list */ +static int numresfree6; /* number of structures on free list 2 */ + +static u_long res_calls; +static u_long res_found; +static u_long res_not_found; + +/* + * Parameters of the RES_LIMITED restriction option. + */ +u_long res_avg_interval = 5; /* min average interpacket interval */ +u_long res_min_interval = 1; /* min interpacket interval */ + +/* + * Count number of restriction entries referring to RES_LIMITED controls + * activation/deactivation of monitoring (with respect to RES_LIMITED + * control) + */ +static u_long res_limited_refcnt; +static u_long res_limited_refcnt6; + +/* + * Our initial allocation of lists entries. + */ +static struct restrictlist resinit[INITRESLIST]; +static struct restrictlist6 resinit6[INITRESLIST]; + +/* + * init_restrict - initialize the restriction data structures + */ +void +init_restrict(void) +{ + register int i; + + /* + * Zero the list and put all but one on the free list + */ + resfree = 0; + memset((char *)resinit, 0, sizeof resinit); + resfree6 = 0; + memset((char *)resinit6, 0, sizeof resinit6); + for (i = 1; i < INITRESLIST; i++) { + resinit[i].next = resfree; + resinit6[i].next = resfree6; + resfree = &resinit[i]; + resfree6 = &resinit6[i]; + } + numresfree = INITRESLIST-1; + numresfree6 = INITRESLIST-1; + + /* + * Put the remaining item at the head of the list as our default + * entry. Everything in here should be zero for now. + */ + resinit[0].addr = htonl(INADDR_ANY); + resinit[0].mask = 0; + memset(&resinit6[0].addr6, 0, sizeof(struct in6_addr)); + memset(&resinit6[0].mask6, 0, sizeof(struct in6_addr)); + restrictlist = &resinit[0]; + restrictlist6 = &resinit6[0]; + restrictcount = 1; + restrictcount = 2; + + /* + * fix up stat counters + */ + res_calls = 0; + res_found = 0; + res_not_found = 0; + + /* + * set default values for RES_LIMIT functionality + */ + res_limited_refcnt = 0; + res_limited_refcnt6 = 0; +} + + +/* + * restrictions - return restrictions for this host + */ +int +restrictions( + struct sockaddr_storage *srcadr + ) +{ + struct restrictlist *rl; + struct restrictlist *match = NULL; + struct restrictlist6 *rl6; + struct restrictlist6 *match6 = NULL; + struct in6_addr hostaddr6; + struct in6_addr hostservaddr6; + u_int32 hostaddr; + int flags = 0; + int isntpport; + + res_calls++; + if (srcadr->ss_family == AF_INET) { + /* + * We need the host address in host order. Also need to + * know whether this is from the ntp port or not. + */ + hostaddr = SRCADR(srcadr); + isntpport = (SRCPORT(srcadr) == NTP_PORT); + + /* + * Ignore any packets with a multicast source address + * (this should be done early in the receive process, + * later!) + */ + if (IN_CLASSD(SRCADR(srcadr))) + return (int)RES_IGNORE; + + /* + * Set match to first entry, which is default entry. + * Work our way down from there. + */ + match = restrictlist; + for (rl = match->next; rl != 0 && rl->addr <= hostaddr; + rl = rl->next) + if ((hostaddr & rl->mask) == rl->addr) { + if ((rl->mflags & RESM_NTPONLY) && + !isntpport) + continue; + match = rl; + } + match->count++; + if (match == restrictlist) + res_not_found++; + else + res_found++; + flags = match->flags; + } + + /* IPv6 source address */ + if (srcadr->ss_family == AF_INET6) { + /* + * Need to know whether this is from the ntp port or + * not. + */ + hostaddr6 = GET_INADDR6(*srcadr); + isntpport = (ntohs(( + (struct sockaddr_in6 *)srcadr)->sin6_port) == + NTP_PORT); + + /* + * Ignore any packets with a multicast source address + * (this should be done early in the receive process, + * later!) + */ + if (IN6_IS_ADDR_MULTICAST(&hostaddr6)) + return (int)RES_IGNORE; + + /* + * Set match to first entry, which is default entry. + * Work our way down from there. + */ + match6 = restrictlist6; + for (rl6 = match6->next; rl6 != 0 && + (memcmp(&(rl6->addr6), &hostaddr6, + sizeof(hostaddr6)) <= 0); rl6 = rl6->next) { + SET_IPV6_ADDR_MASK(&hostservaddr6, &hostaddr6, + &rl6->mask6); + if (memcmp(&hostservaddr6, &(rl6->addr6), + sizeof(hostservaddr6)) == 0) { + if ((rl6->mflags & RESM_NTPONLY) && + !isntpport) + continue; + match6 = rl6; + } + } + match6->count++; + if (match6 == restrictlist6) + res_not_found++; + else + res_found++; + flags = match6->flags; + } + + /* + * The following implements a generalized call gap facility. + * Douse the RES_LIMITED bit only if the interval since the last + * packet is greater than res_min_interval and the average is + * greater thatn res_avg_interval. + */ + if (mon_enabled == MON_OFF) { + flags &= ~RES_LIMITED; + } else { + struct mon_data *md; + + /* + * At this poin the most recent arrival is first in the + * MRU list. Let the first 10 packets in for free until + * the average stabilizes. + */ + md = mon_mru_list.mru_next; + if (md->avg_interval == 0) + md->avg_interval = md->drop_count; + else + md->avg_interval += (md->drop_count - + md->avg_interval) / RES_AVG; + if (md->count < 10 || (md->drop_count > + res_min_interval && md->avg_interval > + res_avg_interval)) + flags &= ~RES_LIMITED; + md->drop_count = flags; + } + return (flags); +} + + +/* + * hack_restrict - add/subtract/manipulate entries on the restrict list + */ +void +hack_restrict( + int op, + struct sockaddr_storage *resaddr, + struct sockaddr_storage *resmask, + int mflags, + int flags + ) +{ + register u_int32 addr = 0; + register u_int32 mask = 0; + struct in6_addr addr6; + struct in6_addr mask6; + register struct restrictlist *rl = NULL; + register struct restrictlist *rlprev = NULL; + register struct restrictlist6 *rl6 = NULL; + register struct restrictlist6 *rlprev6 = NULL; + int i, addr_cmp, mask_cmp; + memset(&addr6, 0, sizeof(struct in6_addr)); + memset(&mask6, 0, sizeof(struct in6_addr)); + + if (resaddr->ss_family == AF_INET) { + /* + * Get address and mask in host byte order + */ + addr = SRCADR(resaddr); + mask = SRCADR(resmask); + addr &= mask; /* make sure low bits zero */ + + /* + * If this is the default address, point at first on + * list. Else go searching for it. + */ + if (addr == 0) { + rlprev = 0; + rl = restrictlist; + } else { + rlprev = restrictlist; + rl = rlprev->next; + while (rl != 0) { + if (rl->addr > addr) { + rl = 0; + break; + } else if (rl->addr == addr) { + if (rl->mask == mask) { + if ((mflags & + RESM_NTPONLY) == + (rl->mflags & + RESM_NTPONLY)) + break; + + if (!(mflags & + RESM_NTPONLY)) { + rl = 0; + break; + } + } else if (rl->mask > mask) { + rl = 0; + break; + } + } + rlprev = rl; + rl = rl->next; + } + } + } + + if (resaddr->ss_family == AF_INET6) { + mask6 = GET_INADDR6(*resmask); + SET_IPV6_ADDR_MASK(&addr6, + &GET_INADDR6(*resaddr), &mask6); + if (IN6_IS_ADDR_UNSPECIFIED(&addr6)) { + rlprev6 = 0; + rl6 = restrictlist6; + } else { + rlprev6 = restrictlist6; + rl6 = rlprev6->next; + while (rl6 != 0) { + addr_cmp = memcmp(&rl6->addr6, &addr6, + sizeof(addr6)); + if (addr_cmp > 0) { + rl6 = 0; + break; + } else if (addr_cmp == 0) { + mask_cmp = memcmp(&rl6->mask6, + &mask6, sizeof(mask6)); + if (mask_cmp == 0) { + if ((mflags & + RESM_NTPONLY) == + (rl6->mflags & + RESM_NTPONLY)) + break; + + if (!(mflags & + RESM_NTPONLY)) { + rl6 = 0; + break; + } + } else if (mask_cmp > 0) { + rl6 = 0; + break; + } + } + rlprev6 = rl6; + rl6 = rl6->next; + } + } + } + + /* + * In case the above wasn't clear :-), either rl now points + * at the entry this call refers to, or rl is zero and rlprev + * points to the entry prior to where this one should go in + * the sort. + */ + + /* + * Switch based on operation + */ + if (resaddr->ss_family == AF_INET) { + switch (op) { + case RESTRICT_FLAGS: + /* + * Here we add bits to the flags. If this is a + * new restriction add it. + */ + if (rl == 0) { + if (numresfree == 0) { + rl = (struct restrictlist *) + emalloc(INCRESLIST * + sizeof(struct + restrictlist)); + memset((char *)rl, 0, + INCRESLIST * sizeof(struct + restrictlist)); + for (i = 0; i < INCRESLIST; i++) { + rl->next = resfree; + resfree = rl; + rl++; + } + numresfree = INCRESLIST; + } + + rl = resfree; + resfree = rl->next; + numresfree--; + + rl->addr = addr; + rl->mask = mask; + rl->mflags = (u_short)mflags; + + rl->next = rlprev->next; + rlprev->next = rl; + restrictcount++; + } + if ((rl->flags ^ (u_short)flags) & + RES_LIMITED) { + res_limited_refcnt++; + mon_start(MON_RES); + } + rl->flags |= (u_short)flags; + break; + + case RESTRICT_UNFLAG: + /* + * Remove some bits from the flags. If we didn't + * find this one, just return. + */ + if (rl != 0) { + if ((rl->flags ^ (u_short)flags) & + RES_LIMITED) { + res_limited_refcnt--; + if (res_limited_refcnt == 0) + mon_stop(MON_RES); + } + rl->flags &= (u_short)~flags; + } + break; + + case RESTRICT_REMOVE: + /* + * Remove an entry from the table entirely if we + * found one. Don't remove the default entry and + * don't remove an interface entry. + */ + if (rl != 0 + && rl->addr != htonl(INADDR_ANY) + && !(rl->mflags & RESM_INTERFACE)) { + rlprev->next = rl->next; + restrictcount--; + if (rl->flags & RES_LIMITED) { + res_limited_refcnt--; + if (res_limited_refcnt == 0) + mon_stop(MON_RES); + } + memset((char *)rl, 0, + sizeof(struct restrictlist)); + + rl->next = resfree; + resfree = rl; + numresfree++; + } + break; + + default: + break; + } + } else if (resaddr->ss_family == AF_INET6) { + switch (op) { + case RESTRICT_FLAGS: + /* + * Here we add bits to the flags. If this is a + * new restriction add it. + */ + if (rl6 == 0) { + if (numresfree6 == 0) { + rl6 = (struct + restrictlist6 *)emalloc( + INCRESLIST * sizeof(struct + restrictlist6)); + memset((char *)rl6, 0, + INCRESLIST * sizeof(struct + restrictlist6)); + + for (i = 0; i < INCRESLIST; + i++) { + rl6->next = resfree6; + resfree6 = rl6; + rl6++; + } + numresfree6 = INCRESLIST; + } + rl6 = resfree6; + resfree6 = rl6->next; + numresfree6--; + rl6->addr6 = addr6; + rl6->mask6 = mask6; + rl6->mflags = (u_short)mflags; + rl6->next = rlprev6->next; + rlprev6->next = rl6; + restrictcount6++; + } + if ((rl6->flags ^ (u_short)flags) & + RES_LIMITED) { + res_limited_refcnt6++; + mon_start(MON_RES); + } + rl6->flags |= (u_short)flags; + break; + + case RESTRICT_UNFLAG: + /* + * Remove some bits from the flags. If we didn't + * find this one, just return. + */ + if (rl6 != 0) { + if ((rl6->flags ^ (u_short)flags) & + RES_LIMITED) { + res_limited_refcnt6--; + if (res_limited_refcnt6 == 0) + mon_stop(MON_RES); + } + rl6->flags &= (u_short)~flags; + } + break; + + case RESTRICT_REMOVE: + /* + * Remove an entry from the table entirely if we + * found one. Don't remove the default entry and + * don't remove an interface entry. + */ + if (rl6 != 0 && + !IN6_IS_ADDR_UNSPECIFIED(&rl6->addr6) + && !(rl6->mflags & RESM_INTERFACE)) { + rlprev6->next = rl6->next; + restrictcount6--; + if (rl6->flags & RES_LIMITED) { + res_limited_refcnt6--; + if (res_limited_refcnt6 == 0) + mon_stop(MON_RES); + } + memset((char *)rl6, 0, + sizeof(struct restrictlist6)); + rl6->next = resfree6; + resfree6 = rl6; + numresfree6++; + } + break; + + default: + break; + } + } +} diff --git a/ntpd/ntp_timer.c b/ntpd/ntp_timer.c new file mode 100644 index 0000000..6f0f18b --- /dev/null +++ b/ntpd/ntp_timer.c @@ -0,0 +1,377 @@ +/* + * ntp_timer.c - event timer support routines + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntp_machine.h" +#include "ntpd.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <signal.h> +#ifdef HAVE_SYS_SIGNAL_H +# include <sys/signal.h> +#endif +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if defined(HAVE_IO_COMPLETION_PORT) +# include "ntp_iocompletionport.h" +# include "ntp_timer.h" +#endif + +/* + * These routines provide support for the event timer. The timer is + * implemented by an interrupt routine which sets a flag once every + * 2**EVENT_TIMEOUT seconds (currently 4), and a timer routine which + * is called when the mainline code gets around to seeing the flag. + * The timer routine dispatches the clock adjustment code if its time + * has come, then searches the timer queue for expiries which are + * dispatched to the transmit procedure. Finally, we call the hourly + * procedure to do cleanup and print a message. + */ + +/* + * Alarm flag. The mainline code imports this. + */ +volatile int alarm_flag; + +/* + * The counters + */ +static u_long adjust_timer; /* second timer */ +static u_long keys_timer; /* minute timer */ +static u_long hourly_timer; /* hour timer */ +static u_long huffpuff_timer; /* huff-n'-puff timer */ +#ifdef OPENSSL +static u_long revoke_timer; /* keys revoke timer */ +u_char sys_revoke = KEY_REVOKE; /* keys revoke timeout (log2 s) */ +#endif /* OPENSSL */ + +/* + * Statistics counter for the interested. + */ +volatile u_long alarm_overflow; + +#define MINUTE 60 +#define HOUR (60*60) + +u_long current_time; + +/* + * Stats. Number of overflows and number of calls to transmit(). + */ +u_long timer_timereset; +u_long timer_overflows; +u_long timer_xmtcalls; + +#if defined(VMS) +static int vmstimer[2]; /* time for next timer AST */ +static int vmsinc[2]; /* timer increment */ +#endif /* VMS */ + +#if defined SYS_WINNT +static HANDLE WaitableTimerHandle = NULL; +#else +static RETSIGTYPE alarming P((int)); +#endif /* SYS_WINNT */ + +#if !defined(VMS) +# if !defined SYS_WINNT || defined(SYS_CYGWIN32) +# ifndef HAVE_TIMER_SETTIME + struct itimerval itimer; +# else + static timer_t ntpd_timerid; + struct itimerspec itimer; +# endif /* HAVE_TIMER_SETTIME */ +# endif /* SYS_WINNT */ +#endif /* VMS */ + +/* + * reinit_timer - reinitialize interval timer. + */ +void +reinit_timer(void) +{ +#if !defined(SYS_WINNT) && !defined(VMS) +# if defined(HAVE_TIMER_CREATE) && defined(HAVE_TIMER_SETTIME) + timer_gettime(ntpd_timerid, &itimer); + if (itimer.it_value.tv_sec < 0 || itimer.it_value.tv_sec > (1<<EVENT_TIMEOUT)) { + itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + } + if (itimer.it_value.tv_nsec < 0 ) { + itimer.it_value.tv_nsec = 0; + } + if (itimer.it_value.tv_sec == 0 && itimer.it_value.tv_nsec == 0) { + itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_value.tv_nsec = 0; + } + itimer.it_interval.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_interval.tv_nsec = 0; + timer_settime(ntpd_timerid, 0 /*!TIMER_ABSTIME*/, &itimer, NULL); +# else + getitimer(ITIMER_REAL, &itimer); + if (itimer.it_value.tv_sec < 0 || itimer.it_value.tv_sec > (1<<EVENT_TIMEOUT)) { + itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + } + if (itimer.it_value.tv_usec < 0 ) { + itimer.it_value.tv_usec = 0; + } + if (itimer.it_value.tv_sec == 0 && itimer.it_value.tv_usec == 0) { + itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_value.tv_usec = 0; + } + itimer.it_interval.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0); +# endif +# endif /* VMS */ +} + +/* + * init_timer - initialize the timer data structures + */ +void +init_timer(void) +{ +# if defined SYS_WINNT & !defined(SYS_CYGWIN32) + HANDLE hToken; + TOKEN_PRIVILEGES tkp; +# endif /* SYS_WINNT */ + + /* + * Initialize... + */ + alarm_flag = 0; + alarm_overflow = 0; + adjust_timer = 1; + hourly_timer = HOUR; + huffpuff_timer = 0; + current_time = 0; + timer_overflows = 0; + timer_xmtcalls = 0; + timer_timereset = 0; + +#if !defined(SYS_WINNT) + /* + * Set up the alarm interrupt. The first comes 2**EVENT_TIMEOUT + * seconds from now and they continue on every 2**EVENT_TIMEOUT + * seconds. + */ +# if !defined(VMS) +# if defined(HAVE_TIMER_CREATE) && defined(HAVE_TIMER_SETTIME) + if (timer_create (CLOCK_REALTIME, NULL, &ntpd_timerid) == +# ifdef SYS_VXWORKS + ERROR +# else + -1 +# endif + ) + { + fprintf (stderr, "timer create FAILED\n"); + exit (0); + } + (void) signal_no_reset(SIGALRM, alarming); + itimer.it_interval.tv_sec = itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_interval.tv_nsec = itimer.it_value.tv_nsec = 0; + timer_settime(ntpd_timerid, 0 /*!TIMER_ABSTIME*/, &itimer, NULL); +# else + (void) signal_no_reset(SIGALRM, alarming); + itimer.it_interval.tv_sec = itimer.it_value.tv_sec = (1<<EVENT_TIMEOUT); + itimer.it_interval.tv_usec = itimer.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0); +# endif +# else /* VMS */ + vmsinc[0] = 10000000; /* 1 sec */ + vmsinc[1] = 0; + lib$emul(&(1<<EVENT_TIMEOUT), &vmsinc, &0, &vmsinc); + + sys$gettim(&vmstimer); /* that's "now" as abstime */ + + lib$addx(&vmsinc, &vmstimer, &vmstimer); + sys$setimr(0, &vmstimer, alarming, alarming, 0); +# endif /* VMS */ +#else /* SYS_WINNT */ + _tzset(); + + /* + * Get privileges needed for fiddling with the clock + */ + + /* get the current process token handle */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { + msyslog(LOG_ERR, "OpenProcessToken failed: %m"); + exit(1); + } + /* get the LUID for system-time privilege. */ + LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkp.Privileges[0].Luid); + tkp.PrivilegeCount = 1; /* one privilege to set */ + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + /* get set-time privilege for this process. */ + AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0); + /* cannot test return value of AdjustTokenPrivileges. */ + if (GetLastError() != ERROR_SUCCESS) { + msyslog(LOG_ERR, "AdjustTokenPrivileges failed: %m"); + } + + /* + * Set up timer interrupts for every 2**EVENT_TIMEOUT seconds + * Under Windows/NT, + */ + + WaitableTimerHandle = CreateWaitableTimer(NULL, FALSE, NULL); + if (WaitableTimerHandle == NULL) { + msyslog(LOG_ERR, "CreateWaitableTimer failed: %m"); + exit(1); + } + else { + DWORD Period = (1<<EVENT_TIMEOUT) * 1000; + LARGE_INTEGER DueTime; + DueTime.QuadPart = Period * 10000i64; + if (!SetWaitableTimer(WaitableTimerHandle, &DueTime, Period, NULL, NULL, FALSE) != NO_ERROR) { + msyslog(LOG_ERR, "SetWaitableTimer failed: %m"); + exit(1); + } + } + +#endif /* SYS_WINNT */ +} + +#if defined(SYS_WINNT) +extern HANDLE +get_timer_handle(void) +{ + return WaitableTimerHandle; +} +#endif + +/* + * timer - dispatch anyone who needs to be + */ +void +timer(void) +{ + register struct peer *peer, *next_peer; +#ifdef OPENSSL + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ +#endif /* OPENSSL */ + u_int n; + + current_time += (1<<EVENT_TIMEOUT); + + /* + * Adjustment timeout first. + */ + if (adjust_timer <= current_time) { + adjust_timer += 1; + adj_host_clock(); + kod_proto(); + } + + /* + * Now dispatch any peers whose event timer has expired. Be careful + * here, since the peer structure might go away as the result of + * the call. + */ + for (n = 0; n < HASH_SIZE; n++) { + for (peer = peer_hash[n]; peer != 0; peer = next_peer) { + next_peer = peer->next; + if (peer->action && peer->nextaction <= current_time) + peer->action(peer); + if (peer->nextdate <= current_time) { +#ifdef REFCLOCK + if (peer->flags & FLAG_REFCLOCK) + refclock_transmit(peer); + else + transmit(peer); +#else /* REFCLOCK */ + transmit(peer); +#endif /* REFCLOCK */ + } + } + } + + /* + * Garbage collect expired keys. + */ + if (keys_timer <= current_time) { + keys_timer += MINUTE; + auth_agekeys(); + } + + /* + * Huff-n'-puff filter + */ + if (huffpuff_timer <= current_time) { + huffpuff_timer += HUFFPUFF; + huffpuff(); + } + +#ifdef OPENSSL + /* + * Garbage collect old keys and generate new private value + */ + if (revoke_timer <= current_time) { + revoke_timer += RANDPOLL(sys_revoke); + expire_all(); + sprintf(statstr, "refresh ts %u", ntohl(hostval.tstamp)); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("timer: %s\n", statstr); +#endif + } +#endif /* OPENSSL */ + + /* + * Finally, call the hourly routine. + */ + if (hourly_timer <= current_time) { + hourly_timer += HOUR; + hourly_stats(); + } +} + + +#ifndef SYS_WINNT +/* + * alarming - tell the world we've been alarmed + */ +static RETSIGTYPE +alarming( + int sig + ) +{ +#if !defined(VMS) + if (initializing) + return; + if (alarm_flag) + alarm_overflow++; + else + alarm_flag++; +#else /* VMS AST routine */ + if (!initializing) { + if (alarm_flag) alarm_overflow++; + else alarm_flag = 1; /* increment is no good */ + } + lib$addx(&vmsinc,&vmstimer,&vmstimer); + sys$setimr(0,&vmstimer,alarming,alarming,0); +#endif /* VMS */ +} +#endif /* SYS_WINNT */ + + +/* + * timer_clr_stats - clear timer module stat counters + */ +void +timer_clr_stats(void) +{ + timer_overflows = 0; + timer_xmtcalls = 0; + timer_timereset = current_time; +} + diff --git a/ntpd/ntp_util.c b/ntpd/ntp_util.c new file mode 100644 index 0000000..135f9b3 --- /dev/null +++ b/ntpd/ntp_util.c @@ -0,0 +1,797 @@ +/* + * ntp_util.c - stuff I didn't have any other place for + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_filegen.h" +#include "ntp_if.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> +#include <sys/types.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +#ifdef HAVE_IEEEFP_H +# include <ieeefp.h> +#endif +#ifdef HAVE_MATH_H +# include <math.h> +#endif + +#ifdef DOSYNCTODR +#if !defined(VMS) +#include <sys/resource.h> +#endif /* VMS */ +#endif + +#if defined(VMS) +#include <descrip.h> +#endif /* VMS */ + +/* + * This contains odds and ends. Right now the only thing you'll find + * in here is the hourly stats printer and some code to support + * rereading the keys file, but I may eventually put other things in + * here such as code to do something with the leap bits. + */ +/* + * Name of the keys file + */ +static char *key_file_name; + +/* + * The name of the drift_comp file and the temporary. + */ +static char *stats_drift_file; +static char *stats_temp_file; + +/* + * Statistics file stuff + */ +#ifndef NTP_VAR +#ifndef SYS_WINNT +#define NTP_VAR "/var/NTP/" /* NOTE the trailing '/' */ +#else +#define NTP_VAR "c:\\var\\ntp\\" /* NOTE the trailing '\\' */ +#endif /* SYS_WINNT */ +#endif + +#ifndef MAXPATHLEN +#define MAXPATHLEN 256 +#endif + +static char statsdir[MAXPATHLEN] = NTP_VAR; + +static FILEGEN peerstats; +static FILEGEN loopstats; +static FILEGEN clockstats; +static FILEGEN rawstats; +static FILEGEN sysstats; +#ifdef OPENSSL +static FILEGEN cryptostats; +#endif /* OPENSSL */ + +/* + * This controls whether stats are written to the fileset. Provided + * so that ntpdc can turn off stats when the file system fills up. + */ +int stats_control; + +/* + * init_util - initialize the utilities + */ +void +init_util(void) +{ + stats_drift_file = 0; + stats_temp_file = 0; + key_file_name = 0; + +#define PEERNAME "peerstats" +#define LOOPNAME "loopstats" +#define CLOCKNAME "clockstats" +#define RAWNAME "rawstats" +#define STANAME "systats" +#ifdef OPENSSL +#define CRYPTONAME "cryptostats" +#endif /* OPENSSL */ + + peerstats.fp = NULL; + peerstats.prefix = &statsdir[0]; + peerstats.basename = (char*)emalloc(strlen(PEERNAME)+1); + strcpy(peerstats.basename, PEERNAME); + peerstats.id = 0; + peerstats.type = FILEGEN_DAY; + peerstats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("peerstats", &peerstats); + + loopstats.fp = NULL; + loopstats.prefix = &statsdir[0]; + loopstats.basename = (char*)emalloc(strlen(LOOPNAME)+1); + strcpy(loopstats.basename, LOOPNAME); + loopstats.id = 0; + loopstats.type = FILEGEN_DAY; + loopstats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("loopstats", &loopstats); + + clockstats.fp = NULL; + clockstats.prefix = &statsdir[0]; + clockstats.basename = (char*)emalloc(strlen(CLOCKNAME)+1); + strcpy(clockstats.basename, CLOCKNAME); + clockstats.id = 0; + clockstats.type = FILEGEN_DAY; + clockstats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("clockstats", &clockstats); + + rawstats.fp = NULL; + rawstats.prefix = &statsdir[0]; + rawstats.basename = (char*)emalloc(strlen(RAWNAME)+1); + strcpy(rawstats.basename, RAWNAME); + rawstats.id = 0; + rawstats.type = FILEGEN_DAY; + rawstats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("rawstats", &rawstats); + + sysstats.fp = NULL; + sysstats.prefix = &statsdir[0]; + sysstats.basename = (char*)emalloc(strlen(STANAME)+1); + strcpy(sysstats.basename, STANAME); + sysstats.id = 0; + sysstats.type = FILEGEN_DAY; + sysstats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("sysstats", &sysstats); + +#ifdef OPENSSL + cryptostats.fp = NULL; + cryptostats.prefix = &statsdir[0]; + cryptostats.basename = (char*)emalloc(strlen(CRYPTONAME)+1); + strcpy(cryptostats.basename, CRYPTONAME); + cryptostats.id = 0; + cryptostats.type = FILEGEN_DAY; + cryptostats.flag = FGEN_FLAG_LINK; /* not yet enabled !!*/ + filegen_register("cryptostats", &cryptostats); +#endif /* OPENSSL */ + +#undef PEERNAME +#undef LOOPNAME +#undef CLOCKNAME +#undef RAWNAME +#undef STANAME +#ifdef OPENSSL +#undef CRYPTONAME +#endif /* OPENSSL */ +} + + +/* + * hourly_stats - print some interesting stats + */ +void +hourly_stats(void) +{ + FILE *fp; + +#ifdef DOSYNCTODR + struct timeval tv; +#if !defined(VMS) + int prio_set; +#endif +#ifdef HAVE_GETCLOCK + struct timespec ts; +#endif + int o_prio; + + /* + * Sometimes having a Sun can be a drag. + * + * The kernel variable dosynctodr controls whether the system's + * soft clock is kept in sync with the battery clock. If it + * is zero, then the soft clock is not synced, and the battery + * clock is simply left to rot. That means that when the system + * reboots, the battery clock (which has probably gone wacky) + * sets the soft clock. That means ntpd starts off with a very + * confused idea of what time it is. It then takes a large + * amount of time to figure out just how wacky the battery clock + * has made things drift, etc, etc. The solution is to make the + * battery clock sync up to system time. The way to do THAT is + * to simply set the time of day to the current time of day, but + * as quickly as possible. This may, or may not be a sensible + * thing to do. + * + * CAVEAT: settimeofday() steps the sun clock by about 800 us, + * so setting DOSYNCTODR seems a bad idea in the + * case of us resolution + */ + +#if !defined(VMS) + /* (prr) getpriority returns -1 on error, but -1 is also a valid + * return value (!), so instead we have to zero errno before the + * call and check it for non-zero afterwards. + */ + errno = 0; + prio_set = 0; + o_prio = getpriority(PRIO_PROCESS,0); /* Save setting */ + + /* + * (prr) if getpriority succeeded, call setpriority to raise + * scheduling priority as high as possible. If that succeeds + * as well, set the prio_set flag so we remember to reset + * priority to its previous value below. Note that on Solaris + * 2.6 (and beyond?), both getpriority and setpriority will fail + * with ESRCH, because sched_setscheduler (called from main) put + * us in the real-time scheduling class which setpriority + * doesn't know about. Being in the real-time class is better + * than anything setpriority can do, anyhow, so this error is + * silently ignored. + */ + if ((errno == 0) && (setpriority(PRIO_PROCESS,0,-20) == 0)) + prio_set = 1; /* overdrive */ +#endif /* VMS */ +#ifdef HAVE_GETCLOCK + (void) getclock(TIMEOFDAY, &ts); + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec / 1000; +#else /* not HAVE_GETCLOCK */ + GETTIMEOFDAY(&tv,(struct timezone *)NULL); +#endif /* not HAVE_GETCLOCK */ + if (ntp_set_tod(&tv,(struct timezone *)NULL) != 0) { + msyslog(LOG_ERR, "can't sync battery time: %m"); + } +#if !defined(VMS) + if (prio_set) + setpriority(PRIO_PROCESS, 0, o_prio); /* downshift */ +#endif /* VMS */ +#endif /* DOSYNCTODR */ + + NLOG(NLOG_SYSSTATIST) + msyslog(LOG_INFO, + "offset %.6f sec freq %.3f ppm error %.6f poll %d", + last_offset, drift_comp * 1e6, sys_jitter, + sys_poll); + + + record_sys_stats(); + if (stats_drift_file != 0) { + if ((fp = fopen(stats_temp_file, "w")) == NULL) { + msyslog(LOG_ERR, "can't open %s: %m", + stats_temp_file); + return; + } + fprintf(fp, "%.3f\n", drift_comp * 1e6); + (void)fclose(fp); + /* atomic */ +#ifdef SYS_WINNT + (void) _unlink(stats_drift_file); /* rename semantics differ under NT */ +#endif /* SYS_WINNT */ + +#ifndef NO_RENAME + (void) rename(stats_temp_file, stats_drift_file); +#else + /* we have no rename NFS of ftp in use*/ + if ((fp = fopen(stats_drift_file, "w")) == NULL) { + msyslog(LOG_ERR, "can't open %s: %m", + stats_drift_file); + return; + } + +#endif + +#if defined(VMS) + /* PURGE */ + { + $DESCRIPTOR(oldvers,";-1"); + struct dsc$descriptor driftdsc = { + strlen(stats_drift_file),0,0,stats_drift_file }; + + while(lib$delete_file(&oldvers,&driftdsc) & 1) ; + } +#endif + } +} + + +/* + * stats_config - configure the stats operation + */ +void +stats_config( + int item, + char *invalue /* only one type so far */ + ) +{ + FILE *fp; + char *value; + double old_drift; + int len; + + /* + * Expand environment strings under Windows NT, since the + * command interpreter doesn't do this, the program must. + */ +#ifdef SYS_WINNT + char newvalue[MAX_PATH], parameter[MAX_PATH]; + + if (!ExpandEnvironmentStrings(invalue, newvalue, MAX_PATH)) { + switch(item) { + case STATS_FREQ_FILE: + strcpy(parameter,"STATS_FREQ_FILE"); + break; + case STATS_STATSDIR: + strcpy(parameter,"STATS_STATSDIR"); + break; + case STATS_PID_FILE: + strcpy(parameter,"STATS_PID_FILE"); + break; + default: + strcpy(parameter,"UNKNOWN"); + break; + } + value = invalue; + + msyslog(LOG_ERR, + "ExpandEnvironmentStrings(%s) failed: %m\n", parameter); + } else { + value = newvalue; + } +#else + value = invalue; +#endif /* SYS_WINNT */ + + switch(item) { + case STATS_FREQ_FILE: + if (stats_drift_file != 0) { + (void) free(stats_drift_file); + (void) free(stats_temp_file); + stats_drift_file = 0; + stats_temp_file = 0; + } + + if (value == 0 || (len = strlen(value)) == 0) + break; + + stats_drift_file = (char*)emalloc((u_int)(len + 1)); +#if !defined(VMS) + stats_temp_file = (char*)emalloc((u_int)(len + + sizeof(".TEMP"))); +#else + stats_temp_file = (char*)emalloc((u_int)(len + + sizeof("-TEMP"))); +#endif /* VMS */ + memmove(stats_drift_file, value, (unsigned)(len+1)); + memmove(stats_temp_file, value, (unsigned)len); +#if !defined(VMS) + memmove(stats_temp_file + len, ".TEMP", + sizeof(".TEMP")); +#else + memmove(stats_temp_file + len, "-TEMP", + sizeof("-TEMP")); +#endif /* VMS */ + + /* + * Open drift file and read frequency. If the file is + * missing or contains errors, tell the loop to reset. + */ + if ((fp = fopen(stats_drift_file, "r")) == NULL) { + loop_config(LOOP_DRIFTCOMP, 1e9); + break; + } + if (fscanf(fp, "%lf", &old_drift) != 1) { + msyslog(LOG_ERR, "Frequency format error in %s", + stats_drift_file); + loop_config(LOOP_DRIFTCOMP, 1e9); + fclose(fp); + break; + } + fclose(fp); + msyslog(LOG_INFO, + "frequency initialized %.3f PPM from %s", + old_drift, stats_drift_file); + loop_config(LOOP_DRIFTCOMP, old_drift / 1e6); + break; + + case STATS_STATSDIR: + if (strlen(value) >= sizeof(statsdir)) { + msyslog(LOG_ERR, + "value for statsdir too long (>%d, sigh)", + (int)sizeof(statsdir)-1); + } else { + l_fp now; + + get_systime(&now); + strcpy(statsdir,value); + if(peerstats.prefix == &statsdir[0] && + peerstats.fp != NULL) { + fclose(peerstats.fp); + peerstats.fp = NULL; + filegen_setup(&peerstats, now.l_ui); + } + if(loopstats.prefix == &statsdir[0] && + loopstats.fp != NULL) { + fclose(loopstats.fp); + loopstats.fp = NULL; + filegen_setup(&loopstats, now.l_ui); + } + if(clockstats.prefix == &statsdir[0] && + clockstats.fp != NULL) { + fclose(clockstats.fp); + clockstats.fp = NULL; + filegen_setup(&clockstats, now.l_ui); + } + if(rawstats.prefix == &statsdir[0] && + rawstats.fp != NULL) { + fclose(rawstats.fp); + rawstats.fp = NULL; + filegen_setup(&rawstats, now.l_ui); + } + if(sysstats.prefix == &statsdir[0] && + sysstats.fp != NULL) { + fclose(sysstats.fp); + sysstats.fp = NULL; + filegen_setup(&sysstats, now.l_ui); + } +#ifdef OPENSSL + if(cryptostats.prefix == &statsdir[0] && + cryptostats.fp != NULL) { + fclose(cryptostats.fp); + cryptostats.fp = NULL; + filegen_setup(&cryptostats, now.l_ui); + } +#endif /* OPENSSL */ + } + break; + + case STATS_PID_FILE: + if ((fp = fopen(value, "w")) == NULL) { + msyslog(LOG_ERR, "Can't open %s: %m", value); + break; + } + fprintf(fp, "%d", (int) getpid()); + fclose(fp);; + break; + + default: + /* oh well */ + break; + } +} + +/* + * record_peer_stats - write peer statistics to file + * + * file format: + * day (mjd) + * time (s past UTC midnight) + * peer (ip address) + * peer status word (hex) + * peer offset (s) + * peer delay (s) + * peer error bound (s) + * peer error (s) +*/ +void +record_peer_stats( + struct sockaddr_storage *addr, + int status, + double offset, + double delay, + double dispersion, + double skew + ) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&peerstats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (peerstats.fp != NULL) { + fprintf(peerstats.fp, + "%lu %s %s %x %.9f %.9f %.9f %.9f\n", + day, ulfptoa(&now, 3), stoa(addr), status, offset, + delay, dispersion, skew); + fflush(peerstats.fp); + } +} +/* + * record_loop_stats - write loop filter statistics to file + * + * file format: + * day (mjd) + * time (s past midnight) + * offset (s) + * frequency (approx ppm) + * time constant (log base 2) + */ +void +record_loop_stats( + double offset, + double freq, + double jitter, + double stability, + int spoll + ) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&loopstats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (loopstats.fp != NULL) { + fprintf(loopstats.fp, "%lu %s %.9f %.6f %.9f %.6f %d\n", + day, ulfptoa(&now, 3), offset, freq * 1e6, jitter, + stability * 1e6, spoll); + fflush(loopstats.fp); + } +} + +/* + * record_clock_stats - write clock statistics to file + * + * file format: + * day (mjd) + * time (s past midnight) + * peer (ip address) + * text message + */ +void +record_clock_stats( + struct sockaddr_storage *addr, + const char *text + ) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&clockstats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (clockstats.fp != NULL) { + fprintf(clockstats.fp, "%lu %s %s %s\n", + day, ulfptoa(&now, 3), stoa(addr), text); + fflush(clockstats.fp); + } +} + +/* + * record_raw_stats - write raw timestamps to file + * + * + * file format + * time (s past midnight) + * peer ip address + * local ip address + * t1 t2 t3 t4 timestamps + */ +void +record_raw_stats( + struct sockaddr_storage *srcadr, + struct sockaddr_storage *dstadr, + l_fp *t1, + l_fp *t2, + l_fp *t3, + l_fp *t4 + ) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&rawstats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (rawstats.fp != NULL) { + fprintf(rawstats.fp, "%lu %s %s %s %s %s %s %s\n", + day, ulfptoa(&now, 3), stoa(srcadr), stoa(dstadr), + ulfptoa(t1, 9), ulfptoa(t2, 9), ulfptoa(t3, 9), + ulfptoa(t4, 9)); + fflush(rawstats.fp); + } +} + + +/* + * record_sys_stats - write system statistics to file + * + * file format + * time (s past midnight) + * time since startup (hr) + * packets recieved + * packets processed + * current version + * previous version + * bad version + * access denied + * bad length or format + * bad authentication + * rate exceeded + */ +void +record_sys_stats(void) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&sysstats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (sysstats.fp != NULL) { + fprintf(sysstats.fp, + "%lu %s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu\n", + day, ulfptoa(&now, 3), sys_stattime / 3600, + sys_received, sys_processed, sys_newversionpkt, + sys_oldversionpkt, sys_unknownversion, + sys_restricted, sys_badlength, sys_badauth, + sys_limitrejected); + fflush(sysstats.fp); + proto_clr_stats(); + } +} + + +#ifdef OPENSSL +/* + * record_crypto_stats - write crypto statistics to file + * + * file format: + * day (mjd) + * time (s past midnight) + * peer (ip address) + * text message + */ +void +record_crypto_stats( + struct sockaddr_storage *addr, + const char *text + ) +{ + l_fp now; + u_long day; + + if (!stats_control) + return; + + get_systime(&now); + filegen_setup(&cryptostats, now.l_ui); + day = now.l_ui / 86400 + MJD_1900; + now.l_ui %= 86400; + if (cryptostats.fp != NULL) { + if (addr == NULL) + fprintf(cryptostats.fp, "%lu %s %s\n", + day, ulfptoa(&now, 3), text); + else + fprintf(cryptostats.fp, "%lu %s %s %s\n", + day, ulfptoa(&now, 3), stoa(addr), text); + fflush(cryptostats.fp); + } +} +#endif /* OPENSSL */ + + +/* + * getauthkeys - read the authentication keys from the specified file + */ +void +getauthkeys( + char *keyfile + ) +{ + int len; + + len = strlen(keyfile); + if (len == 0) + return; + + if (key_file_name != 0) { + if (len > (int)strlen(key_file_name)) { + (void) free(key_file_name); + key_file_name = 0; + } + } + + if (key_file_name == 0) { +#ifndef SYS_WINNT + key_file_name = (char*)emalloc((u_int) (len + 1)); +#else + key_file_name = (char*)emalloc((u_int) (MAXPATHLEN)); +#endif + } +#ifndef SYS_WINNT + memmove(key_file_name, keyfile, (unsigned)(len+1)); +#else + if (!ExpandEnvironmentStrings(keyfile, key_file_name, MAXPATHLEN)) + { + msyslog(LOG_ERR, + "ExpandEnvironmentStrings(KEY_FILE) failed: %m\n"); + } +#endif /* SYS_WINNT */ + + authreadkeys(key_file_name); +} + + +/* + * rereadkeys - read the authentication key file over again. + */ +void +rereadkeys(void) +{ + if (key_file_name != 0) + authreadkeys(key_file_name); +} + +/* + * sock_hash - hash an sockaddr_storage structure + */ +int +sock_hash( + struct sockaddr_storage *addr + ) +{ + int hashVal; + int i; + int len; + char *ch; + + hashVal = 0; + len = 0; + /* + * We can't just hash the whole thing because there are hidden + * fields in sockaddr_in6 that might be filled in by recvfrom(), + * so just use the family, port and address. + */ + ch = (char *)&addr->ss_family; + hashVal = 37 * hashVal + (int)*ch; + if (sizeof(addr->ss_family) > 1) { + ch++; + hashVal = 37 * hashVal + (int)*ch; + } + switch(addr->ss_family) { + case AF_INET: + ch = (char *)&((struct sockaddr_in *)addr)->sin_addr; + len = sizeof(struct in_addr); + break; + case AF_INET6: + ch = (char *)&((struct sockaddr_in6 *)addr)->sin6_addr; + len = sizeof(struct in6_addr); + break; + } + + for (i = 0; i < len ; i++) + hashVal = 37 * hashVal + (int)*(ch + i); + + hashVal = hashVal % 128; /* % MON_HASH_SIZE hardcoded */ + + if (hashVal < 0) + hashVal += 128; + + return hashVal; +} diff --git a/ntpd/ntpd.c b/ntpd/ntpd.c new file mode 100644 index 0000000..0b05253 --- /dev/null +++ b/ntpd/ntpd.c @@ -0,0 +1,1274 @@ +/* + * ntpd.c - main program for the fixed point NTP daemon + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include "ntp_machine.h" +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_stdlib.h" + +#ifdef SIM +#include "ntpsim.h" +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#include <stdio.h> +#ifndef SYS_WINNT +# if !defined(VMS) /*wjm*/ +# ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +# endif +# endif /* VMS */ +# ifdef HAVE_SYS_SIGNAL_H +# include <sys/signal.h> +# else +# include <signal.h> +# endif +# ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +# endif /* HAVE_SYS_IOCTL_H */ +# ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +# endif /* HAVE_SYS_RESOURCE_H */ +#else +# include <signal.h> +# include <process.h> +# include <io.h> +# include "../libntp/log.h" +# include <clockstuff.h> +# include <crtdbg.h> +#endif /* SYS_WINNT */ +#if defined(HAVE_RTPRIO) +# ifdef HAVE_SYS_RESOURCE_H +# include <sys/resource.h> +# endif +# ifdef HAVE_SYS_LOCK_H +# include <sys/lock.h> +# endif +# include <sys/rtprio.h> +#else +# ifdef HAVE_PLOCK +# ifdef HAVE_SYS_LOCK_H +# include <sys/lock.h> +# endif +# endif +#endif +#if defined(HAVE_SCHED_SETSCHEDULER) +# ifdef HAVE_SCHED_H +# include <sched.h> +# else +# ifdef HAVE_SYS_SCHED_H +# include <sys/sched.h> +# endif +# endif +#endif +#if defined(HAVE_SYS_MMAN_H) +# include <sys/mman.h> +#endif + +#ifdef HAVE_TERMIOS_H +# include <termios.h> +#endif + +#ifdef SYS_DOMAINOS +# include <apollo/base.h> +#endif /* SYS_DOMAINOS */ + +#include "recvbuff.h" +#include "ntp_cmdargs.h" + +#if 0 /* HMS: I don't think we need this. 961223 */ +#ifdef LOCK_PROCESS +# ifdef SYS_SOLARIS +# include <sys/mman.h> +# else +# include <sys/lock.h> +# endif +#endif +#endif + +#ifdef _AIX +# include <ulimit.h> +#endif /* _AIX */ + +#ifdef SCO5_CLOCK +# include <sys/ci/ciioctl.h> +#endif + +#ifdef HAVE_CLOCKCTL +# include <ctype.h> +# include <grp.h> +# include <pwd.h> +#endif + +/* + * Signals we catch for debugging. If not debugging we ignore them. + */ +#define MOREDEBUGSIG SIGUSR1 +#define LESSDEBUGSIG SIGUSR2 + +/* + * Signals which terminate us gracefully. + */ +#ifndef SYS_WINNT +# define SIGDIE1 SIGHUP +# define SIGDIE3 SIGQUIT +# define SIGDIE2 SIGINT +# define SIGDIE4 SIGTERM +#endif /* SYS_WINNT */ + +#if defined SYS_WINNT +/* handles for various threads, process, and objects */ +HANDLE ResolverThreadHandle = NULL; +/* variables used to inform the Service Control Manager of our current state */ +BOOL NoWinService = FALSE; +SERVICE_STATUS ssStatus; +SERVICE_STATUS_HANDLE sshStatusHandle; +HANDLE WaitHandles[3] = { NULL, NULL, NULL }; +char szMsgPath[255]; +static BOOL WINAPI OnConsoleEvent(DWORD dwCtrlType); +BOOL init_randfile(); +#endif /* SYS_WINNT */ + +/* + * Scheduling priority we run at + */ +#define NTPD_PRIO (-12) + +int priority_done = 2; /* 0 - Set priority */ + /* 1 - priority is OK where it is */ + /* 2 - Don't set priority */ + /* 1 and 2 are pretty much the same */ + +/* + * Debugging flag + */ +volatile int debug; + +/* + * Set the processing not to be in the forground + */ +int forground_process = FALSE; + +/* + * No-fork flag. If set, we do not become a background daemon. + */ +int nofork; + +#ifdef HAVE_CLOCKCTL +char *user = NULL; /* User to switch to */ +char *group = NULL; /* group to switch to */ +char *chrootdir = NULL; /* directory to chroot to */ +int sw_uid; +int sw_gid; +char *endp; +struct group *gr; +struct passwd *pw; +#endif /* HAVE_CLOCKCTL */ + +/* + * Initializing flag. All async routines watch this and only do their + * thing when it is clear. + */ +int initializing; + +/* + * Version declaration + */ +extern const char *Version; + +int was_alarmed; + +#ifdef DECL_SYSCALL +/* + * We put this here, since the argument profile is syscall-specific + */ +extern int syscall P((int, ...)); +#endif /* DECL_SYSCALL */ + + +#ifdef SIGDIE2 +static RETSIGTYPE finish P((int)); +#endif /* SIGDIE2 */ + +#ifdef DEBUG +#ifndef SYS_WINNT +static RETSIGTYPE moredebug P((int)); +static RETSIGTYPE lessdebug P((int)); +#endif +#else /* not DEBUG */ +static RETSIGTYPE no_debug P((int)); +#endif /* not DEBUG */ + +int ntpdmain P((int, char **)); +static void set_process_priority P((void)); + +#ifdef SIM +int +main( + int argc, + char *argv[] + ) +{ + return ntpsim(argc, argv); +} +#else /* SIM */ +#ifdef NO_MAIN_ALLOWED +CALL(ntpd,"ntpd",ntpdmain); +#else +int +main( + int argc, + char *argv[] + ) +{ + return ntpdmain(argc, argv); +} +#endif +#endif /* SIM */ + +#ifdef _AIX +/* + * OK. AIX is different than solaris in how it implements plock(). + * If you do NOT adjust the stack limit, you will get the MAXIMUM + * stack size allocated and PINNED with you program. To check the + * value, use ulimit -a. + * + * To fix this, we create an automatic variable and set our stack limit + * to that PLUS 32KB of extra space (we need some headroom). + * + * This subroutine gets the stack address. + * + * Grover Davidson and Matt Ladendorf + * + */ +static char * +get_aix_stack(void) +{ + char ch; + return (&ch); +} + +/* + * Signal handler for SIGDANGER. + */ +static void +catch_danger(int signo) +{ + msyslog(LOG_INFO, "ntpd: setpgid(): %m"); + /* Make the system believe we'll free something, but don't do it! */ + return; +} +#endif /* _AIX */ + +/* + * Set the process priority + */ +static void +set_process_priority(void) +{ + +#ifdef DEBUG + if (debug > 1) + msyslog(LOG_DEBUG, "set_process_priority: %s: priority_done is <%d>", + ((priority_done) + ? "Leave priority alone" + : "Attempt to set priority" + ), + priority_done); +#endif /* DEBUG */ + +#ifdef SYS_WINNT + priority_done += NT_set_process_priority(); +#endif + +#if defined(HAVE_SCHED_SETSCHEDULER) + if (!priority_done) { + extern int config_priority_override, config_priority; + int pmax, pmin; + struct sched_param sched; + + pmax = sched_get_priority_max(SCHED_FIFO); + sched.sched_priority = pmax; + if ( config_priority_override ) { + pmin = sched_get_priority_min(SCHED_FIFO); + if ( config_priority > pmax ) + sched.sched_priority = pmax; + else if ( config_priority < pmin ) + sched.sched_priority = pmin; + else + sched.sched_priority = config_priority; + } + if ( sched_setscheduler(0, SCHED_FIFO, &sched) == -1 ) + msyslog(LOG_ERR, "sched_setscheduler(): %m"); + else + ++priority_done; + } +#endif /* HAVE_SCHED_SETSCHEDULER */ +#if defined(HAVE_RTPRIO) +# ifdef RTP_SET + if (!priority_done) { + struct rtprio srtp; + + srtp.type = RTP_PRIO_REALTIME; /* was: RTP_PRIO_NORMAL */ + srtp.prio = 0; /* 0 (hi) -> RTP_PRIO_MAX (31,lo) */ + + if (rtprio(RTP_SET, getpid(), &srtp) < 0) + msyslog(LOG_ERR, "rtprio() error: %m"); + else + ++priority_done; + } +# else /* not RTP_SET */ + if (!priority_done) { + if (rtprio(0, 120) < 0) + msyslog(LOG_ERR, "rtprio() error: %m"); + else + ++priority_done; + } +# endif /* not RTP_SET */ +#endif /* HAVE_RTPRIO */ +#if defined(NTPD_PRIO) && NTPD_PRIO != 0 +# ifdef HAVE_ATT_NICE + if (!priority_done) { + errno = 0; + if (-1 == nice (NTPD_PRIO) && errno != 0) + msyslog(LOG_ERR, "nice() error: %m"); + else + ++priority_done; + } +# endif /* HAVE_ATT_NICE */ +# ifdef HAVE_BSD_NICE + if (!priority_done) { + if (-1 == setpriority(PRIO_PROCESS, 0, NTPD_PRIO)) + msyslog(LOG_ERR, "setpriority() error: %m"); + else + ++priority_done; + } +# endif /* HAVE_BSD_NICE */ +#endif /* NTPD_PRIO && NTPD_PRIO != 0 */ + if (!priority_done) + msyslog(LOG_ERR, "set_process_priority: No way found to improve our priority"); +} + + +/* + * Main program. Initialize us, disconnect us from the tty if necessary, + * and loop waiting for I/O and/or timer expiries. + */ +int +ntpdmain( + int argc, + char *argv[] + ) +{ + l_fp now; + char *cp; + struct recvbuf *rbuflist; + struct recvbuf *rbuf; +#ifdef _AIX /* HMS: ifdef SIGDANGER? */ + struct sigaction sa; +#endif + + initializing = 1; /* mark that we are initializing */ + debug = 0; /* no debugging by default */ + nofork = 0; /* will fork by default */ + +#ifdef HAVE_UMASK + { + mode_t uv; + + uv = umask(0); + if(uv) + (void) umask(uv); + else + (void) umask(022); + } +#endif + +#if defined(HAVE_GETUID) && !defined(MPE) /* MPE lacks the concept of root */ + { + uid_t uid; + + uid = getuid(); + if (uid) + { + msyslog(LOG_ERR, "ntpd: must be run as root, not uid %ld", (long)uid); + exit(1); + } + } +#endif + +#ifdef SYS_WINNT + /* Set the Event-ID message-file name. */ + if (!GetModuleFileName(NULL, szMsgPath, sizeof(szMsgPath))) { + msyslog(LOG_ERR, "GetModuleFileName(PGM_EXE_FILE) failed: %m\n"); + exit(1); + } + addSourceToRegistry("NTP", szMsgPath); +#endif + getstartup(argc, argv); /* startup configuration, may set debug */ + + if (debug) + printf("%s\n", Version); + + /* + * Initialize random generator and public key pair + */ +#ifdef SYS_WINNT + /* Initialize random file before OpenSSL checks */ + if(!init_randfile()) + msyslog(LOG_ERR, "Unable to initialize .rnd file\n"); +#endif + get_systime(&now); + SRANDOM((int)(now.l_i * now.l_uf)); + +#if !defined(VMS) +# ifndef NODETACH + /* + * Detach us from the terminal. May need an #ifndef GIZMO. + */ +# ifdef DEBUG + if (!debug && !nofork) +# else /* DEBUG */ + if (!nofork) +# endif /* DEBUG */ + { +# ifndef SYS_WINNT +# ifdef HAVE_DAEMON + daemon(0, 0); +# else /* not HAVE_DAEMON */ + if (fork()) /* HMS: What about a -1? */ + exit(0); + + { +#if !defined(F_CLOSEM) + u_long s; + int max_fd; +#endif /* not F_CLOSEM */ + +#if defined(F_CLOSEM) + /* + * From 'Writing Reliable AIX Daemons,' SG24-4946-00, + * by Eric Agar (saves us from doing 32767 system + * calls) + */ + if (fcntl(0, F_CLOSEM, 0) == -1) + msyslog(LOG_ERR, "ntpd: failed to close open files(): %m"); +#else /* not F_CLOSEM */ + +# if defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX) + max_fd = sysconf(_SC_OPEN_MAX); +# else /* HAVE_SYSCONF && _SC_OPEN_MAX */ + max_fd = getdtablesize(); +# endif /* HAVE_SYSCONF && _SC_OPEN_MAX */ + for (s = 0; s < max_fd; s++) + (void) close((int)s); +#endif /* not F_CLOSEM */ + (void) open("/", 0); + (void) dup2(0, 1); + (void) dup2(0, 2); +#ifdef SYS_DOMAINOS + { + uid_$t puid; + status_$t st; + + proc2_$who_am_i(&puid); + proc2_$make_server(&puid, &st); + } +#endif /* SYS_DOMAINOS */ +#if defined(HAVE_SETPGID) || defined(HAVE_SETSID) +# ifdef HAVE_SETSID + if (setsid() == (pid_t)-1) + msyslog(LOG_ERR, "ntpd: setsid(): %m"); +# else + if (setpgid(0, 0) == -1) + msyslog(LOG_ERR, "ntpd: setpgid(): %m"); +# endif +#else /* HAVE_SETPGID || HAVE_SETSID */ + { +# if defined(TIOCNOTTY) + int fid; + + fid = open("/dev/tty", 2); + if (fid >= 0) + { + (void) ioctl(fid, (u_long) TIOCNOTTY, (char *) 0); + (void) close(fid); + } +# endif /* defined(TIOCNOTTY) */ +# ifdef HAVE_SETPGRP_0 + (void) setpgrp(); +# else /* HAVE_SETPGRP_0 */ + (void) setpgrp(0, getpid()); +# endif /* HAVE_SETPGRP_0 */ + } +#endif /* HAVE_SETPGID || HAVE_SETSID */ +#ifdef _AIX + /* Don't get killed by low-on-memory signal. */ + sa.sa_handler = catch_danger; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + (void) sigaction(SIGDANGER, &sa, NULL); +#endif /* _AIX */ + } +# endif /* not HAVE_DAEMON */ +# else /* SYS_WINNT */ + + { + if (NoWinService == FALSE) { + SERVICE_TABLE_ENTRY dispatchTable[] = { + { TEXT("NetworkTimeProtocol"), (LPSERVICE_MAIN_FUNCTION)service_main }, + { NULL, NULL } + }; + + /* daemonize */ + if (!StartServiceCtrlDispatcher(dispatchTable)) + { + msyslog(LOG_ERR, "StartServiceCtrlDispatcher: %m"); + ExitProcess(2); + } + } + else { + service_main(argc, argv); + return 0; + } + } +# endif /* SYS_WINNT */ + } +# endif /* NODETACH */ +# if defined(SYS_WINNT) && !defined(NODETACH) + else + service_main(argc, argv); + return 0; /* must return a value */ +} /* end main */ + +/* + * If this runs as a service under NT, the main thread will block at + * StartServiceCtrlDispatcher() and another thread will be started by the + * Service Control Dispatcher which will begin execution at the routine + * specified in that call (viz. service_main) + */ +void +service_main( + DWORD argc, + LPTSTR *argv + ) +{ + char *cp; + struct recvbuf *rbuflist; + struct recvbuf *rbuf; + + if(!debug && NoWinService == FALSE) + { + /* register our service control handler */ + sshStatusHandle = RegisterServiceCtrlHandler( TEXT("NetworkTimeProtocol"), + (LPHANDLER_FUNCTION)service_ctrl); + if(sshStatusHandle == 0) + { + msyslog(LOG_ERR, "RegisterServiceCtrlHandler failed: %m"); + return; + } + + /* report pending status to Service Control Manager */ + ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + ssStatus.dwCurrentState = SERVICE_START_PENDING; + ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + ssStatus.dwWin32ExitCode = NO_ERROR; + ssStatus.dwServiceSpecificExitCode = 0; + ssStatus.dwCheckPoint = 1; + ssStatus.dwWaitHint = 5000; + if (!SetServiceStatus(sshStatusHandle, &ssStatus)) + { + msyslog(LOG_ERR, "SetServiceStatus: %m"); + ssStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(sshStatusHandle, &ssStatus); + return; + } + + } /* debug */ +# endif /* defined(SYS_WINNT) && !defined(NODETACH) */ +#endif /* VMS */ + + /* + * Logging. This may actually work on the gizmo board. Find a name + * to log with by using the basename of argv[0] + */ + cp = strrchr(argv[0], '/'); + if (cp == 0) + cp = argv[0]; + else + cp++; + + debug = 0; /* will be immediately re-initialized 8-( */ + getstartup(argc, argv); /* startup configuration, catch logfile this time */ + +#if !defined(VMS) + +# ifndef LOG_DAEMON + openlog(cp, LOG_PID); +# else /* LOG_DAEMON */ + +# ifndef LOG_NTP +# define LOG_NTP LOG_DAEMON +# endif + openlog(cp, LOG_PID | LOG_NDELAY, LOG_NTP); +# ifdef DEBUG + if (debug) + setlogmask(LOG_UPTO(LOG_DEBUG)); + else +# endif /* DEBUG */ + setlogmask(LOG_UPTO(LOG_DEBUG)); /* @@@ was INFO */ +# endif /* LOG_DAEMON */ +#endif /* !SYS_WINNT && !VMS */ + + NLOG(NLOG_SYSINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "%s", Version); + +#ifdef SYS_WINNT + /* GMS 1/18/1997 + * TODO: lock the process in memory using SetProcessWorkingSetSize() and VirtualLock() functions + * + process_handle = GetCurrentProcess(); + if (SetProcessWorkingSetSize(process_handle, 2097152 , 4194304 ) == TRUE) { + if (VirtualLock(0 , 4194304) == FALSE) + msyslog(LOG_ERR, "VirtualLock() failed: %m"); + } else { + msyslog(LOG_ERR, "SetProcessWorkingSetSize() failed: %m"); + } + */ +#endif /* SYS_WINNT */ + +#ifdef SCO5_CLOCK + /* + * SCO OpenServer's system clock offers much more precise timekeeping + * on the base CPU than the other CPUs (for multiprocessor systems), + * so we must lock to the base CPU. + */ + { + int fd = open("/dev/at1", O_RDONLY); + if (fd >= 0) { + int zero = 0; + if (ioctl(fd, ACPU_LOCK, &zero) < 0) + msyslog(LOG_ERR, "cannot lock to base CPU: %m\n"); + close( fd ); + } /* else ... + * If we can't open the device, this probably just isn't + * a multiprocessor system, so we're A-OK. + */ + } +#endif + +#if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) && defined(MCL_FUTURE) +# ifdef HAVE_SETRLIMIT + /* + * Set the stack limit to something smaller, so that we don't lock a lot + * of unused stack memory. + */ + { + struct rlimit rl; + + if (getrlimit(RLIMIT_STACK, &rl) != -1 + && (rl.rlim_cur = 20 * 4096) < rl.rlim_max) + { + if (setrlimit(RLIMIT_STACK, &rl) == -1) + { + msyslog(LOG_ERR, + "Cannot adjust stack limit for mlockall: %m"); + } + } + } +# endif /* HAVE_SETRLIMIT */ + /* + * lock the process into memory + */ + if (mlockall(MCL_CURRENT|MCL_FUTURE) < 0) + msyslog(LOG_ERR, "mlockall(): %m"); +#else /* not (HAVE_MLOCKALL && MCL_CURRENT && MCL_FUTURE) */ +# ifdef HAVE_PLOCK +# ifdef PROCLOCK +# ifdef _AIX + /* + * set the stack limit for AIX for plock(). + * see get_aix_stack for more info. + */ + if (ulimit(SET_STACKLIM, (get_aix_stack() - 8*4096)) < 0) + { + msyslog(LOG_ERR,"Cannot adjust stack limit for plock on AIX: %m"); + } +# endif /* _AIX */ + /* + * lock the process into memory + */ + if (plock(PROCLOCK) < 0) + msyslog(LOG_ERR, "plock(PROCLOCK): %m"); +# else /* not PROCLOCK */ +# ifdef TXTLOCK + /* + * Lock text into ram + */ + if (plock(TXTLOCK) < 0) + msyslog(LOG_ERR, "plock(TXTLOCK) error: %m"); +# else /* not TXTLOCK */ + msyslog(LOG_ERR, "plock() - don't know what to lock!"); +# endif /* not TXTLOCK */ +# endif /* not PROCLOCK */ +# endif /* HAVE_PLOCK */ +#endif /* not (HAVE_MLOCKALL && MCL_CURRENT && MCL_FUTURE) */ + + /* + * Set up signals we pay attention to locally. + */ +#ifdef SIGDIE1 + (void) signal_no_reset(SIGDIE1, finish); +#endif /* SIGDIE1 */ +#ifdef SIGDIE2 + (void) signal_no_reset(SIGDIE2, finish); +#endif /* SIGDIE2 */ +#ifdef SIGDIE3 + (void) signal_no_reset(SIGDIE3, finish); +#endif /* SIGDIE3 */ +#ifdef SIGDIE4 + (void) signal_no_reset(SIGDIE4, finish); +#endif /* SIGDIE4 */ + +#ifdef SIGBUS + (void) signal_no_reset(SIGBUS, finish); +#endif /* SIGBUS */ + +#if !defined(SYS_WINNT) && !defined(VMS) +# ifdef DEBUG + (void) signal_no_reset(MOREDEBUGSIG, moredebug); + (void) signal_no_reset(LESSDEBUGSIG, lessdebug); +# else + (void) signal_no_reset(MOREDEBUGSIG, no_debug); + (void) signal_no_reset(LESSDEBUGSIG, no_debug); +# endif /* DEBUG */ +#endif /* !SYS_WINNT && !VMS */ + + /* + * Set up signals we should never pay attention to. + */ +#if defined SIGPIPE + (void) signal_no_reset(SIGPIPE, SIG_IGN); +#endif /* SIGPIPE */ + +#if defined SYS_WINNT + if (!SetConsoleCtrlHandler(OnConsoleEvent, TRUE)) { + msyslog(LOG_ERR, "Can't set console control handler: %m"); + } +#endif + + /* + * Call the init_ routines to initialize the data structures. + */ +#if defined (HAVE_IO_COMPLETION_PORT) + init_io_completion_port(); + init_winnt_time(); +#endif + init_auth(); + init_util(); + init_restrict(); + init_mon(); + init_timer(); + init_lib(); + init_random(); + init_request(); + init_control(); + init_peer(); +#ifdef REFCLOCK + init_refclock(); +#endif + set_process_priority(); + init_proto(); /* Call at high priority */ + init_io(); + init_loopfilter(); + mon_start(MON_ON); /* monitor on by default now */ + /* turn off in config if unwanted */ + + /* + * Get configuration. This (including argument list parsing) is + * done in a separate module since this will definitely be different + * for the gizmo board. While at it, save the host name for later + * along with the length. The crypto needs this. + */ +#ifdef DEBUG + debug = 0; +#endif + getconfig(argc, argv); +#ifdef OPENSSL + crypto_setup(); +#endif /* OPENSSL */ + initializing = 0; + +#if defined(SYS_WINNT) && !defined(NODETACH) +# if defined(DEBUG) + if(!debug) + { +# endif + if (NoWinService == FALSE) { + /* report to the service control manager that the service is running */ + ssStatus.dwCurrentState = SERVICE_RUNNING; + ssStatus.dwWin32ExitCode = NO_ERROR; + if (!SetServiceStatus(sshStatusHandle, &ssStatus)) + { + msyslog(LOG_ERR, "SetServiceStatus: %m"); + if (ResolverThreadHandle != NULL) + CloseHandle(ResolverThreadHandle); + ssStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(sshStatusHandle, &ssStatus); + return; + } + } +# if defined(DEBUG) + } +# endif +#endif + +#ifdef HAVE_CLOCKCTL + /* + * Drop super-user privileges and chroot now if the OS supports + * non root clock control (only NetBSD for now). + */ + if (user != NULL) { + if (isdigit((unsigned char)*user)) { + sw_uid = (uid_t)strtoul(user, &endp, 0); + if (*endp != '\0') + goto getuser; + } else { +getuser: + if ((pw = getpwnam(user)) != NULL) { + sw_uid = pw->pw_uid; + } else { + errno = 0; + msyslog(LOG_ERR, "Cannot find user `%s'", user); + exit (-1); + } + } + } + if (group != NULL) { + if (isdigit((unsigned char)*group)) { + sw_gid = (gid_t)strtoul(group, &endp, 0); + if (*endp != '\0') + goto getgroup; + } else { +getgroup: + if ((gr = getgrnam(group)) != NULL) { + sw_gid = pw->pw_gid; + } else { + errno = 0; + msyslog(LOG_ERR, "Cannot find group `%s'", group); + exit (-1); + } + } + } + if (chrootdir && chroot(chrootdir)) { + msyslog(LOG_ERR, "Cannot chroot to `%s': %m", chrootdir); + exit (-1); + } + if (group && setgid(sw_gid)) { + msyslog(LOG_ERR, "Cannot setgid() to group `%s': %m", group); + exit (-1); + } + if (group && setegid(sw_gid)) { + msyslog(LOG_ERR, "Cannot setegid() to group `%s': %m", group); + exit (-1); + } + if (user && setuid(sw_uid)) { + msyslog(LOG_ERR, "Cannot setuid() to user `%s': %m", user); + exit (-1); + } + if (user && seteuid(sw_uid)) { + msyslog(LOG_ERR, "Cannot seteuid() to user `%s': %m", user); + exit (-1); + } +#endif + /* + * Report that we're up to any trappers + */ + report_event(EVNT_SYSRESTART, (struct peer *)0); + + /* + * Use select() on all on all input fd's for unlimited + * time. select() will terminate on SIGALARM or on the + * reception of input. Using select() means we can't do + * robust signal handling and we get a potential race + * between checking for alarms and doing the select(). + * Mostly harmless, I think. + */ + /* On VMS, I suspect that select() can't be interrupted + * by a "signal" either, so I take the easy way out and + * have select() time out after one second. + * System clock updates really aren't time-critical, + * and - lacking a hardware reference clock - I have + * yet to learn about anything else that is. + */ +#if defined(HAVE_IO_COMPLETION_PORT) + WaitHandles[0] = CreateEvent(NULL, FALSE, FALSE, NULL); /* exit reques */ + WaitHandles[1] = get_timer_handle(); + WaitHandles[2] = get_io_event(); + + for (;;) { + DWORD Index = WaitForMultipleObjectsEx(sizeof(WaitHandles)/sizeof(WaitHandles[0]), WaitHandles, FALSE, 1000, TRUE); + switch (Index) { + case WAIT_OBJECT_0 + 0 : /* exit request */ + exit(0); + break; + + case WAIT_OBJECT_0 + 1 : /* timer */ + timer(); + break; + + case WAIT_OBJECT_0 + 2 : /* Io event */ +# ifdef DEBUG + if ( debug > 3 ) + { + printf( "IoEvent occurred\n" ); + } +# endif + break; + + case WAIT_IO_COMPLETION : /* loop */ + case WAIT_TIMEOUT : + break; + case WAIT_FAILED: + msyslog(LOG_ERR, "ntpdc: WaitForMultipleObjectsEx Failed: Error: %m"); + break; + + /* For now do nothing if not expected */ + default: + break; + + } /* switch */ + rbuflist = getrecvbufs(); /* get received buffers */ + +#else /* normal I/O */ + + was_alarmed = 0; + rbuflist = (struct recvbuf *)0; + for (;;) + { +# if !defined(HAVE_SIGNALED_IO) + extern fd_set activefds; + extern int maxactivefd; + + fd_set rdfdes; + int nfound; +# elif defined(HAVE_SIGNALED_IO) + block_io_and_alarm(); +# endif + + rbuflist = getrecvbufs(); /* get received buffers */ + if (alarm_flag) /* alarmed? */ + { + was_alarmed = 1; + alarm_flag = 0; + } + + if (!was_alarmed && rbuflist == (struct recvbuf *)0) + { + /* + * Nothing to do. Wait for something. + */ +# ifndef HAVE_SIGNALED_IO + rdfdes = activefds; +# if defined(VMS) || defined(SYS_VXWORKS) + /* make select() wake up after one second */ + { + struct timeval t1; + + t1.tv_sec = 1; t1.tv_usec = 0; + nfound = select(maxactivefd+1, &rdfdes, (fd_set *)0, + (fd_set *)0, &t1); + } +# else + nfound = select(maxactivefd+1, &rdfdes, (fd_set *)0, + (fd_set *)0, (struct timeval *)0); +# endif /* VMS */ + if (nfound > 0) + { + l_fp ts; + + get_systime(&ts); + + (void)input_handler(&ts); + } + else if (nfound == -1 && errno != EINTR) + msyslog(LOG_ERR, "select() error: %m"); +# ifdef DEBUG + else if (debug > 2) + msyslog(LOG_DEBUG, "select(): nfound=%d, error: %m", nfound); +# endif /* DEBUG */ +# else /* HAVE_SIGNALED_IO */ + + wait_for_signal(); +# endif /* HAVE_SIGNALED_IO */ + if (alarm_flag) /* alarmed? */ + { + was_alarmed = 1; + alarm_flag = 0; + } + rbuflist = getrecvbufs(); /* get received buffers */ + } +# ifdef HAVE_SIGNALED_IO + unblock_io_and_alarm(); +# endif /* HAVE_SIGNALED_IO */ + + /* + * Out here, signals are unblocked. Call timer routine + * to process expiry. + */ + if (was_alarmed) + { + timer(); + was_alarmed = 0; + } + +#endif /* HAVE_IO_COMPLETION_PORT */ + /* + * Call the data procedure to handle each received + * packet. + */ + while (rbuflist != (struct recvbuf *)0) + { + rbuf = rbuflist; + rbuflist = rbuf->next; + (rbuf->receiver)(rbuf); + freerecvbuf(rbuf); + } +#if defined DEBUG && defined SYS_WINNT + if (debug > 4) + printf("getrecvbufs: %ld handler interrupts, %ld frames\n", + handler_calls, handler_pkts); +#endif + + /* + * Go around again + */ + } +#ifndef SYS_WINNT + exit(1); /* unreachable */ +#endif +#ifndef SYS_WINNT + return 1; /* DEC OSF cc braindamage */ +#endif +} + + +#ifdef SIGDIE2 +/* + * finish - exit gracefully + */ +static RETSIGTYPE +finish( + int sig + ) +{ + + msyslog(LOG_NOTICE, "ntpd exiting on signal %d", sig); + + switch (sig) + { +# ifdef SIGBUS + case SIGBUS: + printf("\nfinish(SIGBUS)\n"); + exit(0); +# endif + case 0: /* Should never happen... */ + return; + default: + exit(0); + } +} +#endif /* SIGDIE2 */ + + +#ifdef DEBUG +#ifndef SYS_WINNT +/* + * moredebug - increase debugging verbosity + */ +static RETSIGTYPE +moredebug( + int sig + ) +{ + int saved_errno = errno; + + if (debug < 255) + { + debug++; + msyslog(LOG_DEBUG, "debug raised to %d", debug); + } + errno = saved_errno; +} + +/* + * lessdebug - decrease debugging verbosity + */ +static RETSIGTYPE +lessdebug( + int sig + ) +{ + int saved_errno = errno; + + if (debug > 0) + { + debug--; + msyslog(LOG_DEBUG, "debug lowered to %d", debug); + } + errno = saved_errno; +} +#endif +#else /* not DEBUG */ +#ifndef SYS_WINNT/* + * no_debug - We don't do the debug here. + */ +static RETSIGTYPE +no_debug( + int sig + ) +{ + int saved_errno = errno; + + msyslog(LOG_DEBUG, "ntpd not compiled for debugging (signal %d)", sig); + errno = saved_errno; +} +#endif /* not SYS_WINNT */ +#endif /* not DEBUG */ + +#ifdef SYS_WINNT +/* service_ctrl - control handler for NTP service + * signals the service_main routine of start/stop requests + * from the control panel or other applications making + * win32API calls + */ +void +service_ctrl( + DWORD dwCtrlCode + ) +{ + DWORD dwState = SERVICE_RUNNING; + + /* Handle the requested control code */ + switch(dwCtrlCode) + { + case SERVICE_CONTROL_PAUSE: + /* see no reason to support this */ + break; + + case SERVICE_CONTROL_CONTINUE: + /* see no reason to support this */ + break; + + case SERVICE_CONTROL_STOP: + dwState = SERVICE_STOP_PENDING; + /* + * Report the status, specifying the checkpoint and waithint, + * before setting the termination event. + */ + ssStatus.dwCurrentState = dwState; + ssStatus.dwWin32ExitCode = NO_ERROR; + ssStatus.dwWaitHint = 3000; + if (!SetServiceStatus(sshStatusHandle, &ssStatus)) + { + msyslog(LOG_ERR, "SetServiceStatus: %m"); + } + if (WaitHandles[0] != NULL) { + SetEvent(WaitHandles[0]); + } + return; + + case SERVICE_CONTROL_INTERROGATE: + /* Update the service status */ + break; + + default: + /* invalid control code */ + break; + + } + + ssStatus.dwCurrentState = dwState; + ssStatus.dwWin32ExitCode = NO_ERROR; + if (!SetServiceStatus(sshStatusHandle, &ssStatus)) + { + msyslog(LOG_ERR, "SetServiceStatus: %m"); + } +} + +static BOOL WINAPI +OnConsoleEvent( + DWORD dwCtrlType + ) +{ + switch (dwCtrlType) { + case CTRL_BREAK_EVENT : + if (debug > 0) { + debug <<= 1; + } + else { + debug = 1; + } + if (debug > 8) { + debug = 0; + } + printf("debug level %d\n", debug); + break ; + + case CTRL_C_EVENT : + case CTRL_CLOSE_EVENT : + case CTRL_SHUTDOWN_EVENT : + if (WaitHandles[0] != NULL) { + SetEvent(WaitHandles[0]); + } + break; + + default : + return FALSE; + + + } + return TRUE;; +} + + +/* + * NT version of exit() - all calls to exit() should be routed to + * this function. + */ +void +service_exit( + int status + ) +{ + if (!debug) { /* did not become a service, simply exit */ + /* service mode, need to have the service_main routine + * register with the service control manager that the + * service has stopped running, before exiting + */ + ssStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(sshStatusHandle, &ssStatus); + + } + uninit_io_completion_port(); + reset_winnt_time(); + +# if defined _MSC_VER + _CrtDumpMemoryLeaks(); +# endif +#undef exit + exit(status); +} + +#endif /* SYS_WINNT */ diff --git a/ntpd/ntpsim.c b/ntpd/ntpsim.c new file mode 100644 index 0000000..3fbae17 --- /dev/null +++ b/ntpd/ntpsim.c @@ -0,0 +1,368 @@ +/* + * NTP simulator engine - Harish Nair + * University of Delaware, 2001 + */ +#include "ntpd.h" +#include "ntpsim.h" + +/* + * Defines... + */ +#define SIM_TIME 86400 /* end simulation time */ +#define NET_DLY .001 /* network delay */ +#define PROC_DLY .001 /* processing delay */ +#define BEEP_DLY 3600 /* beep interval (s) */ +#define SLEW 500e-6 /* correction rate (PPM) */ + +/* + * Function pointers + */ +void (*funcPtr[]) (Node *, Event) = { + &ndbeep, &ndeclk, &ntptmr, &netpkt +}; + + +/* + * ntpsim - initialize global variables and event queue and start + */ +int +ntpsim( + int argc, + char *argv[] + ) +{ + Event e; + double maxtime; + struct timeval seed; + + /* + * Initialize the global node + */ + ntp_node.time = 0; /* simulation time */ + ntp_node.sim_time = SIM_TIME; /* end simulation time (-S) */ + ntp_node.ntp_time = 0; /* client disciplined time */ + ntp_node.adj = 0; /* remaining time correction */ + ntp_node.slew = SLEW; /* correction rate (-H) */ + + ntp_node.clk_time = 0; /* server time (-O) */ + ntp_node.ferr = 0; /* frequency error (-T) */ + ntp_node.fnse = 0; /* random walk noise (-W) */ + ntp_node.ndly = NET_DLY; /* network delay (-Y) */ + ntp_node.snse = 0; /* phase noise (-C) */ + ntp_node.pdly = PROC_DLY; /* processing delay (-Z) */ + ntp_node.bdly = BEEP_DLY; /* beep interval (-B) */ + + ntp_node.events = NULL; + ntp_node.rbuflist = NULL; + + /* + * Initialize ntp variables + */ + initializing = 1; + init_auth(); + init_util(); + init_restrict(); + init_mon(); + init_timer(); + init_lib(); + init_random(); + init_request(); + init_control(); + init_peer(); + init_proto(); + init_io(); + init_loopfilter(); + mon_start(MON_OFF); + getconfig(argc, argv); + initializing = 0; + + /* + * Watch out here, we want the real time, not the silly stuff. + */ + gettimeofday(&seed, NULL); + srand48(seed.tv_usec); + + /* + * Push a beep and timer interrupt on the queue + */ + push(event(0, BEEP), &ntp_node.events); + push(event(ntp_node.time + 1.0, TIMER), &ntp_node.events); + + /* + * Pop the queue until nothing is left or time is exceeded + */ + maxtime = ntp_node.time + ntp_node.sim_time; + while (ntp_node.time <= maxtime && ntp_node.events != NULL ) { + e = pop(&ntp_node.events); + ndeclk(&ntp_node, e); + funcPtr[e.function](&ntp_node, e); + } + return (0); +} + + +/* + * Return an event + */ +Event +event( + double t, + funcTkn f + ) +{ + Event e; + + e.time = t; + e.function = f; + return (e); +} + +/* + * Create an event queue + */ +Queue +queue( + Event e, + Queue q + ) +{ + Queue ret; + + if ((ret = (Queue)malloc(sizeof(struct List))) == NULL) + abortsim("queue-malloc"); + ret->event = e; + ret->next = q; + return (ret); +} + + +/* + * Push an event into the event queue + */ +void push( + Event e, + Queue *qp + ) +{ + Queue *tmp = qp; + + while (*tmp != NULL && ((*tmp)->event.time < e.time)) + tmp = &((*tmp)->next); + *tmp = queue(e, (*tmp)); +} + + +/* + * Pop the first event from the event queue + */ +Event +pop( + Queue *qp + ) +{ + Event ret; + Queue tmp; + + tmp = *qp; + if (tmp == NULL) + abortsim("pop - empty queue"); + ret = tmp->event; + *qp = tmp->next; + free(tmp); + return (ret); +} + + +/* + * Update clocks + */ +void +ndeclk( + Node *n, + Event e + ) +{ + node_clock(n, e.time); +} + + +/* + * Timer interrupt. Eventually, this results in calling the + * srvr_rplyi() routine below. + */ +void +ntptmr( + Node *n, + Event e + ) +{ + struct recvbuf *rbuf; + + timer(); + + /* + * Process buffers received. They had better be in order by + * receive timestamp. + */ + while (n->rbuflist != NULL) { + rbuf = n->rbuflist; + n->rbuflist = rbuf->next; + (rbuf->receiver)(rbuf); + free(rbuf); + } + + /* + * Arm the next timer interrupt. + */ + push(event(e.time + (1 << EVENT_TIMEOUT), TIMER), &n->events); +} + + +/* + * srvr_rply() - send packet + */ +int srvr_rply( + Node *n, + struct sockaddr_storage *dest, + struct interface *inter, struct pkt *rpkt + ) +{ + struct pkt xpkt; + struct recvbuf rbuf; + Event xvnt; + double dtemp, etemp; + + /* + * Insert packet header values. We make this look like a + * stratum-1 server with a GPS clock, but nobody will ever + * notice that. + */ + xpkt.li_vn_mode = PKT_LI_VN_MODE(LEAP_NOWARNING, NTP_VERSION, + MODE_SERVER); + xpkt.stratum = STRATUM_TO_PKT(((u_char)1)); + memcpy(&xpkt.refid, "GPS", 4); + xpkt.ppoll = rpkt->ppoll; + xpkt.precision = rpkt->precision; + xpkt.rootdelay = 0; + xpkt.rootdispersion = 0; + + /* + * Insert the timestamps. + */ + xpkt.org = rpkt->xmt; + dtemp = poisson(n->ndly, n->snse); /* client->server delay */ + DTOLFP(dtemp + n->clk_time, &xpkt.rec); + dtemp += poisson(n->pdly, 0); /* server delay */ + DTOLFP(dtemp + n->clk_time, &xpkt.xmt); + xpkt.reftime = xpkt.xmt; + dtemp += poisson(n->ndly, n->snse); /* server->client delay */ + + /* + * Insert the I/O stuff. + */ + rbuf.receiver = receive; + get_systime(&rbuf.recv_time); + rbuf.recv_length = LEN_PKT_NOMAC; + rbuf.recv_pkt = xpkt; + memcpy(&rbuf.srcadr, dest, sizeof(struct sockaddr_storage)); + memcpy(&rbuf.recv_srcadr, dest, + sizeof(struct sockaddr_storage)); + if ((rbuf.dstadr = malloc(sizeof(struct interface))) == NULL) + abortsim("server-malloc"); + memcpy(rbuf.dstadr, inter, sizeof(struct interface)); + rbuf.next = NULL; + + /* + * Very carefully predict the time of arrival for the received + * packet. + */ + LFPTOD(&xpkt.org, etemp); + etemp += dtemp; + xvnt = event(etemp, PACKET); + xvnt.rcv_buf = rbuf; + push(xvnt, &n->events); + return (0); +} + + +/* + * netpkt() - receive packet + */ +void +netpkt( + Node *n, + Event e + ) +{ + struct recvbuf *rbuf; + struct recvbuf *obuf; + + /* + * Insert the packet on the receive queue and record the arrival + * time. + */ + if ((rbuf = malloc(sizeof(struct recvbuf))) == NULL) + abortsim("ntprcv-malloc"); + memcpy(rbuf, &e.rcv_buf, sizeof(struct recvbuf)); + rbuf->receiver = receive; + DTOLFP(n->ntp_time, &rbuf->recv_time); + rbuf->next = NULL; + obuf = n->rbuflist; + + /* + * In the present incarnation, no more than one buffer can be on + * the queue; however, we sniff the queue anyway as a hint for + * further development. + */ + if (obuf == NULL) { + n->rbuflist = rbuf; + } else { + while (obuf->next != NULL) + obuf = obuf->next; + obuf->next = rbuf; + } +} + + +/* + * ndbeep() - progress indicator + */ +void +ndbeep( + Node *n, + Event e + ) +{ + static int first_time = 1; + char *dash = "-----------------"; + + if(n->bdly > 0) { + if (first_time) { + printf( + "\t%4c T %4c\t%4c T+ERR %3c\t%5cT+ERR+NTP\n", ' ', ' ', ' ', ' ',' '); + printf("\t%s\t%s\t%s\n", dash, dash, dash); + first_time = 0; + push(event(n->bdly, BEEP), &n->events); + push(event(n->sim_time, BEEP), &n->events); + printf("\t%16.6f\t%16.6f\t%16.6f\n", + n->time, n->clk_time, n->ntp_time); + return; + } + printf("\t%16.6f\t%16.6f\t%16.6f\n", + n->time, n->clk_time, n->ntp_time); + push(event(e.time + n->bdly, BEEP), &n->events); + } +} + + +/* + * Abort simulation + */ +void +abortsim( + char *errmsg + ) +{ + perror(errmsg); + exit(1); +} diff --git a/ntpd/refclock_acts.c b/ntpd/refclock_acts.c new file mode 100644 index 0000000..d26ceed --- /dev/null +++ b/ntpd/refclock_acts.c @@ -0,0 +1,984 @@ +/* + * refclock_acts - clock driver for the NIST/PTB Automated Computer Time + * Service aka Amalgamated Containerized Trash Service (ACTS) + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && (defined(CLOCK_ACTS) || defined(CLOCK_PTBACTS)) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_control.h" + +#include <stdio.h> +#include <ctype.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +/* MUST BE AFTER LAST #include <config.h> !!! */ + +#if defined(CLOCK_ACTS) && defined(CLOCK_PTBACTS) +# if defined(KEEPPTBACTS) +# undef CLOCK_ACTS +# else /* not KEEPPTBACTS */ +# undef CLOCK_PTBACTS +# endif /* not KEEPPTBACTS */ +#endif /* CLOCK_ACTS && CLOCK_PTBACTS */ + +/* + * This driver supports the NIST Automated Computer Time Service (ACTS). + * It periodically dials a prespecified telephone number, receives the + * NIST timecode data and calculates the local clock correction. It is + * designed primarily for use as a backup when neither a radio clock nor + * connectivity to Internet time servers is available. For the best + * accuracy, the individual telephone line/modem delay needs to be + * calibrated using outside sources. + * + * The ACTS is located at NIST Boulder, CO, telephone 303 494 4774. A + * toll call from a residence telephone in Newark, DE, costs between 14 + * and 27 cents, depending on time of day, and from a campus telephone + * between 3 and 4 cents, although it is not clear what carrier and time + * of day discounts apply in this case. The modem dial string will + * differ depending on local telephone configuration, etc., and is + * specified by the phone command in the configuration file. The + * argument to this command is an AT command for a Hayes compatible + * modem. + * + * The accuracy produced by this driver should be in the range of a + * millisecond or two, but may need correction due to the delay + * characteristics of the individual modem involved. For undetermined + * reasons, some modems work with the ACTS echo-delay measurement scheme + * and some don't. This driver tries to do the best it can with what it + * gets. Initial experiments with a Practical Peripherals 9600SA modem + * here in Delaware suggest an accuracy of a millisecond or two can be + * achieved without the scheme by using a fudge time1 value of 65.0 ms. + * In either case, the dispersion for a single call involving ten + * samples is about 1.3 ms. + * + * The driver can operate in either of three modes, as determined by + * the mode parameter in the server configuration command. In mode 0 + * (automatic) the driver operates continuously at intervals depending + * on the prediction error, as measured by the driver, usually in the + * order of several hours. In mode 1 (backup) the driver is enabled in + * automatic mode only when no other source of synchronization is + * available and when more than MAXOUTAGE (3600 s) have elapsed since + * last synchronized by other sources. In mode 2 (manual) the driver + * operates only when enabled using a fudge flags switch, as described + * below. + * + * For reliable call management, this driver requires a 1200-bps modem + * with a Hayes-compatible command set and control over the modem data + * terminal ready (DTR) control line. Present restrictions require the + * use of a POSIX-compatible programming interface, although other + * interfaces may work as well. The modem setup string is hard-coded in + * the driver and may require changes for nonstandard modems or special + * circumstances. + * + * Further information can be found in the README.refclock file in the + * ntp - Version 3 distribution. + * + * Fudge Factors + * + * Ordinarily, the propagation time correction is computed automatically + * by ACTS and the driver. When this is not possible or erratic due to + * individual modem characteristics, the fudge flag2 switch should be + * set to disable the ACTS echo-delay scheme. In any case, the fudge + * time1 parameter can be used to adjust the propagation delay as + * required. + * + * The ACTS call interval is determined in one of three ways. In manual + * mode a call is initiated by setting fudge flag1 using ntpdc, either + * manually or via a cron job. In AUTO mode this flag is set by the peer + * timer, which is controlled by the sys_poll variable in response to + * measured errors. In backup mode the driver is ordinarily asleep, but + * awakes (in auto mode) if all other synchronization sources are lost. + * In either auto or backup modes, the call interval increases as long + * as the measured errors do not exceed the value of the fudge time2 + * parameter. + * + * When the fudge flag1 is set, the ACTS calling program is activated. + * This program dials each number listed in the phones command of the + * configuration file in turn. If a call attempt fails, the next number + * in the list is dialed. The fudge flag1 and counter are reset and the + * calling program terminated if (a) a valid clock update has been + * determined, (b) no more numbers remain in the list, (c) a device + * fault or timeout occurs or (d) fudge flag1 is reset manually using + * ntpdc. + * + * In automatic and backup modes, the driver determines the call + * interval using a procedure depending on the measured prediction + * error and the fudge time2 parameter. If the error exceeds time2 for a + * number of times depending on the current interval, the interval is + * decreased, but not less than about 1000 s. If the error is less than + * time2 for some number of times, the interval is increased, but not + * more than about 18 h. With the default value of zero for fudge time2, + * the interval will increase from 1000 s to the 4000-8000-s range, in + * which the expected accuracy should be in the 1-2-ms range. Setting + * fudge time2 to a large value, like 0.1 s, may result in errors of + * that order, but increase the call interval to the maximum. The exact + * value for each configuration will depend on the modem and operating + * system involved, so some experimentation may be necessary. + */ + +/* + * DESCRIPTION OF THE AUTOMATED COMPUTER TELEPHONE SERVICE (ACTS) + * (reformatted from ACTS on-line computer help information) + * + * The following is transmitted (at 1200 baud) following completion of + * the telephone connection. + * + * National Institute of Standards and Technology + * Telephone Time Service, Generator 3B + * Enter question mark "?" for HELP + * D L D + * MJD YR MO DA H M S ST S UT1 msADV <OTM> + * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) * + * 47999 90-04-18 21:39:16 50 0 +.1 045.0 UTC(NIST) * + * 47999 90-04-18 21:39:17 50 0 +.1 045.0 UTC(NIST) * + * 47999 90-04-18 21:39:18 50 0 +.1 045.0 UTC(NIST) * + * 47999 90-04-18 21:39:19 50 0 +.1 037.6 UTC(NIST) # + * 47999 90-04-18 21:39:20 50 0 +.1 037.6 UTC(NIST) # + * etc..etc...etc....... + * + * UTC = Universal Time Coordinated, the official world time referred to + * the zero meridian. + * + * DST Daylight savings time characters, valid for the continental + * U.S., are set as follows: + * + * 00 We are on standard time (ST). + * 01-49 Now on DST, go to ST when your local time is 2:00 am and + * the count is 01. The count is decremented daily at 00 + * (UTC). + * 50 We are on DST. + * 51-99 Now on ST, go to DST when your local time is 2:00 am and + * the count is 51. The count is decremented daily at 00 + * (UTC). + * + * The two DST characters provide up to 48 days advance notice of a + * change in time. The count remains at 00 or 50 at other times. + * + * LS Leap second flag is set to "1" to indicate that a leap second is + * to be added as 23:59:60 (UTC) on the last day of the current UTC + * month. The LS flag will be reset to "0" starting with 23:59:60 + * (UTC). The flag will remain on for the entire month before the + * second is added. Leap seconds are added as needed at the end of + * any month. Usually June and/or December are chosen. + * + * The leap second flag will be set to a "2" to indicate that a + * leap second is to be deleted at 23:59:58--00:00:00 on the last + * day of the current month. (This latter provision is included per + * international recommendation, however it is not likely to be + * required in the near future.) + * + * DUT1 Approximate difference between earth rotation time (UT1) and + * UTC, in steps of 0.1 second: DUT1 = UT1 - UTC. + * + * MJD Modified Julian Date, often used to tag certain scientific data. + * + * The full time format is sent at 1200 baud, 8 bit, 1 stop, no parity. + * The format at 300 Baud is also 8 bit, 1 stop, no parity. At 300 Baud + * the MJD and DUT1 values are deleted and the time is transmitted only + * on even seconds. + * + * Maximum on line time will be 56 seconds. If all lines are busy at any + * time, the oldest call will be terminated if it has been on line more + * than 28 seconds, otherwise, the call that first reaches 28 seconds + * will be terminated. + * + * Current time is valid at the "on-time" marker (OTM), either "*" or + * "#". The nominal on-time marker (*) will be transmitted 45 ms early + * to account for the 8 ms required to send 1 character at 1200 Baud, + * plus an additional 7 ms for delay from NIST to the user, and + * approximately 30 ms "scrambler" delay inherent in 1200 Baud modems. + * If the caller echoes all characters, NIST will measure the round trip + * delay and advance the on-time marker so that the midpoint of the stop + * bit arrives at the user on time. The amount of msADV will reflect the + * actual required advance in milliseconds and the OTM will be a "#". + * + * (The NIST system requires 4 or 5 consecutive delay measurements which + * are consistent before switching from "*" to "#". If the user has a + * 1200 Baud modem with the same internal delay as that used by NIST, + * then the "#" OTM should arrive at the user within +-2 ms of the + * correct time. + * + * However, NIST has studied different brands of 1200 Baud modems and + * found internal delays from 24 ms to 40 ms and offsets of the "#" OTM + * of +-10 ms. For many computer users, +-10 ms accuracy should be more + * than adequate since many computer internal clocks can only be set + * with granularity of 20 to 50 ms. In any case, the repeatability of + * the offset for the "#" OTM should be within +-2 ms, if the dial-up + * path is reciprocal and the user doesn't change the brand or model of + * modem used. + * + * This should be true even if the dial-up path on one day is a land- + * line of less than 40 ms (one way) and on the next day is a satellite + * link of 260 to 300 ms. In the rare event that the path is one way by + * satellite and the other way by land line with a round trip + * measurement in the range of 90 to 260 ms, the OTM will remain a "*" + * indicating 45 ms advance. + * + * For user comments write: + * NIST-ACTS + * Time and Frequency Division + * Mail Stop 847 + * 325 Broadway + * Boulder, CO 80303 + * + * Software for setting (PC)DOS compatable machines is available on a + * 360-kbyte diskette for $35.00 from: NIST Office of Standard Reference + * Materials B311-Chemistry Bldg, NIST, Gaithersburg, MD, 20899, (301) + * 975-6776 + * + * PTB timecode service (+49 531 512038) + * The Physikalisch-Technische Bundesanstalt (Germany) + * also supports a modem time service + * as the data formats are very similar this driver can also be compiled for + * utilizing the PTB time code service. + * + * Data format + * 0000000000111111111122222222223333333333444444444455555555556666666666777777777 7 + * 0123456789012345678901234567890123456789012345678901234567890123456789012345678 9 + * 1995-01-23 20:58:51 MEZ 10402303260219950123195849740+40000500 * + * A B C D EF G H IJ K L M N O P Q R S T U V W XY Z<CR><LF> + * + * A year + * B month + * C day + * D hour + * E : normally + * A for DST to ST switch first hour + * B for DST to ST switch second hour if not marked in H + * F minute + * G second + * H timezone + * I day of week + * J week of year + * K day of year + * L month for next ST/DST changes + * M day + * N hour + * O UTC year + * P UTC month + * Q UTC day + * R UTC hour + * S UTC minute + * T modified julian day (MJD) + * U DUT1 + * V direction and month if leap second + * W signal delay (assumed/measured) + * X sequence number for additional text line in Y + * Y additional text + * Z on time marker (* - assumed delay / # measured delay) + * <CR>!<LF> ! is second change ! + * + * This format is also used by the National Physical Laboratory (NPL)'s + * TRUETIME service in the UK. In this case the timezone field is + * UTC+0 or UTC+1 for standard and daylight saving time. The phone + * number for this service (a premium rate number) is 0891 516 333. + * It is not clear whether the echo check is implemented. + * + * For more detail, see http://www.npl.co.uk/npl/cetm/taf/truetime.html. + */ + +/* + * Interface definitions + */ +#define SPEED232 B1200 /* uart speed (1200 cowardly baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#ifdef CLOCK_ACTS +# define REFID "ACTS" /* reference ID */ +# define DESCRIPTION "NIST Automated Computer Time Service" /* WRU */ +# define LENCODE 50 /* length of valid timecode string */ +# define DEVICE "/dev/acts%d" /* device name and unit */ +# define REF_ENTRY refclock_acts +#else /* not CLOCK_ACTS */ +# define REFID "TPTB" /* reference ID */ +# define DESCRIPTION "PTB Automated Computer Time Service" +# define LENCODE 78 /* length of valid timecode string */ +# define DEVICE "/dev/ptb%d" /* device name and unit */ +# define REF_ENTRY refclock_ptb +#endif /* not CLOCK_ACTS */ +#define MODE_AUTO 0 /* automatic mode */ +#define MODE_BACKUP 1 /* backup mode */ +#define MODE_MANUAL 2 /* manual mode */ + +#define MSGCNT 10 /* we need this many ACTS messages */ +#define SMAX 80 /* max token string length */ +#define ACTS_MINPOLL 10 /* log2 min poll interval (1024 s) */ +#define ACTS_MAXPOLL 18 /* log2 max poll interval (16384 s) */ +#define MAXOUTAGE 3600 /* max before ACTS kicks in (s) */ + +/* + * Modem control strings. These may have to be changed for some modems. + * + * AT command prefix + * B1 initiate call negotiation using Bell 212A + * &C1 enable carrier detect + * &D2 hang up and return to command mode on DTR transition + * E0 modem command echo disabled + * l1 set modem speaker volume to low level + * M1 speaker enabled untill carrier detect + * Q0 return result codes + * V1 return result codes as English words + */ +#define MODEM_SETUP "ATB1&C1&D2E0L1M1Q0V1" /* modem setup */ +#define MODEM_HANGUP "ATH" /* modem disconnect */ + +/* + * Timeouts + */ +#define IDLE 60 /* idle timeout (s) */ +#define WAIT 2 /* wait timeout (s) */ +#define ANSWER 30 /* answer timeout (s) */ +#define CONNECT 10 /* connect timeout (s) */ +#define TIMECODE 15 /* timecode timeout (s) */ + +/* + * Tables to compute the ddd of year form icky dd/mm timecode. Viva la + * leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Unit control structure + */ +struct actsunit { + int pollcnt; /* poll message counter */ + int state; /* the first one was Delaware */ + int run; /* call program run switch */ + int msgcnt; /* count of ACTS messages received */ + long redial; /* interval to next automatic call */ + double msADV; /* millisecond advance of last message */ +}; + +/* + * Function prototypes + */ +static int acts_start P((int, struct peer *)); +static void acts_shutdown P((int, struct peer *)); +static void acts_receive P((struct recvbuf *)); +static void acts_poll P((int, struct peer *)); +static void acts_timeout P((struct peer *)); +static void acts_disc P((struct peer *)); +static int acts_write P((struct peer *, const char *)); + +/* + * Transfer vector (conditional structure name) + */ +struct refclock REF_ENTRY = { + acts_start, /* start up driver */ + acts_shutdown, /* shut down driver */ + acts_poll, /* transmit poll message */ + noentry, /* not used (old acts_control) */ + noentry, /* not used (old acts_init) */ + noentry, /* not used (old acts_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * acts_start - open the devices and initialize data for processing + */ + +static int +acts_start ( + int unit, + struct peer *peer + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + int dtr = TIOCM_DTR; + + /* + * Open serial port. Use ACTS line discipline, if available. It + * pumps a timestamp into the data stream at every on-time + * character '*' found. Note: the port must have modem control + * or deep pockets for the phone bill. HP-UX 9.03 users should + * have very deep pockets. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_ACTS))) + return (0); + if (ioctl(fd, TIOCMBIS, (char *)&dtr) < 0) { + msyslog(LOG_ERR, "clock %s ACTS no modem control", + ntoa(&peer->srcadr)); + return (0); + } + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct actsunit *) + emalloc(sizeof(struct actsunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct actsunit)); + pp = peer->procptr; + pp->io.clock_recv = acts_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + peer->minpoll = ACTS_MINPOLL; + peer->maxpoll = ACTS_MAXPOLL; + peer->sstclktype = CTL_SST_TS_TELEPHONE; + + /* + * Initialize modem and kill DTR. We skedaddle if this comes + * bum. + */ + if (!acts_write(peer, MODEM_SETUP)) { + (void) close(fd); + free(up); + return (0); + } + + /* + * Set up the driver timeout + */ + peer->nextdate = current_time + WAIT; + return (1); +} + + +/* + * acts_shutdown - shut down the clock + */ +static void +acts_shutdown ( + int unit, + struct peer *peer + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * acts_receive - receive data from the serial interface + */ +static void +acts_receive ( + struct recvbuf *rbufp + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + struct peer *peer; + char str[SMAX]; + int i; + char hangup = '%'; /* ACTS hangup */ + int day; /* day of the month */ + int month; /* month of the year */ + u_long mjd; /* Modified Julian Day */ + double dut1; /* DUT adjustment */ + double msADV; /* ACTS transmit advance (ms) */ + char flag; /* calibration flag */ +#ifndef CLOCK_PTBACTS + char utc[10]; /* this is NIST and you're not */ + u_int dst; /* daylight/standard time indicator */ + u_int leap; /* leap-second indicator */ +#else + char leapdir; /* leap direction */ + u_int leapmonth; /* month of leap */ +#endif + /* + * Initialize pointers and read the timecode and timestamp. If + * the OK modem status code, leave it where folks can find it. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, + &pp->lastrec); + if (pp->lencode == 0) { + if (strcmp(pp->a_lastcode, "OK") == 0) + pp->lencode = 2; + return; + } +#ifdef DEBUG + if (debug) + printf("acts: state %d timecode %d %*s\n", up->state, + pp->lencode, pp->lencode, pp->a_lastcode); +#endif + + switch (up->state) { + + case 0: + + /* + * State 0. We are not expecting anything. Probably + * modem disconnect noise. Go back to sleep. + */ + return; + + case 1: + + /* + * State 1. We are waiting for the call to be answered. + * All we care about here is CONNECT as the first token + * in the string. If the modem signals BUSY, ERROR, NO + * ANSWER, NO CARRIER or NO DIALTONE, we immediately + * hang up the phone. If CONNECT doesn't happen after + * ANSWER seconds, hang up the phone. If everything is + * okay, start the connect timeout and slide into state + * 2. + */ + if( strcmp(pp->a_lastcode, " ") == 0) { + acts_disc(peer); + return; + } + if( strcmp(sys_phone[0],"DIRECT") != 0 ) { + (void)strncpy(str, strtok(pp->a_lastcode, " "), SMAX); + if (strcmp(str, "BUSY") == 0 || strcmp(str, "ERROR") == + 0 || strcmp(str, "NO") == 0) { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s ACTS modem status %s", + ntoa(&peer->srcadr), pp->a_lastcode); + acts_disc(peer); + } else if (strcmp(str, "CONNECT") == 0) { + peer->nextdate = current_time + CONNECT; + up->msgcnt = 0; + up->state++; + } + } else { + (void) strncpy(str,"CONNECT",7); + peer->nextdate = current_time + CONNECT; + up->msgcnt = 0; + up->state++; + } + return; + + case 2: + + /* + * State 2. The call has been answered and we are + * waiting for the first ACTS message. If this doesn't + * happen within the timecode timeout, hang up the + * phone. We probably got a wrong number or ACTS is + * down. + */ + peer->nextdate = current_time + TIMECODE; + up->state++; + } + + /* + * Real yucky things here. Ignore everything except timecode + * messages, as determined by the message length. We told the + * terminal routines to end the line with '*' and the line + * discipline to strike a timestamp on that character. However, + * when the ACTS echo-delay scheme works, the '*' eventually + * becomes a '#'. In this case the message is ended by the <CR> + * that comes about 200 ms after the '#' and the '#' cannot be + * echoed at the proper time. But, this may not be a lose, since + * we already have good data from prior messages and only need + * the millisecond advance calculated by ACTS. So, if the + * message is long enough and has an on-time character at the + * right place, we consider the message (but not neccesarily the + * timestmap) to be valid. + */ + if (pp->lencode != LENCODE) + return; + +#ifndef CLOCK_PTBACTS + /* + * We apparently have a valid timecode message, so dismember it + * with sscan(). This routine does a good job in spotting syntax + * errors without becoming overly pedantic. + * + * D L D + * MJD YR MO DA H M S ST S UT1 msADV OTM + * 47222 88-03-02 21:39:15 83 0 +.3 045.0 UTC(NBS) * + */ + if (sscanf(pp->a_lastcode, + "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %s %c", + &mjd, &pp->year, &month, &day, &pp->hour, &pp->minute, + &pp->second, &dst, &leap, &dut1, &msADV, utc, &flag) != 13) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } +#else + /* + * Data format + * 0000000000111111111122222222223333333333444444444455555555556666666666777777777 7 + * 0123456789012345678901234567890123456789012345678901234567890123456789012345678 9 + * 1995-01-23 20:58:51 MEZ 10402303260219950123195849740+40000500 * + */ + if (sscanf(pp->a_lastcode, + "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c", + &pp->second, &pp->year, &month, &day, &pp->hour, &pp->minute, &mjd, &dut1, &leapdir, &leapmonth, &msADV, &flag) != 12) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } +#endif + /* + * Some modems can't be trusted (the Practical Peripherals + * 9600SA comes to mind) and, even if they manage to unstick + * ACTS, the millisecond advance is wrong, so we use CLK_FLAG2 + * to disable echoes, if neccessary. + */ + if ((flag == '*' || flag == '#') && !(pp->sloppyclockflag & + CLK_FLAG2)) + (void)write(pp->io.fd, &flag, 1); + + /* + * The ACTS timecode format croaks in 2000. Life is short. + * Would only the timecode mavens resist the urge to express months + * of the year and days of the month in favor of days of the year. + */ + if (month < 1 || month > 12 || day < 1) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * Depending on the driver, at this point we have a two-digit year + * or a four-digit year. Make sure we have a four-digit year. + */ + if ( pp->year < YEAR_PIVOT ) pp->year += 100; /* Y2KFixes */ + if ( pp->year < YEAR_BREAK ) pp->year += 1900; /* Y2KFixes */ + if ( !isleap_4(pp->year) ) { /* Y2KFixes */ + if (day > day1tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } else { + if (day > day2tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } + pp->day = day; + +#ifndef CLOCK_PTBACTS + if (leap == 1) + pp->leap = LEAP_ADDSECOND; + else if (pp->leap == 2) + pp->leap = LEAP_DELSECOND; +#else + if (leapmonth == month) { + if (leapdir == '+') + pp->leap = LEAP_ADDSECOND; + else if (leapdir == '-') + pp->leap = LEAP_DELSECOND; + } +#endif + + /* + * Colossal hack here. We process each sample in a trimmed-mean + * filter and determine the reference clock offset and + * dispersion. The fudge time1 value is added to each sample as + * received. If we collect MSGCNT samples before the '#' on-time + * character, we use the results of the filter as is. If the '#' + * is found before that, the adjusted msADV is used to correct + * the propagation delay. + */ + up->msgcnt++; + if (flag == '#') { + pp->offset += (msADV - up->msADV) * 1000 * 1e-6; + } else { + up->msADV = msADV; + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } else if (up->msgcnt < MSGCNT) + return; + } + + /* + * We have a filtered sample offset ready for peer processing. + * We use lastrec as both the reference time and receive time in + * order to avoid being cute, like setting the reference time + * later than the receive time, which may cause a paranoid + * protocol module to chuck out the data. Finaly, we unhook the + * timeout, arm for the next call, fold the tent and go home. + * The little dance with the '%' character is an undocumented + * ACTS feature that hangs up the phone real quick without + * waiting for carrier loss or long-space disconnect, but we do + * these clumsy things anyway. + */ + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + pp->sloppyclockflag &= ~CLK_FLAG1; + up->pollcnt = 0; + (void)write(pp->io.fd, &hangup, 1); + up->state = 0; + acts_disc(peer); +} + + +/* + * acts_poll - called by the transmit routine + */ +static void +acts_poll ( + int unit, + struct peer *peer + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + + /* + * If the driver is running, we set the enable flag (fudge + * flag1), which causes the driver timeout routine to initiate a + * call to ACTS. If not, the enable flag can be set using + * ntpdc. If this is the sustem peer, then follow the system + * poll interval. + */ + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + + if (up->run) { + pp->sloppyclockflag |= CLK_FLAG1; + if (peer == sys_peer) + peer->hpoll = sys_poll; + else + peer->hpoll = peer->minpoll; + } + acts_timeout (peer); + return; +} + + +/* + * acts_timeout - called by the timer interrupt + */ +static void +acts_timeout ( + struct peer *peer + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + int dtr = TIOCM_DTR; + + /* + * If a timeout occurs in other than state 0, the call has + * failed. If in state 0, we just see if there is other work to + * do. + */ + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + if (up->state) { + acts_disc(peer); + return; + } + switch (peer->ttl) { + + /* + * In manual mode the ACTS calling program is activated + * by the ntpdc program using the enable flag (fudge + * flag1), either manually or by a cron job. + */ + case MODE_MANUAL: + up->run = 0; + break; + + /* + * In automatic mode the ACTS calling program runs + * continuously at intervals determined by the sys_poll + * variable. + */ + case MODE_AUTO: + if (!up->run) + pp->sloppyclockflag |= CLK_FLAG1; + up->run = 1; + break; + + /* + * In backup mode the ACTS calling program is disabled, + * unless no system peer has been selected for MAXOUTAGE + * (3600 s). Once enabled, it runs until some other NTP + * peer shows up. + */ + case MODE_BACKUP: + if (!up->run && sys_peer == 0) { + if (current_time - last_time > MAXOUTAGE) { + up->run = 1; + peer->hpoll = peer->minpoll; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s ACTS backup started ", + ntoa(&peer->srcadr)); + } + } else if (up->run && sys_peer->sstclktype != CTL_SST_TS_TELEPHONE) { + peer->hpoll = peer->minpoll; + up->run = 0; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s ACTS backup stopped", + ntoa(&peer->srcadr)); + } + break; + + default: + msyslog(LOG_ERR, + "clock %s ACTS invalid mode", ntoa(&peer->srcadr)); + } + + /* + * The fudge flag1 is used as an enable/disable; if set either + * by the code or via ntpdc, the ACTS calling program is + * started; if reset, the phones stop ringing. + */ + if (!(pp->sloppyclockflag & CLK_FLAG1)) { + up->pollcnt = 0; + peer->nextdate = current_time + IDLE; + return; + } + + /* + * Initiate a call to the ACTS service. If we wind up here in + * other than state 0, a successful call could not be completed + * within minpoll seconds. We advance to the next modem dial + * string. If none are left, we log a notice and clear the + * enable flag. For future enhancement: call the site RP and + * leave an obscene message in his voicemail. + */ + if (sys_phone[up->pollcnt][0] == '\0') { + refclock_report(peer, CEVNT_TIMEOUT); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s ACTS calling program terminated", + ntoa(&peer->srcadr)); + pp->sloppyclockflag &= ~CLK_FLAG1; +#ifdef DEBUG + if (debug) + printf("acts: calling program terminated\n"); +#endif + up->pollcnt = 0; + peer->nextdate = current_time + IDLE; + return; + } + + /* + * Raise DTR, call ACTS and start the answer timeout. We think + * it strange if the OK status has not been received from the + * modem, but plow ahead anyway. + */ + if (strcmp(pp->a_lastcode, "OK") != 0) + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s ACTS no modem status", + ntoa(&peer->srcadr)); + (void)ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr); + (void)acts_write(peer, sys_phone[up->pollcnt]); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s ACTS calling %s\n", + ntoa(&peer->srcadr), sys_phone[up->pollcnt]); + up->state = 1; + up->pollcnt++; + pp->polls++; + peer->nextdate = current_time + ANSWER; + return; +} + + +/* + * acts_disc - disconnect the call and wait for the ruckus to cool + */ +static void +acts_disc ( + struct peer *peer + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + int dtr = TIOCM_DTR; + + /* + * We should never get here other than in state 0, unless a call + * has timed out. We drop DTR, which will reliably get the modem + * off the air, even while ACTS is hammering away full tilt. + */ + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + (void)ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr); + if (up->state > 0) { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s ACTS call failed %d", + ntoa(&peer->srcadr), up->state); +#ifdef DEBUG + if (debug) + printf("acts: call failed %d\n", up->state); +#endif + up->state = 0; + } + peer->nextdate = current_time + WAIT; +} + + +/* + * acts_write - write a message to the serial port + */ +static int +acts_write ( + struct peer *peer, + const char *str + ) +{ + register struct actsunit *up; + struct refclockproc *pp; + int len; + int code; + char cr = '\r'; + + /* + * Not much to do here, other than send the message, handle + * debug and report faults. + */ + pp = peer->procptr; + up = (struct actsunit *)pp->unitptr; + len = strlen(str); +#ifdef DEBUG + if (debug) + printf("acts: state %d send %d %s\n", up->state, len, + str); +#endif + code = write(pp->io.fd, str, (unsigned)len) == len; + code &= write(pp->io.fd, &cr, 1) == 1; + if (!code) + refclock_report(peer, CEVNT_FAULT); + return (code); +} + +#else +int refclock_acts_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_arbiter.c b/ntpd/refclock_arbiter.c new file mode 100644 index 0000000..cf5f92f --- /dev/null +++ b/ntpd/refclock_arbiter.c @@ -0,0 +1,429 @@ +/* + * refclock_arbiter - clock driver for Arbiter 1088A/B Satellite + * Controlled Clock + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_ARBITER) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the Arbiter 1088A/B Satellite Controlled Clock. + * The claimed accuracy of this clock is 100 ns relative to the PPS + * output when receiving four or more satellites. + * + * The receiver should be configured before starting the NTP daemon, in + * order to establish reliable position and operating conditions. It + * does not initiate surveying or hold mode. For use with NTP, the + * daylight savings time feature should be disables (D0 command) and the + * broadcast mode set to operate in UTC (BU command). + * + * The timecode format supported by this driver is selected by the poll + * sequence "B5", which initiates a line in the following format to be + * repeated once per second until turned off by the "B0" poll sequence. + * + * Format B5 (24 ASCII printing characters): + * + * <cr><lf>i yy ddd hh:mm:ss.000bbb + * + * on-time = <cr> + * i = synchronization flag (' ' = locked, '?' = unlocked) + * yy = year of century + * ddd = day of year + * hh:mm:ss = hours, minutes, seconds + * .000 = fraction of second (not used) + * bbb = tailing spaces for fill + * + * The alarm condition is indicated by a '?' at i, which indicates the + * receiver is not synchronized. In normal operation, a line consisting + * of the timecode followed by the time quality character (TQ) followed + * by the receiver status string (SR) is written to the clockstats file. + * The time quality character is encoded in IEEE P1344 standard: + * + * Format TQ (IEEE P1344 estimated worst-case time quality) + * + * 0 clock locked, maximum accuracy + * F clock failure, time not reliable + * 4 clock unlocked, accuracy < 1 us + * 5 clock unlocked, accuracy < 10 us + * 6 clock unlocked, accuracy < 100 us + * 7 clock unlocked, accuracy < 1 ms + * 8 clock unlocked, accuracy < 10 ms + * 9 clock unlocked, accuracy < 100 ms + * A clock unlocked, accuracy < 1 s + * B clock unlocked, accuracy < 10 s + * + * The status string is encoded as follows: + * + * Format SR (25 ASCII printing characters) + * + * V=vv S=ss T=t P=pdop E=ee + * + * vv = satellites visible + * ss = relative signal strength + * t = satellites tracked + * pdop = position dilution of precision (meters) + * ee = hardware errors + * + * If flag4 is set, an additional line consisting of the receiver + * latitude (LA), longitude (LO) and elevation (LH) (meters) is written + * to this file. If channel B is enabled for deviation mode and connected + * to a 1-PPS signal, the last two numbers on the line are the deviation + * and standard deviation averaged over the last 15 seconds. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/gps%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS " /* reference ID */ +#define DESCRIPTION "Arbiter 1088A/B GPS Receiver" /* WRU */ + +#define LENARB 24 /* format B5 timecode length */ +#define MAXSTA 30 /* max length of status string */ +#define MAXPOS 70 /* max length of position string */ + +/* + * ARB unit control structure + */ +struct arbunit { + l_fp laststamp; /* last receive timestamp */ + int tcswitch; /* timecode switch/counter */ + char qualchar; /* IEEE P1344 quality (TQ command) */ + char status[MAXSTA]; /* receiver status (SR command) */ + char latlon[MAXPOS]; /* receiver position (lat/lon/alt) */ +}; + +/* + * Function prototypes + */ +static int arb_start P((int, struct peer *)); +static void arb_shutdown P((int, struct peer *)); +static void arb_receive P((struct recvbuf *)); +static void arb_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_arbiter = { + arb_start, /* start up driver */ + arb_shutdown, /* shut down driver */ + arb_poll, /* transmit poll message */ + noentry, /* not used (old arb_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old arb_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * arb_start - open the devices and initialize data for processing + */ +static int +arb_start( + int unit, + struct peer *peer + ) +{ + register struct arbunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct arbunit *)emalloc(sizeof(struct arbunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct arbunit)); + pp = peer->procptr; + pp->io.clock_recv = arb_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + write(pp->io.fd, "B0", 2); + return (1); +} + + +/* + * arb_shutdown - shut down the clock + */ +static void +arb_shutdown( + int unit, + struct peer *peer + ) +{ + register struct arbunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct arbunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * arb_receive - receive data from the serial interface + */ +static void +arb_receive( + struct recvbuf *rbufp + ) +{ + register struct arbunit *up; + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + int temp; + u_char syncchar; /* synchronization indicator */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct arbunit *)pp->unitptr; + temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + + /* + * Note we get a buffer and timestamp for both a <cr> and <lf>, + * but only the <cr> timestamp is retained. The program first + * sends a TQ and expects the echo followed by the time quality + * character. It then sends a B5 starting the timecode broadcast + * and expects the echo followed some time later by the on-time + * character <cr> and then the <lf> beginning the timecode + * itself. Finally, at the <cr> beginning the next timecode at + * the next second, the program sends a B0 shutting down the + * timecode broadcast. + * + * If flag4 is set, the program snatches the latitude, longitude + * and elevation and writes it to the clockstats file. + */ + if (temp == 0) + return; + pp->lastrec = up->laststamp; + up->laststamp = trtmp; + if (temp < 3) + return; + if (up->tcswitch == 0) { + + /* + * Collect statistics. If nothing is recogized, just + * ignore; sometimes the clock doesn't stop spewing + * timecodes for awhile after the B0 commant. + */ + if (!strncmp(pp->a_lastcode, "TQ", 2)) { + up->qualchar = pp->a_lastcode[2]; + write(pp->io.fd, "SR", 2); + } else if (!strncmp(pp->a_lastcode, "SR", 2)) { + strcpy(up->status, pp->a_lastcode + 2); + if (pp->sloppyclockflag & CLK_FLAG4) + write(pp->io.fd, "LA", 2); + else { + write(pp->io.fd, "B5", 2); + up->tcswitch++; + } + } else if (!strncmp(pp->a_lastcode, "LA", 2)) { + strcpy(up->latlon, pp->a_lastcode + 2); + write(pp->io.fd, "LO", 2); + } else if (!strncmp(pp->a_lastcode, "LO", 2)) { + strcat(up->latlon, " "); + strcat(up->latlon, pp->a_lastcode + 2); + write(pp->io.fd, "LH", 2); + } else if (!strncmp(pp->a_lastcode, "LH", 2)) { + strcat(up->latlon, " "); + strcat(up->latlon, pp->a_lastcode + 2); + write(pp->io.fd, "DB", 2); + } else if (!strncmp(pp->a_lastcode, "DB", 2)) { + strcat(up->latlon, " "); + strcat(up->latlon, pp->a_lastcode + 2); + record_clock_stats(&peer->srcadr, up->latlon); + write(pp->io.fd, "B5", 2); + up->tcswitch++; + } + return; + } + pp->lencode = temp; + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has valid length, but not in + * proper format, we declare bad format and exit. If the + * timecode has invalid length, which sometimes occurs when the + * B0 amputates the broadcast, we just quietly steal away. Note + * that the time quality character and receiver status string is + * tacked on the end for clockstats display. + */ + if (pp->lencode == LENARB) { + /* + * Timecode format B5: "i yy ddd hh:mm:ss.000 " + */ + pp->a_lastcode[LENARB - 2] = up->qualchar; + strcat(pp->a_lastcode, up->status); + syncchar = ' '; + if (sscanf(pp->a_lastcode, "%c%2d %3d %2d:%2d:%2d", + &syncchar, &pp->year, &pp->day, &pp->hour, + &pp->minute, &pp->second) != 6) { + refclock_report(peer, CEVNT_BADREPLY); + write(pp->io.fd, "B0", 2); + return; + } + } else { + write(pp->io.fd, "B0", 2); + return; + } + up->tcswitch++; + + /* + * We decode the clock dispersion from the time quality + * character. + */ + switch (up->qualchar) { + + case '0': /* locked, max accuracy */ + pp->disp = 1e-7; + break; + + case '4': /* unlock accuracy < 1 us */ + pp->disp = 1e-6; + break; + + case '5': /* unlock accuracy < 10 us */ + pp->disp = 1e-5; + break; + + case '6': /* unlock accuracy < 100 us */ + pp->disp = 1e-4; + break; + + case '7': /* unlock accuracy < 1 ms */ + pp->disp = .001; + break; + + case '8': /* unlock accuracy < 10 ms */ + pp->disp = .01; + break; + + case '9': /* unlock accuracy < 100 ms */ + pp->disp = .1; + break; + + case 'A': /* unlock accuracy < 1 s */ + pp->disp = 1; + break; + + case 'B': /* unlock accuracy < 10 s */ + pp->disp = 10; + break; + + case 'F': /* clock failure */ + pp->disp = MAXDISPERSE; + refclock_report(peer, CEVNT_FAULT); + write(pp->io.fd, "B0", 2); + return; + + default: + pp->disp = MAXDISPERSE; + refclock_report(peer, CEVNT_BADREPLY); + write(pp->io.fd, "B0", 2); + return; + } + if (syncchar != ' ') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; +#ifdef DEBUG + if (debug) + printf("arbiter: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + if (up->tcswitch >= NSTAGE) + write(pp->io.fd, "B0", 2); + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) + refclock_report(peer, CEVNT_BADTIME); +} + + +/* + * arb_poll - called by the transmit procedure + */ +static void +arb_poll( + int unit, + struct peer *peer + ) +{ + register struct arbunit *up; + struct refclockproc *pp; + + /* + * Time to poll the clock. The Arbiter clock responds to a "B5" + * by returning a timecode in the format specified above. + * Transmission occurs once per second, unless turned off by a + * "B0". Note there is no checking on state, since this may not + * be the only customer reading the clock. Only one customer + * need poll the clock; all others just listen in. If nothing is + * heard from the clock for two polls, declare a timeout and + * keep going. + */ + pp = peer->procptr; + up = (struct arbunit *)pp->unitptr; + up->tcswitch = 0; + if (write(pp->io.fd, "TQ", 2) != 2) { + refclock_report(peer, CEVNT_FAULT); + } else + pp->polls++; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +} + +#else +int refclock_arbiter_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_arc.c b/ntpd/refclock_arc.c new file mode 100644 index 0000000..f556da6 --- /dev/null +++ b/ntpd/refclock_arc.c @@ -0,0 +1,1529 @@ +/* + * refclock_arc - clock driver for ARCRON MSF/DCF/WWVB receivers + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_ARCRON_MSF) +static const char arc_version[] = { "V1.3 2003/02/21" }; + +/* define PRE_NTP420 for compatibility to previous versions of NTP (at least + to 4.1.0 */ +#undef PRE_NTP420 + +#ifndef ARCRON_NOT_KEEN +#define ARCRON_KEEN 1 /* Be keen, and trusting of the clock, if defined. */ +#endif + +#ifndef ARCRON_NOT_MULTIPLE_SAMPLES +#define ARCRON_MULTIPLE_SAMPLES 1 /* Use all timestamp bytes as samples. */ +#endif + +#ifndef ARCRON_NOT_LEAPSECOND_KEEN +#ifndef ARCRON_LEAPSECOND_KEEN +#undef ARCRON_LEAPSECOND_KEEN /* Respond quickly to leap seconds: doesn't work yet. */ +#endif +#endif + +/* +Code by Derek Mulcahy, <derek@toybox.demon.co.uk>, 1997. +Modifications by Damon Hart-Davis, <d@hd.org>, 1997. +Modifications by Paul Alfille, <palfille@partners.org>, 2003. +Modifications by Christopher Price, <cprice@cs-home.com>, 2003. + + +THIS CODE IS SUPPLIED AS IS, WITH NO WARRANTY OF ANY KIND. USE AT +YOUR OWN RISK. + +Orginally developed and used with ntp3-5.85 by Derek Mulcahy. + +Built against ntp3-5.90 on Solaris 2.5 using gcc 2.7.2. + +This code may be freely copied and used and incorporated in other +systems providing the disclaimer and notice of authorship are +reproduced. + +------------------------------------------------------------------------------- + +Christopher's notes: + +MAJOR CHANGES SINCE V1.2 +======================== + 1) Applied patch by Andrey Bray <abuse@madhouse.demon.co.uk> + 2001-02-17 comp.protocols.time.ntp + + 2) Added WWVB support via clock mode command, localtime/UTC time configured + via flag1=(0=UTC, 1=localtime) + + 3) Added ignore resync request via flag2=(0=resync, 1=ignore resync) + + 4) Added simplified conversion from localtime to UTC with dst/bst translation + + 5) Added average signal quality poll + + 6) Fixed a badformat error when no code is available due to stripping + \n & \r's + + 7) Fixed a badformat error when clearing lencode & memset a_lastcode in poll + routine + + 8) Lots of code cleanup, including standardized DEBUG macros and removal + of unused code + +------------------------------------------------------------------------------- + +Author's original note: + +I enclose my ntp driver for the Galleon Systems Arc MSF receiver. + +It works (after a fashion) on both Solaris-1 and Solaris-2. + +I am currently using ntp3-5.85. I have been running the code for +about 7 months without any problems. Even coped with the change to BST! + +I had to do some funky things to read from the clock because it uses the +power from the receive lines to drive the transmit lines. This makes the +code look a bit stupid but it works. I also had to put in some delays to +allow for the turnaround time from receive to transmit. These delays +are between characters when requesting a time stamp so that shouldn't affect +the results too drastically. + +... + +The bottom line is that it works but could easily be improved. You are +free to do what you will with the code. I haven't been able to determine +how good the clock is. I think that this requires a known good clock +to compare it against. + +------------------------------------------------------------------------------- + +Damon's notes for adjustments: + +MAJOR CHANGES SINCE V1.0 +======================== + 1) Removal of pollcnt variable that made the clock go permanently + off-line once two time polls failed to gain responses. + + 2) Avoiding (at least on Solaris-2) terminal becoming the controlling + terminal of the process when we do a low-level open(). + + 3) Additional logic (conditional on ARCRON_LEAPSECOND_KEEN being + defined) to try to resync quickly after a potential leap-second + insertion or deletion. + + 4) Code significantly slimmer at run-time than V1.0. + + +GENERAL +======= + + 1) The C preprocessor symbol to have the clock built has been changed + from ARC to ARCRON_MSF to CLOCK_ARCRON_MSF to minimise the + possiblity of clashes with other symbols in the future. + + 2) PRECISION should be -4/-5 (63ms/31ms) for the following reasons: + + a) The ARC documentation claims the internal clock is (only) + accurate to about 20ms relative to Rugby (plus there must be + noticable drift and delay in the ms range due to transmission + delays and changing atmospheric effects). This clock is not + designed for ms accuracy as NTP has spoilt us all to expect. + + b) The clock oscillator looks like a simple uncompensated quartz + crystal of the sort used in digital watches (ie 32768Hz) which + can have large temperature coefficients and drifts; it is not + clear if this oscillator is properly disciplined to the MSF + transmission, but as the default is to resync only once per + *day*, we can imagine that it is not, and is free-running. We + can minimise drift by resyncing more often (at the cost of + reduced battery life), but drift/wander may still be + significant. + + c) Note that the bit time of 3.3ms adds to the potential error in + the the clock timestamp, since the bit clock of the serial link + may effectively be free-running with respect to the host clock + and the MSF clock. Actually, the error is probably 1/16th of + the above, since the input data is probably sampled at at least + 16x the bit rate. + + By keeping the clock marked as not very precise, it will have a + fairly large dispersion, and thus will tend to be used as a + `backup' time source and sanity checker, which this clock is + probably ideal for. For an isolated network without other time + sources, this clock can probably be expected to provide *much* + better than 1s accuracy, which will be fine. + + By default, PRECISION is set to -4, but experience, especially at a + particular geographic location with a particular clock, may allow + this to be altered to -5. (Note that skews of +/- 10ms are to be + expected from the clock from time-to-time.) This improvement of + reported precision can be instigated by setting flag3 to 1, though + the PRECISION will revert to the normal value while the clock + signal quality is unknown whatever the flag3 setting. + + IN ANY CASE, BE SURE TO SET AN APPROPRIATE FUDGE FACTOR TO REMOVE + ANY RESIDUAL SKEW, eg: + + server 127.127.27.0 # ARCRON MSF radio clock unit 0. + # Fudge timestamps by about 20ms. + fudge 127.127.27.0 time1 0.020 + + You will need to observe your system's behaviour, assuming you have + some other NTP source to compare it with, to work out what the + fudge factor should be. For my Sun SS1 running SunOS 4.1.3_U1 with + my MSF clock with my distance from the MSF transmitter, +20ms + seemed about right, after some observation. + + 3) REFID has been made "MSFa" to reflect the MSF time source and the + ARCRON receiver. + + 4) DEFAULT_RESYNC_TIME is the time in seconds (by default) before + forcing a resync since the last attempt. This is picked to give a + little less than an hour between resyncs and to try to avoid + clashing with any regular event at a regular time-past-the-hour + which might cause systematic errors. + + The INITIAL_RESYNC_DELAY is to avoid bothering the clock and + running down its batteries unnecesarily if ntpd is going to crash + or be killed or reconfigured quickly. If ARCRON_KEEN is defined + then this period is long enough for (with normal polling rates) + enough time samples to have been taken to allow ntpd to sync to + the clock before the interruption for the clock to resync to MSF. + This avoids ntpd syncing to another peer first and then + almost immediately hopping to the MSF clock. + + The RETRY_RESYNC_TIME is used before rescheduling a resync after a + resync failed to reveal a statisfatory signal quality (too low or + unknown). + + 5) The clock seems quite jittery, so I have increased the + median-filter size from the typical (previous) value of 3. I + discard up to half the results in the filter. It looks like maybe + 1 sample in 10 or so (maybe less) is a spike, so allow the median + filter to discard at least 10% of its entries or 1 entry, whichever + is greater. + + 6) Sleeping *before* each character sent to the unit to allow required + inter-character time but without introducting jitter and delay in + handling the response if possible. + + 7) If the flag ARCRON_KEEN is defined, take time samples whenever + possible, even while resyncing, etc. We rely, in this case, on the + clock always giving us a reasonable time or else telling us in the + status byte at the end of the timestamp that it failed to sync to + MSF---thus we should never end up syncing to completely the wrong + time. + + 8) If the flag ARCRON_OWN_FILTER is defined, use own versions of + refclock median-filter routines to get round small bug in 3-5.90 + code which does not return the median offset. XXX Removed this + bit due NTP Version 4 upgrade - dlm. + + 9) We would appear to have a year-2000 problem with this clock since + it returns only the two least-significant digits of the year. But + ntpd ignores the year and uses the local-system year instead, so + this is in fact not a problem. Nevertheless, we attempt to do a + sensible thing with the dates, wrapping them into a 100-year + window. + + 10)Logs stats information that can be used by Derek's Tcl/Tk utility + to show the status of the clock. + + 11)The clock documentation insists that the number of bits per + character to be sent to the clock, and sent by it, is 11, including + one start bit and two stop bits. The data format is either 7+even + or 8+none. + + +TO-DO LIST +========== + + * Eliminate use of scanf(), and maybe sprintf(). + + * Allow user setting of resync interval to trade battery life for + accuracy; maybe could be done via fudge factor or unit number. + + * Possibly note the time since the last resync of the MSF clock to + MSF as the age of the last reference timestamp, ie trust the + clock's oscillator not very much... + + * Add very slow auto-adjustment up to a value of +/- time2 to correct + for long-term errors in the clock value (time2 defaults to 0 so the + correction would be disabled by default). + + * Consider trying to use the tty_clk/ppsclock support. + + * Possibly use average or maximum signal quality reported during + resync, rather than just the last one, which may be atypical. + +*/ + + +/* Notes for HKW Elektronik GmBH Radio clock driver */ +/* Author Lyndon David, Sentinet Ltd, Feb 1997 */ +/* These notes seem also to apply usefully to the ARCRON clock. */ + +/* The HKW clock module is a radio receiver tuned into the Rugby */ +/* MSF time signal tranmitted on 60 kHz. The clock module connects */ +/* to the computer via a serial line and transmits the time encoded */ +/* in 15 bytes at 300 baud 7 bits two stop bits even parity */ + +/* Clock communications, from the datasheet */ +/* All characters sent to the clock are echoed back to the controlling */ +/* device. */ +/* Transmit time/date information */ +/* syntax ASCII o<cr> */ +/* Character o may be replaced if neccesary by a character whose code */ +/* contains the lowest four bits f(hex) eg */ +/* syntax binary: xxxx1111 00001101 */ + +/* DHD note: +You have to wait for character echo + 10ms before sending next character. +*/ + +/* The clock replies to this command with a sequence of 15 characters */ +/* which contain the complete time and a final <cr> making 16 characters */ +/* in total. */ +/* The RC computer clock will not reply immediately to this command because */ +/* the start bit edge of the first reply character marks the beginning of */ +/* the second. So the RC Computer Clock will reply to this command at the */ +/* start of the next second */ +/* The characters have the following meaning */ +/* 1. hours tens */ +/* 2. hours units */ +/* 3. minutes tens */ +/* 4. minutes units */ +/* 5. seconds tens */ +/* 6. seconds units */ +/* 7. day of week 1-monday 7-sunday */ +/* 8. day of month tens */ +/* 9. day of month units */ +/* 10. month tens */ +/* 11. month units */ +/* 12. year tens */ +/* 13. year units */ +/* 14. BST/UTC status */ +/* bit 7 parity */ +/* bit 6 always 0 */ +/* bit 5 always 1 */ +/* bit 4 always 1 */ +/* bit 3 always 0 */ +/* bit 2 =1 if UTC is in effect, complementary to the BST bit */ +/* bit 1 =1 if BST is in effect, according to the BST bit */ +/* bit 0 BST/UTC change impending bit=1 in case of change impending */ +/* 15. status */ +/* bit 7 parity */ +/* bit 6 always 0 */ +/* bit 5 always 1 */ +/* bit 4 always 1 */ +/* bit 3 =1 if low battery is detected */ +/* bit 2 =1 if the very last reception attempt failed and a valid */ +/* time information already exists (bit0=1) */ +/* =0 if the last reception attempt was successful */ +/* bit 1 =1 if at least one reception since 2:30 am was successful */ +/* =0 if no reception attempt since 2:30 am was successful */ +/* bit 0 =1 if the RC Computer Clock contains valid time information */ +/* This bit is zero after reset and one after the first */ +/* successful reception attempt */ + +/* DHD note: +Also note g<cr> command which confirms that a resync is in progress, and +if so what signal quality (0--5) is available. +Also note h<cr> command which starts a resync to MSF signal. +*/ + + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#if defined(HAVE_BSD_TTYS) +#include <sgtty.h> +#endif /* HAVE_BSD_TTYS */ + +#if defined(HAVE_SYSV_TTYS) +#include <termio.h> +#endif /* HAVE_SYSV_TTYS */ + +#if defined(HAVE_TERMIOS) +#include <termios.h> +#endif + +/* + * This driver supports the ARCRON MSF/DCF/WWVB Radio Controlled Clock + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/arc%d" /* Device name and unit. */ +#define SPEED B300 /* UART speed (300 baud) */ +#define PRECISION (-4) /* Precision (~63 ms). */ +#define HIGHPRECISION (-5) /* If things are going well... */ +#define REFID "MSFa" /* Reference ID. */ +#define REFID_MSF "MSF" /* Reference ID. */ +#define REFID_DCF77 "DCF" /* Reference ID. */ +#define REFID_WWVB "WWVB" /* Reference ID. */ +#define DESCRIPTION "ARCRON MSF/DCF/WWVB Receiver" + +#ifdef PRE_NTP420 +#define MODE ttlmax +#else +#define MODE ttl +#endif + +#define LENARC 16 /* Format `o' timecode length. */ + +#define BITSPERCHAR 11 /* Bits per character. */ +#define BITTIME 0x0DA740E /* Time for 1 bit at 300bps. */ +#define CHARTIME10 0x8888888 /* Time for 10-bit char at 300bps. */ +#define CHARTIME11 0x962FC96 /* Time for 11-bit char at 300bps. */ +#define CHARTIME /* Time for char at 300bps. */ \ +( (BITSPERCHAR == 11) ? CHARTIME11 : ( (BITSPERCHAR == 10) ? CHARTIME10 : \ + (BITSPERCHAR * BITTIME) ) ) + + /* Allow for UART to accept char half-way through final stop bit. */ +#define INITIALOFFSET (u_int32)(-BITTIME/2) + + /* + charoffsets[x] is the time after the start of the second that byte + x (with the first byte being byte 1) is received by the UART, + assuming that the initial edge of the start bit of the first byte + is on-time. The values are represented as the fractional part of + an l_fp. + + We store enough values to have the offset of each byte including + the trailing \r, on the assumption that the bytes follow one + another without gaps. + */ + static const u_int32 charoffsets[LENARC+1] = { +#if BITSPERCHAR == 11 /* Usual case. */ + /* Offsets computed as accurately as possible... */ + 0, + INITIALOFFSET + 0x0962fc96, /* 1 chars, 11 bits */ + INITIALOFFSET + 0x12c5f92c, /* 2 chars, 22 bits */ + INITIALOFFSET + 0x1c28f5c3, /* 3 chars, 33 bits */ + INITIALOFFSET + 0x258bf259, /* 4 chars, 44 bits */ + INITIALOFFSET + 0x2eeeeeef, /* 5 chars, 55 bits */ + INITIALOFFSET + 0x3851eb85, /* 6 chars, 66 bits */ + INITIALOFFSET + 0x41b4e81b, /* 7 chars, 77 bits */ + INITIALOFFSET + 0x4b17e4b1, /* 8 chars, 88 bits */ + INITIALOFFSET + 0x547ae148, /* 9 chars, 99 bits */ + INITIALOFFSET + 0x5dddddde, /* 10 chars, 110 bits */ + INITIALOFFSET + 0x6740da74, /* 11 chars, 121 bits */ + INITIALOFFSET + 0x70a3d70a, /* 12 chars, 132 bits */ + INITIALOFFSET + 0x7a06d3a0, /* 13 chars, 143 bits */ + INITIALOFFSET + 0x8369d037, /* 14 chars, 154 bits */ + INITIALOFFSET + 0x8ccccccd, /* 15 chars, 165 bits */ + INITIALOFFSET + 0x962fc963 /* 16 chars, 176 bits */ +#else + /* Offsets computed with a small rounding error... */ + 0, + INITIALOFFSET + 1 * CHARTIME, + INITIALOFFSET + 2 * CHARTIME, + INITIALOFFSET + 3 * CHARTIME, + INITIALOFFSET + 4 * CHARTIME, + INITIALOFFSET + 5 * CHARTIME, + INITIALOFFSET + 6 * CHARTIME, + INITIALOFFSET + 7 * CHARTIME, + INITIALOFFSET + 8 * CHARTIME, + INITIALOFFSET + 9 * CHARTIME, + INITIALOFFSET + 10 * CHARTIME, + INITIALOFFSET + 11 * CHARTIME, + INITIALOFFSET + 12 * CHARTIME, + INITIALOFFSET + 13 * CHARTIME, + INITIALOFFSET + 14 * CHARTIME, + INITIALOFFSET + 15 * CHARTIME, + INITIALOFFSET + 16 * CHARTIME +#endif + }; + +#define DEFAULT_RESYNC_TIME (57*60) /* Gap between resync attempts (s). */ +#define RETRY_RESYNC_TIME (27*60) /* Gap to emergency resync attempt. */ +#ifdef ARCRON_KEEN +#define INITIAL_RESYNC_DELAY 500 /* Delay before first resync. */ +#else +#define INITIAL_RESYNC_DELAY 50 /* Delay before first resync. */ +#endif + + static const int moff[12] = +{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; +/* Flags for a raw open() of the clock serial device. */ +#ifdef O_NOCTTY /* Good, we can avoid tty becoming controlling tty. */ +#define OPEN_FLAGS (O_RDWR | O_NOCTTY) +#else /* Oh well, it may not matter... */ +#define OPEN_FLAGS (O_RDWR) +#endif + + +/* Length of queue of command bytes to be sent. */ +#define CMDQUEUELEN 4 /* Enough for two cmds + each \r. */ +/* Queue tick time; interval in seconds between chars taken off queue. */ +/* Must be >= 2 to allow o\r response to come back uninterrupted. */ +#define QUEUETICK 2 /* Allow o\r reply to finish. */ + +/* + * ARC unit control structure + */ +struct arcunit { + l_fp lastrec; /* Time tag for the receive time (system). */ + int status; /* Clock status. */ + + int quality; /* Quality of reception 0--5 for unit. */ + /* We may also use the values -1 or 6 internally. */ + u_long quality_stamp; /* Next time to reset quality average. */ + + u_long next_resync; /* Next resync time (s) compared to current_time. */ + int resyncing; /* Resync in progress if true. */ + + /* In the outgoing queue, cmdqueue[0] is next to be sent. */ + char cmdqueue[CMDQUEUELEN+1]; /* Queue of outgoing commands + \0. */ + + u_long saved_flags; /* Saved fudge flags. */ +}; + +#ifdef ARCRON_LEAPSECOND_KEEN +/* The flag `possible_leap' is set non-zero when any MSF unit + thinks a leap-second may have happened. + + Set whenever we receive a valid time sample in the first hour of + the first day of the first/seventh months. + + Outside the special hour this value is unconditionally set + to zero by the receive routine. + + On finding itself in this timeslot, as long as the value is + non-negative, the receive routine sets it to a positive value to + indicate a resync to MSF should be performed. + + In the poll routine, if this value is positive and we are not + already resyncing (eg from a sync that started just before + midnight), start resyncing and set this value negative to + indicate that a leap-triggered resync has been started. Having + set this negative prevents the receive routine setting it + positive and thus prevents multiple resyncs during the witching + hour. + */ +static int possible_leap = 0; /* No resync required by default. */ +#endif + +#if 0 +static void dummy_event_handler P((struct peer *)); +static void arc_event_handler P((struct peer *)); +#endif /* 0 */ + +#define QUALITY_UNKNOWN -1 /* Indicates unknown clock quality. */ +#define MIN_CLOCK_QUALITY 0 /* Min quality clock will return. */ +#define MIN_CLOCK_QUALITY_OK 3 /* Min quality for OK reception. */ +#define MAX_CLOCK_QUALITY 5 /* Max quality clock will return. */ + +/* + * Function prototypes + */ +static int arc_start P((int, struct peer *)); +static void arc_shutdown P((int, struct peer *)); +static void arc_receive P((struct recvbuf *)); +static void arc_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_arc = { + arc_start, /* start up driver */ + arc_shutdown, /* shut down driver */ + arc_poll, /* transmit poll message */ + noentry, /* not used (old arc_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old arc_buginfo) */ + NOFLAGS /* not used */ +}; + +/* Queue us up for the next tick. */ +#define ENQUEUE(up) \ + do { \ + peer->nextaction = current_time + QUEUETICK; \ + } while(0) + +/* Placeholder event handler---does nothing safely---soaks up loose tick. */ +static void +dummy_event_handler( + struct peer *peer + ) +{ +#ifdef DEBUG + if(debug) { printf("arc: dummy_event_handler() called.\n"); } +#endif +} + +/* +Normal event handler. + +Take first character off queue and send to clock if not a null. + +Shift characters down and put a null on the end. + +We assume that there is no parallelism so no race condition, but even +if there is nothing bad will happen except that we might send some bad +data to the clock once in a while. +*/ +static void +arc_event_handler( + struct peer *peer + ) +{ + struct refclockproc *pp = peer->procptr; + register struct arcunit *up = (struct arcunit *)pp->unitptr; + int i; + char c; +#ifdef DEBUG + if(debug > 2) { printf("arc: arc_event_handler() called.\n"); } +#endif + + c = up->cmdqueue[0]; /* Next char to be sent. */ + /* Shift down characters, shifting trailing \0 in at end. */ + for(i = 0; i < CMDQUEUELEN; ++i) + { up->cmdqueue[i] = up->cmdqueue[i+1]; } + + /* Don't send '\0' characters. */ + if(c != '\0') { + if(write(pp->io.fd, &c, 1) != 1) { + msyslog(LOG_NOTICE, "ARCRON: write to fd %d failed", pp->io.fd); + } +#ifdef DEBUG + else if(debug) { printf("arc: sent `%2.2x', fd %d.\n", c, pp->io.fd); } +#endif + } + + ENQUEUE(up); +} + +/* + * arc_start - open the devices and initialize data for processing + */ +static int +arc_start( + int unit, + struct peer *peer + ) +{ + register struct arcunit *up; + struct refclockproc *pp; + int fd; + char device[20]; +#ifdef HAVE_TERMIOS + struct termios arg; +#endif + + msyslog(LOG_NOTICE, "ARCRON: %s: opening unit %d", arc_version, unit); +#ifdef DEBUG + if(debug) { + printf("arc: %s: attempt to open unit %d.\n", arc_version, unit); + } +#endif + + /* Prevent a ridiculous device number causing overflow of device[]. */ + if((unit < 0) || (unit > 255)) { return(0); } + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED, LDISC_CLK))) + return(0); +#ifdef DEBUG + if(debug) { printf("arc: unit %d using open().\n", unit); } +#endif + fd = open(device, OPEN_FLAGS); + if(fd < 0) { +#ifdef DEBUG + if(debug) { printf("arc: failed [open()] to open %s.\n", device); } +#endif + return(0); + } + + fcntl(fd, F_SETFL, 0); /* clear the descriptor flags */ +#ifdef DEBUG + if(debug) + { printf("arc: opened RS232 port with file descriptor %d.\n", fd); } +#endif + +#ifdef HAVE_TERMIOS + + arg.c_iflag = IGNBRK | ISTRIP; + arg.c_oflag = 0; + arg.c_cflag = B300 | CS8 | CREAD | CLOCAL | CSTOPB; + arg.c_lflag = 0; + arg.c_cc[VMIN] = 1; + arg.c_cc[VTIME] = 0; + + tcsetattr(fd, TCSANOW, &arg); + +#else + + msyslog(LOG_ERR, "ARCRON: termios not supported in this driver"); + (void)close(fd); + + return 0; + +#endif + + up = (struct arcunit *) emalloc(sizeof(struct arcunit)); + if(!up) { (void) close(fd); return(0); } + /* Set structure to all zeros... */ + memset((char *)up, 0, sizeof(struct arcunit)); + pp = peer->procptr; + pp->io.clock_recv = arc_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if(!io_addclock(&pp->io)) { (void) close(fd); free(up); return(0); } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + peer->stratum = 2; /* Default to stratum 2 not 0. */ + pp->clockdesc = DESCRIPTION; + if (peer->MODE > 3) { + msyslog(LOG_NOTICE, "ARCRON: Invalid mode %d", peer->MODE); + return 0; + } +#ifdef DEBUG + if(debug) { printf("arc: mode = %d.\n", peer->MODE); } +#endif + switch (peer->MODE) { + case 1: + memcpy((char *)&pp->refid, REFID_MSF, 4); + break; + case 2: + memcpy((char *)&pp->refid, REFID_DCF77, 4); + break; + case 3: + memcpy((char *)&pp->refid, REFID_WWVB, 4); + break; + default: + memcpy((char *)&pp->refid, REFID, 4); + break; + } + /* Spread out resyncs so that they should remain separated. */ + up->next_resync = current_time + INITIAL_RESYNC_DELAY + (67*unit)%1009; + +#if 0 /* Not needed because of zeroing of arcunit structure... */ + up->resyncing = 0; /* Not resyncing yet. */ + up->saved_flags = 0; /* Default is all flags off. */ + /* Clear send buffer out... */ + { + int i; + for(i = CMDQUEUELEN; i >= 0; --i) { up->cmdqueue[i] = '\0'; } + } +#endif + +#ifdef ARCRON_KEEN + up->quality = QUALITY_UNKNOWN; /* Trust the clock immediately. */ +#else + up->quality = MIN_CLOCK_QUALITY;/* Don't trust the clock yet. */ +#endif + + peer->action = arc_event_handler; + + ENQUEUE(up); + + return(1); +} + + +/* + * arc_shutdown - shut down the clock + */ +static void +arc_shutdown( + int unit, + struct peer *peer + ) +{ + register struct arcunit *up; + struct refclockproc *pp; + + peer->action = dummy_event_handler; + + pp = peer->procptr; + up = (struct arcunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + +/* +Compute space left in output buffer. +*/ +static int +space_left( + register struct arcunit *up + ) +{ + int spaceleft; + + /* Compute space left in buffer after any pending output. */ + for(spaceleft = 0; spaceleft < CMDQUEUELEN; ++spaceleft) + { if(up->cmdqueue[CMDQUEUELEN - 1 - spaceleft] != '\0') { break; } } + return(spaceleft); +} + +/* +Send command by copying into command buffer as far forward as possible, +after any pending output. + +Indicate an error by returning 0 if there is not space for the command. +*/ +static int +send_slow( + register struct arcunit *up, + int fd, + const char *s + ) +{ + int sl = strlen(s); + int spaceleft = space_left(up); + +#ifdef DEBUG + if(debug > 1) { printf("arc: spaceleft = %d.\n", spaceleft); } +#endif + if(spaceleft < sl) { /* Should not normally happen... */ +#ifdef DEBUG + msyslog(LOG_NOTICE, "ARCRON: send-buffer overrun (%d/%d)", + sl, spaceleft); +#endif + return(0); /* FAILED! */ + } + + /* Copy in the command to be sent. */ + while(*s) { up->cmdqueue[CMDQUEUELEN - spaceleft--] = *s++; } + + return(1); +} + + +/* Macro indicating action we will take for different quality values. */ +#define quality_action(q) \ +(((q) == QUALITY_UNKNOWN) ? "UNKNOWN, will use clock anyway" : \ + (((q) < MIN_CLOCK_QUALITY_OK) ? "TOO POOR, will not use clock" : \ + "OK, will use clock")) + + /* + * arc_receive - receive data from the serial interface + */ + static void +arc_receive( + struct recvbuf *rbufp + ) +{ + register struct arcunit *up; + struct refclockproc *pp; + struct peer *peer; + char c; + int i, n, wday, month, flags, status; + int arc_last_offset; + static int quality_average = 0; + static int quality_sum = 0; + static int quality_polls = 0; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct arcunit *)pp->unitptr; + + + /* + If the command buffer is empty, and we are resyncing, insert a + g\r quality request into it to poll for signal quality again. + */ + if((up->resyncing) && (space_left(up) == CMDQUEUELEN)) { +#ifdef DEBUG + if(debug > 1) { printf("arc: inserting signal-quality poll.\n"); } +#endif + send_slow(up, pp->io.fd, "g\r"); + } + + /* + The `arc_last_offset' is the offset in lastcode[] of the last byte + received, and which we assume actually received the input + timestamp. + + (When we get round to using tty_clk and it is available, we + assume that we will receive the whole timecode with the + trailing \r, and that that \r will be timestamped. But this + assumption also works if receive the characters one-by-one.) + */ + arc_last_offset = pp->lencode+rbufp->recv_length - 1; + + /* + We catch a timestamp iff: + + * The command code is `o' for a timestamp. + + * If ARCRON_MULTIPLE_SAMPLES is undefined then we must have + exactly char in the buffer (the command code) so that we + only sample the first character of the timecode as our + `on-time' character. + + * The first character in the buffer is not the echoed `\r' + from the `o` command (so if we are to timestamp an `\r' it + must not be first in the receive buffer with lencode==1. + (Even if we had other characters following it, we probably + would have a premature timestamp on the '\r'.) + + * We have received at least one character (I cannot imagine + how it could be otherwise, but anyway...). + */ + c = rbufp->recv_buffer[0]; + if((pp->a_lastcode[0] == 'o') && +#ifndef ARCRON_MULTIPLE_SAMPLES + (pp->lencode == 1) && +#endif + ((pp->lencode != 1) || (c != '\r')) && + (arc_last_offset >= 1)) { + /* Note that the timestamp should be corrected if >1 char rcvd. */ + l_fp timestamp; + timestamp = rbufp->recv_time; +#ifdef DEBUG + if(debug) { /* Show \r as `R', other non-printing char as `?'. */ + printf("arc: stamp -->%c<-- (%d chars rcvd)\n", + ((c == '\r') ? 'R' : (isgraph((int)c) ? c : '?')), + rbufp->recv_length); + } +#endif + + /* + Now correct timestamp by offset of last byte received---we + subtract from the receive time the delay implied by the + extra characters received. + + Reject the input if the resulting code is too long, but + allow for the trailing \r, normally not used but a good + handle for tty_clk or somesuch kernel timestamper. + */ + if(arc_last_offset > LENARC) { +#ifdef DEBUG + if(debug) { + printf("arc: input code too long (%d cf %d); rejected.\n", + arc_last_offset, LENARC); + } +#endif + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + L_SUBUF(×tamp, charoffsets[arc_last_offset]); +#ifdef DEBUG + if(debug > 1) { + printf( + "arc: %s%d char(s) rcvd, the last for lastcode[%d]; -%sms offset applied.\n", + ((rbufp->recv_length > 1) ? "*** " : ""), + rbufp->recv_length, + arc_last_offset, + mfptoms((unsigned long)0, + charoffsets[arc_last_offset], + 1)); + } +#endif + +#ifdef ARCRON_MULTIPLE_SAMPLES + /* + If taking multiple samples, capture the current adjusted + sample iff: + + * No timestamp has yet been captured (it is zero), OR + + * This adjusted timestamp is earlier than the one already + captured, on the grounds that this one suffered less + delay in being delivered to us and is more accurate. + + */ + if(L_ISZERO(&(up->lastrec)) || + L_ISGEQ(&(up->lastrec), ×tamp)) +#endif + { +#ifdef DEBUG + if(debug > 1) { + printf("arc: system timestamp captured.\n"); +#ifdef ARCRON_MULTIPLE_SAMPLES + if(!L_ISZERO(&(up->lastrec))) { + l_fp diff; + diff = up->lastrec; + L_SUB(&diff, ×tamp); + printf("arc: adjusted timestamp by -%sms.\n", + mfptoms(diff.l_i, diff.l_f, 3)); + } +#endif + } +#endif + up->lastrec = timestamp; + } + + } + + /* Just in case we still have lots of rubbish in the buffer... */ + /* ...and to avoid the same timestamp being reused by mistake, */ + /* eg on receipt of the \r coming in on its own after the */ + /* timecode. */ + if(pp->lencode >= LENARC) { +#ifdef DEBUG + if(debug && (rbufp->recv_buffer[0] != '\r')) + { printf("arc: rubbish in pp->a_lastcode[].\n"); } +#endif + pp->lencode = 0; + return; + } + + /* Append input to code buffer, avoiding overflow. */ + for(i = 0; i < rbufp->recv_length; i++) { + if(pp->lencode >= LENARC) { break; } /* Avoid overflow... */ + c = rbufp->recv_buffer[i]; + + /* Drop trailing '\r's and drop `h' command echo totally. */ + if(c != '\r' && c != 'h') { pp->a_lastcode[pp->lencode++] = c; } + + /* + If we've just put an `o' in the lastcode[0], clear the + timestamp in anticipation of a timecode arriving soon. + + We would expect to get to process this before any of the + timecode arrives. + */ + if((c == 'o') && (pp->lencode == 1)) { + L_CLR(&(up->lastrec)); +#ifdef DEBUG + if(debug > 1) { printf("arc: clearing timestamp.\n"); } +#endif + } + } + if (pp->lencode == 0) return; + + /* Handle a quality message. */ + if(pp->a_lastcode[0] == 'g') { + int r, q; + + if(pp->lencode < 3) { return; } /* Need more data... */ + r = (pp->a_lastcode[1] & 0x7f); /* Strip parity. */ + q = (pp->a_lastcode[2] & 0x7f); /* Strip parity. */ + if(((q & 0x70) != 0x30) || ((q & 0xf) > MAX_CLOCK_QUALITY) || + ((r & 0x70) != 0x30)) { + /* Badly formatted response. */ +#ifdef DEBUG + if(debug) { printf("arc: bad `g' response %2x %2x.\n", r, q); } +#endif + return; + } + if(r == '3') { /* Only use quality value whilst sync in progress. */ + if (up->quality_stamp < current_time) { + struct calendar cal; + l_fp new_stamp; + + get_systime (&new_stamp); + caljulian (new_stamp.l_ui, &cal); + up->quality_stamp = + current_time + 60 - cal.second + 5; + quality_sum = 0; + quality_polls = 0; + } + quality_sum += (q & 0xf); + quality_polls++; + quality_average = (quality_sum / quality_polls); +#ifdef DEBUG + if(debug) { printf("arc: signal quality %d (%d).\n", quality_average, (q & 0xf)); } +#endif + } else if( /* (r == '2') && */ up->resyncing) { + up->quality = quality_average; +#ifdef DEBUG + if(debug) + { + printf("arc: sync finished, signal quality %d: %s\n", + up->quality, + quality_action(up->quality)); + } +#endif + msyslog(LOG_NOTICE, + "ARCRON: sync finished, signal quality %d: %s", + up->quality, + quality_action(up->quality)); + up->resyncing = 0; /* Resync is over. */ + quality_average = 0; + quality_sum = 0; + quality_polls = 0; + +#ifdef ARCRON_KEEN + /* Clock quality dubious; resync earlier than usual. */ + if((up->quality == QUALITY_UNKNOWN) || + (up->quality < MIN_CLOCK_QUALITY_OK)) + { up->next_resync = current_time + RETRY_RESYNC_TIME; } +#endif + } + pp->lencode = 0; + return; + } + + /* Stop now if this is not a timecode message. */ + if(pp->a_lastcode[0] != 'o') { + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* If we don't have enough data, wait for more... */ + if(pp->lencode < LENARC) { return; } + + + /* WE HAVE NOW COLLECTED ONE TIMESTAMP (phew)... */ +#ifdef DEBUG + if(debug > 1) { printf("arc: NOW HAVE TIMESTAMP...\n"); } +#endif + + /* But check that we actually captured a system timestamp on it. */ + if(L_ISZERO(&(up->lastrec))) { +#ifdef DEBUG + if(debug) { printf("arc: FAILED TO GET SYSTEM TIMESTAMP\n"); } +#endif + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + /* + Append a mark of the clock's received signal quality for the + benefit of Derek Mulcahy's Tcl/Tk utility (we map the `unknown' + quality value to `6' for his s/w) and terminate the string for + sure. This should not go off the buffer end. + */ + pp->a_lastcode[pp->lencode] = ((up->quality == QUALITY_UNKNOWN) ? + '6' : ('0' + up->quality)); + pp->a_lastcode[pp->lencode + 1] = '\0'; /* Terminate for printf(). */ + +#ifdef PRE_NTP420 + /* We don't use the micro-/milli- second part... */ + pp->usec = 0; + pp->msec = 0; +#else + /* We don't use the nano-second part... */ + pp->nsec = 0; +#endif + n = sscanf(pp->a_lastcode, "o%2d%2d%2d%1d%2d%2d%2d%1d%1d", + &pp->hour, &pp->minute, &pp->second, + &wday, &pp->day, &month, &pp->year, &flags, &status); + + /* Validate format and numbers. */ + if(n != 9) { +#ifdef DEBUG + /* Would expect to have caught major problems already... */ + if(debug) { printf("arc: badly formatted data.\n"); } +#endif + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + /* + Validate received values at least enough to prevent internal + array-bounds problems, etc. + */ + if((pp->hour < 0) || (pp->hour > 23) || + (pp->minute < 0) || (pp->minute > 59) || + (pp->second < 0) || (pp->second > 60) /*Allow for leap seconds.*/ || + (wday < 1) || (wday > 7) || + (pp->day < 1) || (pp->day > 31) || + (month < 1) || (month > 12) || + (pp->year < 0) || (pp->year > 99)) { + /* Data out of range. */ + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + + if(peer->MODE == 0) { /* compatiblity to original version */ + int bst = flags; + /* Check that BST/UTC bits are the complement of one another. */ + if(!(bst & 2) == !(bst & 4)) { + pp->lencode = 0; + refclock_report(peer, CEVNT_BADREPLY); + return; + } + } + if(status & 0x8) { msyslog(LOG_NOTICE, "ARCRON: battery low"); } + + /* Year-2000 alert! */ + /* Attempt to wrap 2-digit date into sensible window. */ + if(pp->year < YEAR_PIVOT) { pp->year += 100; } /* Y2KFixes */ + pp->year += 1900; /* use full four-digit year */ /* Y2KFixes */ + /* + Attempt to do the right thing by screaming that the code will + soon break when we get to the end of its useful life. What a + hero I am... PLEASE FIX LEAP-YEAR AND WRAP CODE IN 209X! + */ + if(pp->year >= YEAR_PIVOT+2000-2 ) { /* Y2KFixes */ + /*This should get attention B^> */ + msyslog(LOG_NOTICE, + "ARCRON: fix me! EITHER YOUR DATE IS BADLY WRONG or else I will break soon!"); + } +#ifdef DEBUG + if(debug) { + printf("arc: n=%d %02d:%02d:%02d %02d/%02d/%04d %1d %1d\n", + n, + pp->hour, pp->minute, pp->second, + pp->day, month, pp->year, flags, status); + } +#endif + + /* + The status value tested for is not strictly supported by the + clock spec since the value of bit 2 (0x4) is claimed to be + undefined for MSF, yet does seem to indicate if the last resync + was successful or not. + */ + pp->leap = LEAP_NOWARNING; + status &= 0x7; + if(status == 0x3) { + if(status != up->status) + { msyslog(LOG_NOTICE, "ARCRON: signal acquired"); } + } else { + if(status != up->status) { + msyslog(LOG_NOTICE, "ARCRON: signal lost"); + pp->leap = LEAP_NOTINSYNC; /* MSF clock is free-running. */ + up->status = status; + pp->lencode = 0; + refclock_report(peer, CEVNT_FAULT); + return; + } + } + up->status = status; + + if (peer->MODE == 0) { /* compatiblity to original version */ + int bst = flags; + + pp->day += moff[month - 1]; + + if(isleap_4(pp->year) && month > 2) { pp->day++; }/* Y2KFixes */ + + /* Convert to UTC if required */ + if(bst & 2) { + pp->hour--; + if (pp->hour < 0) { + pp->hour = 23; + pp->day--; + /* If we try to wrap round the year + * (BST on 1st Jan), reject.*/ + if(pp->day < 0) { + pp->lencode = 0; + refclock_report(peer, CEVNT_BADTIME); + return; + } + } + } + } + + if(peer->MODE > 0) { + if(pp->sloppyclockflag & CLK_FLAG1) { + struct tm local; + struct tm *gmtp; + time_t unixtime; + + /* + * Convert to GMT for sites that distribute localtime. + * This means we have to do Y2K conversion on the + * 2-digit year; otherwise, we get the time wrong. + */ + + local.tm_year = pp->year-1900; + local.tm_mon = month-1; + local.tm_mday = pp->day; + local.tm_hour = pp->hour; + local.tm_min = pp->minute; + local.tm_sec = pp->second; + switch (peer->MODE) { + case 1: + local.tm_isdst = (flags & 2); + break; + case 2: + local.tm_isdst = (flags & 2); + break; + case 3: + switch (flags & 3) { + case 0: /* It is unclear exactly when the + Arcron changes from DST->ST and + ST->DST. Testing has shown this + to be irregular. For the time + being, let the OS decide. */ + local.tm_isdst = 0; +#ifdef DEBUG + if (debug) + printf ("arc: DST = 00 (0)\n"); +#endif + break; + case 1: /* dst->st time */ + local.tm_isdst = -1; +#ifdef DEBUG + if (debug) + printf ("arc: DST = 01 (1)\n"); +#endif + break; + case 2: /* st->dst time */ + local.tm_isdst = -1; +#ifdef DEBUG + if (debug) + printf ("arc: DST = 10 (2)\n"); +#endif + break; + case 3: /* dst time */ + local.tm_isdst = 1; +#ifdef DEBUG + if (debug) + printf ("arc: DST = 11 (3)\n"); +#endif + break; + } + break; + default: + msyslog(LOG_NOTICE, "ARCRON: Invalid mode %d", + peer->MODE); + return; + break; + } + unixtime = mktime (&local); + if ((gmtp = gmtime (&unixtime)) == NULL) + { + pp->lencode = 0; + refclock_report (peer, CEVNT_FAULT); + return; + } + pp->year = gmtp->tm_year+1900; + month = gmtp->tm_mon+1; + pp->day = ymd2yd(pp->year,month,gmtp->tm_mday); + /* pp->day = gmtp->tm_yday; */ + pp->hour = gmtp->tm_hour; + pp->minute = gmtp->tm_min; + pp->second = gmtp->tm_sec; +#ifdef DEBUG + if (debug) + { + printf ("arc: time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", + pp->year,month,gmtp->tm_mday,pp->hour,pp->minute, + pp->second); + } +#endif + } else + { + /* + * For more rational sites distributing UTC + */ + pp->day = ymd2yd(pp->year,month,pp->day); + } + } + + if (peer->MODE == 0) { /* compatiblity to original version */ + /* If clock signal quality is + * unknown, revert to default PRECISION...*/ + if(up->quality == QUALITY_UNKNOWN) { + peer->precision = PRECISION; + } else { /* ...else improve precision if flag3 is set... */ + peer->precision = ((pp->sloppyclockflag & CLK_FLAG3) ? + HIGHPRECISION : PRECISION); + } + } else { + if ((status == 0x3) && (pp->sloppyclockflag & CLK_FLAG2)) { + peer->precision = ((pp->sloppyclockflag & CLK_FLAG3) ? + HIGHPRECISION : PRECISION); + } else if (up->quality == QUALITY_UNKNOWN) { + peer->precision = PRECISION; + } else { + peer->precision = ((pp->sloppyclockflag & CLK_FLAG3) ? + HIGHPRECISION : PRECISION); + } + } + + /* Notice and log any change (eg from initial defaults) for flags. */ + if(up->saved_flags != pp->sloppyclockflag) { +#ifdef DEBUG + msyslog(LOG_NOTICE, "ARCRON: flags enabled: %s%s%s%s", + ((pp->sloppyclockflag & CLK_FLAG1) ? "1" : "."), + ((pp->sloppyclockflag & CLK_FLAG2) ? "2" : "."), + ((pp->sloppyclockflag & CLK_FLAG3) ? "3" : "."), + ((pp->sloppyclockflag & CLK_FLAG4) ? "4" : ".")); + /* Note effects of flags changing... */ + if(debug) { + printf("arc: PRECISION = %d.\n", peer->precision); + } +#endif + up->saved_flags = pp->sloppyclockflag; + } + + /* Note time of last believable timestamp. */ + pp->lastrec = up->lastrec; + +#ifdef ARCRON_LEAPSECOND_KEEN + /* Find out if a leap-second might just have happened... + (ie is this the first hour of the first day of Jan or Jul?) + */ + if((pp->hour == 0) && + (pp->day == 1) && + ((month == 1) || (month == 7))) { + if(possible_leap >= 0) { + /* A leap may have happened, and no resync has started yet...*/ + possible_leap = 1; + } + } else { + /* Definitely not leap-second territory... */ + possible_leap = 0; + } +#endif + + if (!refclock_process(pp)) { + pp->lencode = 0; + refclock_report(peer, CEVNT_BADTIME); + return; + } + record_clock_stats(&peer->srcadr, pp->a_lastcode); + refclock_receive(peer); +} + + +/* request_time() sends a time request to the clock with given peer. */ +/* This automatically reports a fault if necessary. */ +/* No data should be sent after this until arc_poll() returns. */ +static void request_time P((int, struct peer *)); +static void +request_time( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp = peer->procptr; + register struct arcunit *up = (struct arcunit *)pp->unitptr; +#ifdef DEBUG + if(debug) { printf("arc: unit %d: requesting time.\n", unit); } +#endif + if (!send_slow(up, pp->io.fd, "o\r")) { +#ifdef DEBUG + if (debug) { + printf("arc: unit %d: problem sending", unit); + } +#endif + pp->lencode = 0; + refclock_report(peer, CEVNT_FAULT); + return; + } + pp->polls++; +} + +/* + * arc_poll - called by the transmit procedure + */ +static void +arc_poll( + int unit, + struct peer *peer + ) +{ + register struct arcunit *up; + struct refclockproc *pp; + int resync_needed; /* Should we start a resync? */ + + pp = peer->procptr; + up = (struct arcunit *)pp->unitptr; +#if 0 + pp->lencode = 0; + memset(pp->a_lastcode, 0, sizeof(pp->a_lastcode)); +#endif + +#if 0 + /* Flush input. */ + tcflush(pp->io.fd, TCIFLUSH); +#endif + + /* Resync if our next scheduled resync time is here or has passed. */ + resync_needed = ( !(pp->sloppyclockflag & CLK_FLAG2) && + (up->next_resync <= current_time) ); + +#ifdef ARCRON_LEAPSECOND_KEEN + /* + Try to catch a potential leap-second insertion or deletion quickly. + + In addition to the normal NTP fun of clocks that don't report + leap-seconds spooking their hosts, this clock does not even + sample the radio sugnal the whole time, so may miss a + leap-second insertion or deletion for up to a whole sample + time. + + To try to minimise this effect, if in the first few minutes of + the day immediately following a leap-second-insertion point + (ie in the first hour of the first day of the first and sixth + months), and if the last resync was in the previous day, and a + resync is not already in progress, resync the clock + immediately. + + */ + if((possible_leap > 0) && /* Must be 00:XX 01/0{1,7}/XXXX. */ + (!up->resyncing)) { /* No resync in progress yet. */ + resync_needed = 1; + possible_leap = -1; /* Prevent multiple resyncs. */ + msyslog(LOG_NOTICE,"ARCRON: unit %d: checking for leap second",unit); + } +#endif + + /* Do a resync if required... */ + if(resync_needed) { + /* First, reset quality value to `unknown' so we can detect */ + /* when a quality message has been responded to by this */ + /* being set to some other value. */ + up->quality = QUALITY_UNKNOWN; + + /* Note that we are resyncing... */ + up->resyncing = 1; + + /* Now actually send the resync command and an immediate poll. */ +#ifdef DEBUG + if(debug) { printf("arc: sending resync command (h\\r).\n"); } +#endif + msyslog(LOG_NOTICE, "ARCRON: unit %d: sending resync command", unit); + send_slow(up, pp->io.fd, "h\r"); + + /* Schedule our next resync... */ + up->next_resync = current_time + DEFAULT_RESYNC_TIME; + + /* Drop through to request time if appropriate. */ + } + + /* If clock quality is too poor to trust, indicate a fault. */ + /* If quality is QUALITY_UNKNOWN and ARCRON_KEEN is defined,*/ + /* we'll cross our fingers and just hope that the thing */ + /* synced so quickly we did not catch it---we'll */ + /* double-check the clock is OK elsewhere. */ + if( +#ifdef ARCRON_KEEN + (up->quality != QUALITY_UNKNOWN) && +#else + (up->quality == QUALITY_UNKNOWN) || +#endif + (up->quality < MIN_CLOCK_QUALITY_OK)) { +#ifdef DEBUG + if(debug) { + printf("arc: clock quality %d too poor.\n", up->quality); + } +#endif + pp->lencode = 0; + refclock_report(peer, CEVNT_FAULT); + return; + } + /* This is the normal case: request a timestamp. */ + request_time(unit, peer); +} + +#else +int refclock_arc_bs; +#endif diff --git a/ntpd/refclock_as2201.c b/ntpd/refclock_as2201.c new file mode 100644 index 0000000..f04d417b --- /dev/null +++ b/ntpd/refclock_as2201.c @@ -0,0 +1,388 @@ +/* + * refclock_as2201 - clock driver for the Austron 2201A GPS + * Timing Receiver + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_AS2201) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the Austron 2200A/2201A GPS Receiver with + * Buffered RS-232-C Interface Module. Note that the original 2200/2201 + * receivers will not work reliably with this driver, since the older + * design cannot accept input commands at any reasonable data rate. + * + * The program sends a "*toc\r" to the radio and expects a response of + * the form "yy:ddd:hh:mm:ss.mmm\r" where yy = year of century, ddd = + * day of year, hh:mm:ss = second of day and mmm = millisecond of + * second. Then, it sends statistics commands to the radio and expects + * a multi-line reply showing the corresponding statistics or other + * selected data. Statistics commands are sent in order as determined by + * a vector of commands; these might have to be changed with different + * radio options. If flag4 of the fudge configuration command is set to + * 1, the statistics data are written to the clockstats file for later + * processing. + * + * In order for this code to work, the radio must be placed in non- + * interactive mode using the "off" command and with a single <cr> + * response using the "term cr" command. The setting of the "echo" + * and "df" commands does not matter. The radio should select UTC + * timescale using the "ts utc" command. + * + * There are two modes of operation for this driver. The first with + * default configuration is used with stock kernels and serial-line + * drivers and works with almost any machine. In this mode the driver + * assumes the radio captures a timestamp upon receipt of the "*" that + * begins the driver query. Accuracies in this mode are in the order of + * a millisecond or two and the receiver can be connected to only one + * host. + * + * The second mode of operation can be used for SunOS kernels that have + * been modified with the ppsclock streams module included in this + * distribution. The mode is enabled if flag3 of the fudge configuration + * command has been set to 1. In this mode a precise timestamp is + * available using a gadget box and 1-pps signal from the receiver. This + * improves the accuracy to the order of a few tens of microseconds. In + * addition, the serial output and 1-pps signal can be bussed to more + * than one hosts, but only one of them should be connected to the + * radio input data line. + */ + +/* + * GPS Definitions + */ +#define SMAX 200 /* statistics buffer length */ +#define DEVICE "/dev/gps%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference ID */ +#define DESCRIPTION "Austron 2201A GPS Receiver" /* WRU */ + +#define LENTOC 19 /* yy:ddd:hh:mm:ss.mmm timecode lngth */ + +/* + * AS2201 unit control structure. + */ +struct as2201unit { + char *lastptr; /* statistics buffer pointer */ + char stats[SMAX]; /* statistics buffer */ + int linect; /* count of lines remaining */ + int index; /* current statistics command */ +}; + +/* + * Radio commands to extract statitistics + * + * A command consists of an ASCII string terminated by a <cr> (\r). The + * command list consist of a sequence of commands terminated by a null + * string ("\0"). One command from the list is sent immediately + * following each received timecode (*toc\r command) and the ASCII + * strings received from the radio are saved along with the timecode in + * the clockstats file. Subsequent commands are sent at each timecode, + * with the last one in the list followed by the first one. The data + * received from the radio consist of ASCII strings, each terminated by + * a <cr> (\r) character. The number of strings for each command is + * specified as the first line of output as an ASCII-encode number. Note + * that the ETF command requires the Input Buffer Module and the LORAN + * commands require the LORAN Assist Module. However, if these modules + * are not installed, the radio and this driver will continue to operate + * successfuly, but no data will be captured for these commands. + */ +static char stat_command[][30] = { + "ITF\r", /* internal time/frequency */ + "ETF\r", /* external time/frequency */ + "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ + "LORAN TDATA\r", /* LORAN signal data */ + "ID;OPT;VER\r", /* model; options; software version */ + + "ITF\r", /* internal time/frequency */ + "ETF\r", /* external time/frequency */ + "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ + "TRSTAT\r", /* satellite tracking status */ + "POS;PPS;PPSOFF\r", /* position, pps source, offsets */ + + "ITF\r", /* internal time/frequency */ + "ETF\r", /* external time/frequency */ + "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ + "LORAN TDATA\r", /* LORAN signal data */ + "UTC\r", /* UTC leap info */ + + "ITF\r", /* internal time/frequency */ + "ETF\r", /* external time/frequency */ + "LORAN ENSEMBLE\r", /* GPS/LORAN ensemble statistics */ + "TRSTAT\r", /* satellite tracking status */ + "OSC;ET;TEMP\r", /* osc type; tune volts; oven temp */ + "\0" /* end of table */ +}; + +/* + * Function prototypes + */ +static int as2201_start P((int, struct peer *)); +static void as2201_shutdown P((int, struct peer *)); +static void as2201_receive P((struct recvbuf *)); +static void as2201_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_as2201 = { + as2201_start, /* start up driver */ + as2201_shutdown, /* shut down driver */ + as2201_poll, /* transmit poll message */ + noentry, /* not used (old as2201_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old as2201_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * as2201_start - open the devices and initialize data for processing + */ +static int +as2201_start( + int unit, + struct peer *peer + ) +{ + register struct as2201unit *up; + struct refclockproc *pp; + int fd; + char gpsdev[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(gpsdev, DEVICE, unit); + if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct as2201unit *) + emalloc(sizeof(struct as2201unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct as2201unit)); + pp = peer->procptr; + pp->io.clock_recv = as2201_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + peer->burst = NSTAGE; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->lastptr = up->stats; + up->index = 0; + return (1); +} + + +/* + * as2201_shutdown - shut down the clock + */ +static void +as2201_shutdown( + int unit, + struct peer *peer + ) +{ + register struct as2201unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct as2201unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * as2201__receive - receive data from the serial interface + */ +static void +as2201_receive( + struct recvbuf *rbufp + ) +{ + register struct as2201unit *up; + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + + /* + * Initialize pointers and read the timecode and timestamp. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct as2201unit *)pp->unitptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); +#ifdef DEBUG + if (debug) + printf("gps: timecode %d %d %s\n", + up->linect, pp->lencode, pp->a_lastcode); +#endif + if (pp->lencode == 0) + return; + + /* + * If linect is greater than zero, we must be in the middle of a + * statistics operation, so simply tack the received data at the + * end of the statistics string. If not, we could either have + * just received the timecode itself or a decimal number + * indicating the number of following lines of the statistics + * reply. In the former case, write the accumulated statistics + * data to the clockstats file and continue onward to process + * the timecode; in the later case, save the number of lines and + * quietly return. + */ + if (pp->sloppyclockflag & CLK_FLAG2) + pp->lastrec = trtmp; + if (up->linect > 0) { + up->linect--; + if ((int)(up->lastptr - up->stats + pp->lencode) > SMAX - 2) + return; + *up->lastptr++ = ' '; + (void)strcpy(up->lastptr, pp->a_lastcode); + up->lastptr += pp->lencode; + return; + } else { + if (pp->lencode == 1) { + up->linect = atoi(pp->a_lastcode); + return; + } else { + record_clock_stats(&peer->srcadr, up->stats); +#ifdef DEBUG + if (debug) + printf("gps: stat %s\n", up->stats); +#endif + } + } + up->lastptr = up->stats; + *up->lastptr = '\0'; + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + if (pp->lencode < LENTOC) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Timecode format: "yy:ddd:hh:mm:ss.mmm" + */ + if (sscanf(pp->a_lastcode, "%2d:%3d:%2d:%2d:%2d.%3ld", &pp->year, + &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->nsec) + != 6) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + pp->nsec *= 1000000; + + /* + * Test for synchronization (this is a temporary crock). + */ + if (pp->a_lastcode[2] != ':') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * If CLK_FLAG4 is set, initialize the statistics buffer and + * send the next command. If not, simply write the timecode to + * the clockstats file. + */ + (void)strcpy(up->lastptr, pp->a_lastcode); + up->lastptr += pp->lencode; + if (pp->sloppyclockflag & CLK_FLAG4) { + *up->lastptr++ = ' '; + (void)strcpy(up->lastptr, stat_command[up->index]); + up->lastptr += strlen(stat_command[up->index]); + up->lastptr--; + *up->lastptr = '\0'; + (void)write(pp->io.fd, stat_command[up->index], + strlen(stat_command[up->index])); + up->index++; + if (*stat_command[up->index] == '\0') + up->index = 0; + } +} + + +/* + * as2201_poll - called by the transmit procedure + * + * We go to great pains to avoid changing state here, since there may be + * more than one eavesdropper receiving the same timecode. + */ +static void +as2201_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + + /* + * Send a "\r*toc\r" to get things going. We go to great pains + * to avoid changing state, since there may be more than one + * eavesdropper watching the radio. + */ + pp = peer->procptr; + if (write(pp->io.fd, "\r*toc\r", 6) != 6) { + refclock_report(peer, CEVNT_FAULT); + } else { + pp->polls++; + if (!(pp->sloppyclockflag & CLK_FLAG2)) + get_systime(&pp->lastrec); + } + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + refclock_receive(peer); + peer->burst = NSTAGE; +} + +#else +int refclock_as2201_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_atom.c b/ntpd/refclock_atom.c new file mode 100644 index 0000000..51153ae --- /dev/null +++ b/ntpd/refclock_atom.c @@ -0,0 +1,504 @@ + +/* + * refclock_atom - clock driver for 1-pps signals + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <ctype.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#if defined(REFCLOCK) && defined(CLOCK_ATOM) + +#ifdef HAVE_PPSAPI +# ifdef HAVE_TIMEPPS_H +# include <timepps.h> +# else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +# endif +#endif /* HAVE_PPSAPI */ + +/* + * This driver furnishes an interface for pulse-per-second (PPS) signals + * produced by a cesium clock, timing receiver or related equipment. It + * can be used to remove accumulated jitter and retime a secondary + * server when synchronized to a primary server over a congested, wide- + * area network and before redistributing the time to local clients. + * + * Before this driver becomes active, the local clock must be set to + * within +-500 ms by another means, such as a radio clock or NTP + * itself. There are two ways to connect the PPS signal, normally at TTL + * levels, to the computer. One is to shift to EIA levels and connect to + * pin 8 (DCD) of a serial port. This requires a level converter and + * may require a one-shot flipflop to lengthen the pulse. The other is + * to connect the PPS signal directly to pin 10 (ACK) of a PC paralell + * port. These methods are architecture dependent. + * + * Both methods require a modified device driver and kernel interface + * compatible with the Pulse-per-Second API for Unix-like Operating + * Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are + * available for FreeBSD, Linux, SunOS, Solaris and Alpha. However, at + * present only the Alpha implementation provides the full generality of + * the API with multiple PPS drivers and multiple handles per driver. + * + * In many configurations a single port is used for the radio timecode + * and PPS signal. In order to provide for this configuration and others + * involving dedicated multiple serial/parallel ports, the driver first + * attempts to open the device /dev/pps%d, where %d is the unit number. + * If this fails, the driver attempts to open the device specified by + * the pps configuration command. If a port is to be shared, the pps + * command must be placed before the radio device(s) and the radio + * device(s) must be placed before the PPS driver(s) in the + * configuration file. + * + * This driver normally uses the PLL/FLL clock discipline implemented in + * the ntpd code. If kernel support is available, the kernel PLL/FLL + * clock discipline is used instead. The default configuration is not to + * use the kernel PPS discipline, if present. The kernel PPS discipline + * can be enabled using the pps command. + * + * Fudge Factors + * + * There are no special fudge factors other than the generic. The fudge + * time1 parameter can be used to compensate for miscellaneous device + * driver and OS delays. + */ +/* + * Interface definitions + */ +#ifdef HAVE_PPSAPI +#define DEVICE "/dev/pps%d" /* device name and unit */ +#endif /* HAVE_PPSAPI */ + +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "PPS\0" /* reference ID */ +#define DESCRIPTION "PPS Clock Discipline" /* WRU */ +#define NANOSECOND 1000000000 /* one second (ns) */ +#define RANGEGATE 500000 /* range gate (ns) */ +#define ASTAGE 8 /* filter stages */ + +static struct peer *pps_peer; /* atom driver for PPS sources */ + +#ifdef HAVE_PPSAPI +/* + * PPS unit control structure + */ +struct ppsunit { + struct timespec ts; /* last timestamp */ + int fddev; /* pps device descriptor */ + pps_params_t pps_params; /* pps parameters */ + pps_info_t pps_info; /* last pps data */ + pps_handle_t handle; /* pps handlebars */ +}; +#endif /* HAVE_PPSAPI */ + +/* + * Function prototypes + */ +static int atom_start P((int, struct peer *)); +static void atom_poll P((int, struct peer *)); +#ifdef HAVE_PPSAPI +static void atom_shutdown P((int, struct peer *)); +static void atom_control P((int, struct refclockstat *, struct + refclockstat *, struct peer *)); +static int atom_pps P((struct peer *)); +static int atom_ppsapi P((struct peer *, int, int)); +#endif /* HAVE_PPSAPI */ + +/* + * Transfer vector + */ +struct refclock refclock_atom = { + atom_start, /* start up driver */ +#ifdef HAVE_PPSAPI + atom_shutdown, /* shut down driver */ +#else + noentry, /* shut down driver */ +#endif /* HAVE_PPSAPI */ + atom_poll, /* transmit poll message */ +#ifdef HAVE_PPSAPI + atom_control, /* fudge control */ +#else + noentry, /* fudge control */ +#endif /* HAVE_PPSAPI */ + noentry, /* initialize driver */ + noentry, /* not used (old atom_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * atom_start - initialize data for processing + */ +static int +atom_start( + int unit, /* unit number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; +#ifdef HAVE_PPSAPI + register struct ppsunit *up; + char device[80]; +#endif /* HAVE_PPSAPI */ + + /* + * Allocate and initialize unit structure + */ + pps_peer = peer; + pp = peer->procptr; + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + pp->stratum = STRATUM_UNSPEC; + memcpy((char *)&pp->refid, REFID, 4); + peer->burst = ASTAGE; +#ifdef HAVE_PPSAPI + up = emalloc(sizeof(struct ppsunit)); + memset(up, 0, sizeof(struct ppsunit)); + pp->unitptr = (caddr_t)up; + + /* + * Open PPS device. If this fails and some driver has already + * opened the associated radio device, fdpps has the file + * descriptor for it. + */ + sprintf(device, DEVICE, unit); + up->fddev = open(device, O_RDWR, 0777); + if (up->fddev <= 0 && fdpps > 0) { + strcpy(device, pps_device); + up->fddev = fdpps; + } + if (up->fddev <= 0) { + msyslog(LOG_ERR, + "refclock_atom: %s: %m", device); + return (0); + } + + /* + * Light off the PPSAPI interface. If this PPS device is shared + * with the radio device, take the default options from the pps + * command. This is for legacy purposes. + */ + if (time_pps_create(up->fddev, &up->handle) < 0) { + msyslog(LOG_ERR, + "refclock_atom: time_pps_create failed: %m"); + return (0); + } + return (atom_ppsapi(peer, 0, 0)); +#else /* HAVE_PPSAPI */ + return (1); +#endif /* HAVE_PPSAPI */ +} + + +#ifdef HAVE_PPSAPI +/* + * atom_control - fudge control + */ +static void +atom_control( + int unit, /* unit (not used */ + struct refclockstat *in, /* input parameters (not uded) */ + struct refclockstat *out, /* output parameters (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + atom_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2, + pp->sloppyclockflag & CLK_FLAG3); +} + + +/* + * Initialize PPSAPI + */ +int +atom_ppsapi( + struct peer *peer, /* peer structure pointer */ + int enb_clear, /* clear enable */ + int enb_hardpps /* hardpps enable */ + ) +{ + struct refclockproc *pp; + register struct ppsunit *up; + int capability; + + pp = peer->procptr; + up = (struct ppsunit *)pp->unitptr; + if (time_pps_getcap(up->handle, &capability) < 0) { + msyslog(LOG_ERR, + "refclock_atom: time_pps_getcap failed: %m"); + return (0); + } + memset(&up->pps_params, 0, sizeof(pps_params_t)); + if (enb_clear) + up->pps_params.mode = capability & PPS_CAPTURECLEAR; + else + up->pps_params.mode = capability & PPS_CAPTUREASSERT; + if (!up->pps_params.mode) { + msyslog(LOG_ERR, + "refclock_atom: invalid capture edge %d", + enb_clear); + return (0); + } + up->pps_params.mode |= PPS_TSFMT_TSPEC; + if (time_pps_setparams(up->handle, &up->pps_params) < 0) { + msyslog(LOG_ERR, + "refclock_atom: time_pps_setparams failed: %m"); + return (0); + } + if (enb_hardpps) { + if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS, + up->pps_params.mode & ~PPS_TSFMT_TSPEC, + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, + "refclock_atom: time_pps_kcbind failed: %m"); + return (0); + } + pps_enable = 1; + } +#if DEBUG + if (debug) { + time_pps_getparams(up->handle, &up->pps_params); + printf( + "refclock_ppsapi: fd %d capability 0x%x version %d mode 0x%x kern %d\n", + up->fddev, capability, up->pps_params.api_version, + up->pps_params.mode, enb_hardpps); + } +#endif + return (1); +} + + +/* + * atom_shutdown - shut down the clock + */ +static void +atom_shutdown( + int unit, /* unit number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + register struct ppsunit *up; + + pp = peer->procptr; + up = (struct ppsunit *)pp->unitptr; + if (up->fddev > 0) + close(up->fddev); + if (up->handle != 0) + time_pps_destroy(up->handle); + if (pps_peer == peer) + pps_peer = 0; + free(up); +} + + +/* + * atom_pps - receive data from the PPSAPI interface + * + * This routine is called once per second when the PPSAPI interface is + * present. It snatches the PPS timestamp from the kernel and saves the + * sign-extended fraction in a circular buffer for processing at the + * next poll event. + */ +static int +atom_pps( + struct peer *peer /* peer structure pointer */ + ) +{ + register struct ppsunit *up; + struct refclockproc *pp; + pps_info_t pps_info; + struct timespec timeout, ts; + double dtemp; + + /* + * Convert the timespec nanoseconds field to signed double and + * save in the median filter. for billboards. No harm is done if + * previous data are overwritten. If the discipline comes bum or + * the data grow stale, just forget it. A range gate rejects new + * samples if less than a jiggle time from the next second. + */ + pp = peer->procptr; + up = (struct ppsunit *)pp->unitptr; + if (up->handle == 0) + return (-1); + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t)); + if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info, + &timeout) < 0) + return (-1); + if (up->pps_params.mode & PPS_CAPTUREASSERT) { + if (pps_info.assert_sequence == + up->pps_info.assert_sequence) + return (1); + ts = up->pps_info.assert_timestamp; + } else if (up->pps_params.mode & PPS_CAPTURECLEAR) { + if (pps_info.clear_sequence == + up->pps_info.clear_sequence) + return (1); + ts = up->pps_info.clear_timestamp; + } else { + return (-1); + } + if (!((ts.tv_sec == up->ts.tv_sec && ts.tv_nsec - + up->ts.tv_nsec > NANOSECOND - RANGEGATE) || + (ts.tv_sec - up->ts.tv_sec == 1 && ts.tv_nsec - + up->ts.tv_nsec < RANGEGATE))) { + up->ts = ts; + return (1); + } + up->ts = ts; + pp->lastrec.l_ui = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec * FRAC / 1e9; + if (dtemp >= FRAC) + pp->lastrec.l_ui++; + pp->lastrec.l_uf = (u_int32)dtemp; + if (ts.tv_nsec > NANOSECOND / 2) + ts.tv_nsec -= NANOSECOND; + dtemp = -(double)ts.tv_nsec / NANOSECOND; + SAMPLE(dtemp + pp->fudgetime1); +#ifdef DEBUG + if (debug > 1) + printf("atom_pps %f %f\n", dtemp, pp->fudgetime1); +#endif + return (0); +} +#endif /* HAVE_PPSAPI */ + + +/* + * pps_sample - receive PPS data from some other clock driver + * + * This routine is called once per second when the external clock driver + * processes PPS information. It processes the PPS timestamp and saves + * the sign-extended fraction in a circular buffer for processing at the + * next poll event. This works only for a single PPS device. + */ +int +pps_sample( + l_fp *offset /* PPS offset */ + ) +{ + register struct peer *peer; + struct refclockproc *pp; + l_fp lftmp; + double doffset; + + peer = pps_peer; + if (peer == 0) /* nobody home */ + return (1); + pp = peer->procptr; + + /* + * Convert the timeval to l_fp and save for billboards. Sign- + * extend the fraction and stash in the buffer. No harm is done + * if previous data are overwritten. If the discipline comes bum + * or the data grow stale, just forget it. + */ + pp->lastrec = *offset; + L_CLR(&lftmp); + L_ADDF(&lftmp, pp->lastrec.l_f); + LFPTOD(&lftmp, doffset); + SAMPLE(-doffset + pp->fudgetime1); + return (0); +} + +/* + * atom_poll - called by the transmit procedure + * + * This routine is called once per second when in burst mode to save PPS + * sample offsets in the median filter. At the end of the burst period + * the samples are processed as a heap and the clock filter updated. + */ +static void +atom_poll( + int unit, /* unit number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; +#ifdef HAVE_PPSAPI + int err; +#endif /* HAVE_PPSAPI */ + + /* + * Accumulate samples in the median filter. If a noise sample, + * return with no prejudice; if a protocol error, get mean; + * otherwise, cool. At the end of each poll interval, do a + * little bookeeping and process the surviving samples. + */ + pp = peer->procptr; + pp->polls++; +#ifdef HAVE_PPSAPI + err = atom_pps(peer); + if (err < 0) { + refclock_report(peer, CEVNT_FAULT); + return; + } +#endif /* HAVE_PPSAPI */ + + /* + * Valid time is returned only if the prefer peer has survived + * the intersection algorithm and within clock_max of local time + * and not too long ago. This ensures the PPS time is within + * +-0.5 s of the local time and the seconds numbering is + * unambiguous. Note that the leap bits are set no-warning on + * the first valid update and the stratum is set at the prefer + * peer, unless overriden by a fudge command. + */ + if (peer->burst > 0) + return; + peer->leap = LEAP_NOTINSYNC; + if (pp->codeproc == pp->coderecv) { + refclock_report(peer, CEVNT_TIMEOUT); + peer->burst = ASTAGE; + return; + + } else if (sys_prefer == NULL) { + pp->codeproc = pp->coderecv; + peer->burst = ASTAGE; + return; + + } else if (fabs(sys_prefer->offset) > clock_max) { + pp->codeproc = pp->coderecv; + peer->burst = ASTAGE; + return; + } + pp->leap = LEAP_NOWARNING; + if (pp->stratum >= STRATUM_UNSPEC) + peer->stratum = sys_prefer->stratum; + else + peer->stratum = pp->stratum; + if (peer->stratum == STRATUM_REFCLOCK || peer->stratum == + STRATUM_UNSPEC) + peer->refid = pp->refid; + else + peer->refid = addr2refid(&sys_prefer->srcadr); + pp->lastref = pp->lastrec; + refclock_receive(peer); + peer->burst = ASTAGE; +} +#else +int refclock_atom_bs; +int +pps_sample( + l_fp *offset /* PPS offset */ + ) +{ + return 1; +} +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_bancomm.c b/ntpd/refclock_bancomm.c new file mode 100644 index 0000000..a63be44 --- /dev/null +++ b/ntpd/refclock_bancomm.c @@ -0,0 +1,433 @@ +/* refclock_bancomm.c - clock driver for the Datum/Bancomm bc635VME + * Time and Frequency Processor. It requires the BANCOMM bc635VME/ + * bc350VXI Time and Frequency Processor Module Driver for SunOS4.x + * and SunOS5.x UNIX Systems. It has been tested on a UltraSparc + * IIi-cEngine running Solaris 2.6. + * + * Author(s): Ganesh Ramasivan & Gary Cliff, Computing Devices Canada, + * Ottawa, Canada + * + * Date: July 1999 + * + * Note(s): The refclock type has been defined as 16. + * + * This program has been modelled after the Bancomm driver + * originally written by R. Schmidt of Time Service, U.S. + * Naval Observatory for a HP-UX machine. Since the original + * authors no longer plan to maintain this code, all + * references to the HP-UX vme2 driver subsystem bave been + * removed. Functions vme_report_event(), vme_receive(), + * vme_control() and vme_buginfo() have been deleted because + * they are no longer being used. + * + * The time on the bc635 TFP must be set to GMT due to the + * fact that NTP makes use of GMT for all its calculations. + * + * Installation of the Datum/Bancomm driver creates the + * device file /dev/btfp0 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_BANC) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <syslog.h> +#include <ctype.h> + +/* STUFF BY RES */ +struct btfp_time /* Structure for reading 5 time words */ + /* in one ioctl(2) operation. */ +{ + unsigned short btfp_time[5]; /* Time words 0,1,2,3, and 4. (16bit)*/ +}; + +/* SunOS5 ioctl commands definitions.*/ +#define BTFPIOC ( 'b'<< 8 ) +#define IOCIO( l, n ) ( BTFPIOC | n ) +#define IOCIOR( l, n, s ) ( BTFPIOC | n ) +#define IOCIORN( l, n, s ) ( BTFPIOC | n ) +#define IOCIOWN( l, n, s ) ( BTFPIOC | n ) + +/***** Simple ioctl commands *****/ +#define RUNLOCK IOCIOR(b, 19, int ) /* Release Capture Lockout */ +#define RCR0 IOCIOR(b, 22, int ) /* Read control register zero.*/ +#define WCR0 IOCIOWN(b, 23, int) /* Write control register zero*/ + +/***** Compound ioctl commands *****/ + +/* Read all 5 time words in one call. */ +#define READTIME IOCIORN(b, 32, sizeof( struct btfp_time )) +#define VMEFD "/dev/btfp0" + +struct vmedate { /* structure returned by get_vmetime.c */ + unsigned short year; + unsigned short day; + unsigned short hr; + unsigned short mn; + unsigned short sec; + unsigned long frac; + unsigned short status; +}; + +/* END OF STUFF FROM RES */ + +/* + * VME interface parameters. + */ +#define VMEPRECISION (-21) /* precision assumed (1 us) */ +#define USNOREFID "BTFP" /* or whatever */ +#define VMEREFID "BTFP" /* reference id */ +#define VMEDESCRIPTION "Bancomm bc635 TFP" /* who we are */ +#define VMEHSREFID 0x7f7f1000 /* 127.127.16.00 refid hi strata */ +/* clock type 16 is used here */ +#define GMT 0 /* hour offset from Greenwich */ + +/* + * Imported from ntp_timer module + */ +extern u_long current_time; /* current time(s) */ + +/* + * Imported from ntpd module + */ +extern int debug; /* global debug flag */ + +/* + * VME unit control structure. + * Changes made to vmeunit structure. Most members are now available in the + * new refclockproc structure in ntp_refclock.h - 07/99 - Ganesh Ramasivan + */ +struct vmeunit { + struct vmedate vmedata; /* data returned from vme read */ + u_long lasttime; /* last time clock heard from */ +}; + +/* + * Function prototypes + */ +static void vme_init (void); +static int vme_start (int, struct peer *); +static void vme_shutdown (int, struct peer *); +static void vme_receive (struct recvbuf *); +static void vme_poll (int unit, struct peer *); +struct vmedate *get_datumtime(struct vmedate *); + +/* + * Transfer vector + */ +struct refclock refclock_bancomm = { + vme_start, /* start up driver */ + vme_shutdown, /* shut down driver */ + vme_poll, /* transmit poll message */ + noentry, /* not used (old vme_control) */ + noentry, /* initialize driver */ + noentry, /* not used (old vme_buginfo) */ + NOFLAGS /* not used */ +}; + +int fd_vme; /* file descriptor for ioctls */ +int regvalue; + + +/* + * vme_start - open the VME device and initialize data for processing + */ +static int +vme_start( + int unit, + struct peer *peer + ) +{ + register struct vmeunit *vme; + struct refclockproc *pp; + int dummy; + char vmedev[20]; + + /* + * Open VME device + */ +#ifdef DEBUG + + printf("Opening DATUM VME DEVICE \n"); +#endif + if ( (fd_vme = open(VMEFD, O_RDWR)) < 0) { + msyslog(LOG_ERR, "vme_start: failed open of %s: %m", vmedev); + return (0); + } + else { /* Release capture lockout in case it was set from before. */ + if( ioctl( fd_vme, RUNLOCK, &dummy ) ) + msyslog(LOG_ERR, "vme_start: RUNLOCK failed %m"); + + regvalue = 0; /* More esoteric stuff to do... */ + if( ioctl( fd_vme, WCR0, ®value ) ) + msyslog(LOG_ERR, "vme_start: WCR0 failed %m"); + } + + /* + * Allocate unit structure + */ + vme = (struct vmeunit *)emalloc(sizeof(struct vmeunit)); + bzero((char *)vme, sizeof(struct vmeunit)); + + + /* + * Set up the structures + */ + pp = peer->procptr; + pp->unitptr = (caddr_t) vme; + pp->timestarted = current_time; + + pp->io.clock_recv = vme_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd_vme; + + /* + * All done. Initialize a few random peer variables, then + * return success. Note that root delay and root dispersion are + * always zero for this clock. + */ + peer->precision = VMEPRECISION; + memcpy(&pp->refid, USNOREFID,4); + return (1); +} + + +/* + * vme_shutdown - shut down a VME clock + */ +static void +vme_shutdown( + int unit, + struct peer *peer + ) +{ + register struct vmeunit *vme; + struct refclockproc *pp; + + /* + * Tell the I/O module to turn us off. We're history. + */ + pp = peer->procptr; + vme = (struct vmeunit *)pp->unitptr; + io_closeclock(&pp->io); + pp->unitptr = NULL; + free(vme); +} + + +/* + * vme_receive - receive data from the VME device. + * + * Note: This interface would be interrupt-driven. We don't use that + * now, but include a dummy routine for possible future adventures. + */ +static void +vme_receive( + struct recvbuf *rbufp + ) +{ +} + + +/* + * vme_poll - called by the transmit procedure + */ +static void +vme_poll( + int unit, + struct peer *peer + ) +{ + struct vmedate *tptr; + struct vmeunit *vme; + struct refclockproc *pp; + time_t tloc; + struct tm *tadr; + + pp = peer->procptr; + vme = (struct vmeunit *)pp->unitptr; /* Here is the structure */ + + tptr = &vme->vmedata; + if ((tptr = get_datumtime(tptr)) == NULL ) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + get_systime(&pp->lastrec); + pp->polls++; + vme->lasttime = current_time; + + /* + * Get VME time and convert to timestamp format. + * The year must come from the system clock. + */ + + time(&tloc); + tadr = gmtime(&tloc); + tptr->year = (unsigned short)(tadr->tm_year + 1900); + + + sprintf(pp->a_lastcode, + "%3.3d %2.2d:%2.2d:%2.2d.%.6ld %1d", + tptr->day, + tptr->hr, + tptr->mn, + tptr->sec, + tptr->frac, + tptr->status); + + pp->lencode = (u_short) strlen(pp->a_lastcode); + + pp->day = tptr->day; + pp->hour = tptr->hr; + pp->minute = tptr->mn; + pp->second = tptr->sec; + pp->usec = tptr->frac; + +#ifdef DEBUG + if (debug) + printf("pp: %3d %02d:%02d:%02d.%06ld %1x\n", + pp->day, pp->hour, pp->minute, pp->second, + pp->usec, tptr->status); +#endif + if (tptr->status ) { /* Status 0 is locked to ref., 1 is not */ + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Now, compute the reference time value. Use the heavy + * machinery for the seconds and the millisecond field for the + * fraction when present. If an error in conversion to internal + * format is found, the program declares bad data and exits. + * Note that this code does not yet know how to do the years and + * relies on the clock-calendar chip for sanity. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +} + +struct vmedate * +get_datumtime(struct vmedate *time_vme) +{ + unsigned short status; + char cbuf[7]; + struct btfp_time vts; + + if ( time_vme == (struct vmedate *)NULL) { + time_vme = (struct vmedate *)malloc(sizeof(struct vmedate )); + } + + if( ioctl(fd_vme, READTIME, &vts)) + msyslog(LOG_ERR, "get_datumtime error: %m"); + + /* if you want to actually check the validity of these registers, do a + define of CHECK above this. I didn't find it necessary. - RES + */ + +#ifdef CHECK + + /* Get day */ + sprintf(cbuf,"%3.3x", ((vts.btfp_time[ 0 ] & 0x000f) <<8) + + ((vts.btfp_time[ 1 ] & 0xff00) >> 8)); + + if (isdigit(cbuf[0]) && isdigit(cbuf[1]) && isdigit(cbuf[2]) ) + time_vme->day = (unsigned short)atoi(cbuf); + else + time_vme->day = (unsigned short) 0; + + /* Get hour */ + sprintf(cbuf,"%2.2x", vts.btfp_time[ 1 ] & 0x00ff); + + if (isdigit(cbuf[0]) && isdigit(cbuf[1])) + time_vme->hr = (unsigned short)atoi(cbuf); + else + time_vme->hr = (unsigned short) 0; + + /* Get minutes */ + sprintf(cbuf,"%2.2x", (vts.btfp_time[ 2 ] & 0xff00) >>8); + if (isdigit(cbuf[0]) && isdigit(cbuf[1])) + time_vme->mn = (unsigned short)atoi(cbuf); + else + time_vme->mn = (unsigned short) 0; + + /* Get seconds */ + sprintf(cbuf,"%2.2x", vts.btfp_time[ 2 ] & 0x00ff); + + if (isdigit(cbuf[0]) && isdigit(cbuf[1])) + time_vme->sec = (unsigned short)atoi(cbuf); + else + time_vme->sec = (unsigned short) 0; + + /* Get microseconds. Yes, we ignore the 0.1 microsecond digit so we can + use the TVTOTSF function later on...*/ + + sprintf(cbuf,"%4.4x%2.2x", vts.btfp_time[ 3 ], + vts.btfp_time[ 4 ]>>8); + + if (isdigit(cbuf[0]) && isdigit(cbuf[1]) && isdigit(cbuf[2]) + && isdigit(cbuf[3]) && isdigit(cbuf[4]) && isdigit(cbuf[5])) + time_vme->frac = (u_long) atoi(cbuf); + else + time_vme->frac = (u_long) 0; +#else + + /* DONT CHECK just trust the card */ + + /* Get day */ + sprintf(cbuf,"%3.3x", ((vts.btfp_time[ 0 ] & 0x000f) <<8) + + ((vts.btfp_time[ 1 ] & 0xff00) >> 8)); + time_vme->day = (unsigned short)atoi(cbuf); + + /* Get hour */ + sprintf(cbuf,"%2.2x", vts.btfp_time[ 1 ] & 0x00ff); + + time_vme->hr = (unsigned short)atoi(cbuf); + + /* Get minutes */ + sprintf(cbuf,"%2.2x", (vts.btfp_time[ 2 ] & 0xff00) >>8); + time_vme->mn = (unsigned short)atoi(cbuf); + + /* Get seconds */ + sprintf(cbuf,"%2.2x", vts.btfp_time[ 2 ] & 0x00ff); + time_vme->sec = (unsigned short)atoi(cbuf); + + /* Get microseconds. Yes, we ignore the 0.1 microsecond digit so we can + use the TVTOTSF function later on...*/ + + sprintf(cbuf,"%4.4x%2.2x", vts.btfp_time[ 3 ], + vts.btfp_time[ 4 ]>>8); + + time_vme->frac = (u_long) atoi(cbuf); + +#endif /* CHECK */ + + /* Get status bit */ + status = (vts.btfp_time[0] & 0x0010) >>4; + time_vme->status = status; /* Status=0 if locked to ref. */ + /* Status=1 if flywheeling */ + if (status) { /* lost lock ? */ + return ((void *)NULL); + } + else + return (time_vme); +} + +#else +int refclock_bancomm_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_chronolog.c b/ntpd/refclock_chronolog.c new file mode 100644 index 0000000..a1d131e --- /dev/null +++ b/ntpd/refclock_chronolog.c @@ -0,0 +1,341 @@ +/* + * refclock_chronolog - clock driver for Chronolog K-series WWVB receiver. + */ + +/* + * Must interpolate back to local time. Very annoying. + */ +#define GET_LOCALTIME + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_CHRONOLOG) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the Chronolog K-series WWVB receiver. + * + * Input format: + * + * Y YY/MM/DD<cr><lf> + * Z hh:mm:ss<cr><lf> + * + * YY/MM/DD -- what you'd expect. This arrives a few seconds before the + * timestamp. + * hh:mm:ss -- what you'd expect. We take time on the <cr>. + * + * Our Chronolog writes time out at 2400 bps 8/N/1, but it can be configured + * otherwise. The clock seems to appear every 60 seconds, which doesn't make + * for good statistics collection. + * + * The original source of this module was the WWVB module. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/chronolog%d" /* device name and unit */ +#define SPEED232 B2400 /* uart speed (2400 baud) */ +#define PRECISION (-13) /* precision assumed (about 100 us) */ +#define REFID "chronolog" /* reference ID */ +#define DESCRIPTION "Chrono-log K" /* WRU */ + +#define MONLIN 15 /* number of monitoring lines */ + +/* + * Chrono-log unit control structure + */ +struct chronolog_unit { + u_char tcswitch; /* timecode switch */ + l_fp laststamp; /* last receive timestamp */ + u_char lasthour; /* last hour (for monitor) */ + int year; /* Y2K-adjusted year */ + int day; /* day-of-month */ + int month; /* month-of-year */ +}; + +/* + * Function prototypes + */ +static int chronolog_start P((int, struct peer *)); +static void chronolog_shutdown P((int, struct peer *)); +static void chronolog_receive P((struct recvbuf *)); +static void chronolog_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_chronolog = { + chronolog_start, /* start up driver */ + chronolog_shutdown, /* shut down driver */ + chronolog_poll, /* poll the driver -- a nice fabrication */ + noentry, /* not used */ + noentry, /* not used */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + + +/* + * chronolog_start - open the devices and initialize data for processing + */ +static int +chronolog_start( + int unit, + struct peer *peer + ) +{ + register struct chronolog_unit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Don't bother with CLK line discipline, since + * it's not available. + */ + (void)sprintf(device, DEVICE, unit); +#ifdef DEBUG + if (debug) + printf ("starting Chronolog with device %s\n",device); +#endif + if (!(fd = refclock_open(device, SPEED232, 0))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct chronolog_unit *) + emalloc(sizeof(struct chronolog_unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct chronolog_unit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = chronolog_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * chronolog_shutdown - shut down the clock + */ +static void +chronolog_shutdown( + int unit, + struct peer *peer + ) +{ + register struct chronolog_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct chronolog_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * chronolog_receive - receive data from the serial interface + */ +static void +chronolog_receive( + struct recvbuf *rbufp + ) +{ + struct chronolog_unit *up; + struct refclockproc *pp; + struct peer *peer; + + l_fp trtmp; /* arrival timestamp */ + int hours; /* hour-of-day */ + int minutes; /* minutes-past-the-hour */ + int seconds; /* seconds */ + int temp; /* int temp */ + int got_good; /* got a good time flag */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct chronolog_unit *)pp->unitptr; + temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + + if (temp == 0) { + if (up->tcswitch == 0) { + up->tcswitch = 1; + up->laststamp = trtmp; + } else + up->tcswitch = 0; + return; + } + pp->lencode = temp; + pp->lastrec = up->laststamp; + up->laststamp = trtmp; + up->tcswitch = 1; + +#ifdef DEBUG + if (debug) + printf("chronolog: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + /* + * We get down to business. Check the timecode format and decode + * its contents. This code uses the first character to see whether + * we're looking at a date or a time. We store data data across + * calls since it is transmitted a few seconds ahead of the + * timestamp. + */ + got_good=0; + if (sscanf(pp->a_lastcode, "Y %d/%d/%d", &up->year,&up->month,&up->day)) + { + /* + * Y2K convert the 2-digit year + */ + up->year = up->year >= 69 ? up->year : up->year + 100; + return; + } + if (sscanf(pp->a_lastcode,"Z %02d:%02d:%02d", + &hours,&minutes,&seconds) == 3) + { +#ifdef GET_LOCALTIME + struct tm local; + struct tm *gmtp; + time_t unixtime; + int adjyear; + int adjmon; + + /* + * Convert to GMT for sites that distribute localtime. This + * means we have to do Y2K conversion on the 2-digit year; + * otherwise, we get the time wrong. + */ + + local.tm_year = up->year; + local.tm_mon = up->month-1; + local.tm_mday = up->day; + local.tm_hour = hours; + local.tm_min = minutes; + local.tm_sec = seconds; + local.tm_isdst = -1; + + unixtime = mktime (&local); + if ((gmtp = gmtime (&unixtime)) == NULL) + { + refclock_report (peer, CEVNT_FAULT); + return; + } + adjyear = gmtp->tm_year+1900; + adjmon = gmtp->tm_mon+1; + pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday); + pp->hour = gmtp->tm_hour; + pp->minute = gmtp->tm_min; + pp->second = gmtp->tm_sec; +#ifdef DEBUG + if (debug) + printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", + adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute, + pp->second); +#endif + +#else + /* + * For more rational sites distributing UTC + */ + pp->day = ymd2yd(year+1900,month,day); + pp->hour = hours; + pp->minute = minutes; + pp->second = seconds; + +#endif + got_good=1; + } + + if (!got_good) + return; + + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + up->lasthour = pp->hour; +} + + +/* + * chronolog_poll - called by the transmit procedure + */ +static void +chronolog_poll( + int unit, + struct peer *peer + ) +{ + /* + * Time to poll the clock. The Chrono-log clock is supposed to + * respond to a 'T' by returning a timecode in the format(s) + * specified above. Ours does (can?) not, but this seems to be + * an installation-specific problem. This code is dyked out, + * but may be re-enabled if anyone ever finds a Chrono-log that + * actually listens to this command. + */ +#if 0 + register struct chronolog_unit *up; + struct refclockproc *pp; + char pollchar; + + pp = peer->procptr; + up = (struct chronolog_unit *)pp->unitptr; + if (peer->burst == 0 && peer->reach == 0) + refclock_report(peer, CEVNT_TIMEOUT); + if (up->linect > 0) + pollchar = 'R'; + else + pollchar = 'T'; + if (write(pp->io.fd, &pollchar, 1) != 1) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; +#endif +} + +#else +int refclock_chronolog_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_chu.c b/ntpd/refclock_chu.c new file mode 100644 index 0000000..e0c79e2 --- /dev/null +++ b/ntpd/refclock_chu.c @@ -0,0 +1,1687 @@ +/* + * refclock_chu - clock driver for Canadian CHU time/frequency station + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_CHU) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> +#include <math.h> + +#ifdef HAVE_AUDIO +#include "audio.h" +#endif /* HAVE_AUDIO */ + +#define ICOM 1 /* undefine to suppress ICOM code */ + +#ifdef ICOM +#include "icom.h" +#endif /* ICOM */ + +/* + * Audio CHU demodulator/decoder + * + * This driver synchronizes the computer time using data encoded in + * radio transmissions from Canadian time/frequency station CHU in + * Ottawa, Ontario. Transmissions are made continuously on 3330 kHz, + * 7335 kHz and 14670 kHz in upper sideband, compatible AM mode. An + * ordinary shortwave receiver can be tuned manually to one of these + * frequencies or, in the case of ICOM receivers, the receiver can be + * tuned automatically using this program as propagation conditions + * change throughout the day and night. + * + * The driver receives, demodulates and decodes the radio signals when + * connected to the audio codec of a suported workstation hardware and + * operating system. These include Solaris, SunOS, FreeBSD, NetBSD and + * Linux. In this implementation, only one audio driver and codec can be + * supported on a single machine. + * + * The driver can be compiled to use a Bell 103 compatible modem or + * modem chip to receive the radio signal and demodulate the data. + * Alternatively, the driver can be compiled to use the audio codec of + * the Sun workstation or another with compatible audio drivers. In the + * latter case, the driver implements the modem using DSP routines, so + * the radio can be connected directly to either the microphone on line + * input port. In either case, the driver decodes the data using a + * maximum likelihood technique which exploits the considerable degree + * of redundancy available to maximize accuracy and minimize errors. + * + * The CHU time broadcast includes an audio signal compatible with the + * Bell 103 modem standard (mark = 2225 Hz, space = 2025 Hz). It consist + * of nine, ten-character bursts transmitted at 300 bps and beginning + * each second from second 31 to second 39 of the minute. Each character + * consists of eight data bits plus one start bit and two stop bits to + * encode two hex digits. The burst data consist of five characters (ten + * hex digits) followed by a repeat of these characters. In format A, + * the characters are repeated in the same polarity; in format B, the + * characters are repeated in the opposite polarity. + * + * Format A bursts are sent at seconds 32 through 39 of the minute in + * hex digits + * + * 6dddhhmmss6dddhhmmss + * + * The first ten digits encode a frame marker (6) followed by the day + * (ddd), hour (hh in UTC), minute (mm) and the second (ss). Since + * format A bursts are sent during the third decade of seconds the tens + * digit of ss is always 3. The driver uses this to determine correct + * burst synchronization. These digits are then repeated with the same + * polarity. + * + * Format B bursts are sent at second 31 of the minute in hex digits + * + * xdyyyyttaaxdyyyyttaa + * + * The first ten digits encode a code (x described below) followed by + * the DUT1 (d in deciseconds), Gregorian year (yyyy), difference TAI - + * UTC (tt) and daylight time indicator (aa) peculiar to Canada. These + * digits are then repeated with inverted polarity. + * + * The x is coded + * + * 1 Sign of DUT (0 = +) + * 2 Leap second warning. One second will be added. + * 4 Leap second warning. One second will be subtracted. + * 8 Even parity bit for this nibble. + * + * By design, the last stop bit of the last character in the burst + * coincides with 0.5 second. Since characters have 11 bits and are + * transmitted at 300 bps, the last stop bit of the first character + * coincides with 0.5 - 10 * 11/300 = 0.133 second. Depending on the + * UART, character interrupts can vary somewhere between the beginning + * of bit 9 and end of bit 11. These eccentricities can be corrected + * along with the radio propagation delay using fudge time 1. + * + * Debugging aids + * + * The timecode format used for debugging and data recording includes + * data helpful in diagnosing problems with the radio signal and serial + * connections. With debugging enabled (-d on the ntpd command line), + * the driver produces one line for each burst in two formats + * corresponding to format A and B. Following is format A: + * + * n b f s m code + * + * where n is the number of characters in the burst (0-11), b the burst + * distance (0-40), f the field alignment (-1, 0, 1), s the + * synchronization distance (0-16), m the burst number (2-9) and code + * the burst characters as received. Note that the hex digits in each + * character are reversed, so the burst + * + * 10 38 0 16 9 06851292930685129293 + * + * is interpreted as containing 11 characters with burst distance 38, + * field alignment 0, synchronization distance 16 and burst number 9. + * The nibble-swapped timecode shows day 58, hour 21, minute 29 and + * second 39. + * + * When the audio driver is compiled, format A is preceded by + * the current gain (0-255) and relative signal level (0-9999). The + * receiver folume control should be set so that the gain is somewhere + * near the middle of the range 0-255, which results in a signal level + * near 1000. + * + * Following is format B: + * + * n b s code + * + * where n is the number of characters in the burst (0-11), b the burst + * distance (0-40), s the synchronization distance (0-40) and code the + * burst characters as received. Note that the hex digits in each + * character are reversed and the last ten digits inverted, so the burst + * + * 11 40 1091891300ef6e76ecff + * + * is interpreted as containing 11 characters with burst distance 40. + * The nibble-swapped timecode shows DUT1 +0.1 second, year 1998 and TAI + * - UTC 31 seconds. + * + * In addition to the above, the reference timecode is updated and + * written to the clockstats file and debug score after the last burst + * received in the minute. The format is + * + * qq yyyy ddd hh:mm:ss nn dd tt + * + * where qq are the error flags, as described below, yyyy is the year, + * ddd the day, hh:mm:ss the time of day, nn the number of format A + * bursts received during the previous minute, dd the decoding distance + * and tt the number of timestamps. The error flags are cleared after + * every update. + * + * Fudge factors + * + * For accuracies better than the low millisceconds, fudge time1 can be + * set to the radio propagation delay from CHU to the receiver. This can + * be done conviently using the minimuf program. + * + * Fudge flag4 causes the dubugging output described above to be + * recorded in the clockstats file. When the audio driver is compiled, + * fudge flag2 selects the audio input port, where 0 is the mike port + * (default) and 1 is the line-in port. It does not seem useful to + * select the compact disc player port. Fudge flag3 enables audio + * monitoring of the input signal. For this purpose, the monitor gain is + * set to a default value. + * + * The audio codec code is normally compiled in the driver if the + * architecture supports it (HAVE_AUDIO defined), but is used only if + * the link /dev/chu_audio is defined and valid. The serial port code is + * always compiled in the driver, but is used only if the autdio codec + * is not available and the link /dev/chu%d is defined and valid. + * + * The ICOM code is normally compiled in the driver if selected (ICOM + * defined), but is used only if the link /dev/icom%d is defined and + * valid and the mode keyword on the server configuration command + * specifies a nonzero mode (ICOM ID select code). The C-IV speed is + * 9600 bps if the high order 0x80 bit of the mode is zero and 1200 bps + * if one. The C-IV trace is turned on if the debug level is greater + * than one. + */ +/* + * Interface definitions + */ +#define SPEED232 B300 /* uart speed (300 baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "CHU" /* reference ID */ +#define DEVICE "/dev/chu%d" /* device name and unit */ +#define SPEED232 B300 /* UART speed (300 baud) */ +#ifdef ICOM +#define TUNE .001 /* offset for narrow filter (kHz) */ +#define DWELL 5 /* minutes in a probe cycle */ +#define NCHAN 3 /* number of channels */ +#define ISTAGE 3 /* number of integrator stages */ +#endif /* ICOM */ + +#ifdef HAVE_AUDIO +/* + * Audio demodulator definitions + */ +#define SECOND 8000 /* nominal sample rate (Hz) */ +#define BAUD 300 /* modulation rate (bps) */ +#define OFFSET 128 /* companded sample offset */ +#define SIZE 256 /* decompanding table size */ +#define MAXSIG 6000. /* maximum signal level */ +#define MAXCLP 100 /* max clips above reference per s */ +#define LIMIT 1000. /* soft limiter threshold */ +#define AGAIN 6. /* baseband gain */ +#define LAG 10 /* discriminator lag */ +#define DEVICE_AUDIO "/dev/chu_audio" /* device name */ +#define DESCRIPTION "CHU Audio/Modem Receiver" /* WRU */ +#define AUDIO_BUFSIZ 240 /* audio buffer size (30 ms) */ +#else +#define DESCRIPTION "CHU Modem Receiver" /* WRU */ +#endif /* HAVE_AUDIO */ + +/* + * Decoder definitions + */ +#define CHAR (11. / 300.) /* character time (s) */ +#define FUDGE .185 /* offset to first stop bit (s) */ +#define BURST 11 /* max characters per burst */ +#define MINCHAR 9 /* min characters per burst */ +#define MINDIST 28 /* min burst distance (of 40) */ +#define MINBURST 4 /* min bursts in minute */ +#define MINSYNC 8 /* min sync distance (of 16) */ +#define MINSTAMP 20 /* min timestamps (of 60) */ +#define METRIC 50. /* min channel metric */ +#define PANIC 1440 /* panic timeout (m) */ +#define HOLD 30 /* reach hold (m) */ + +/* + * Hex extension codes (>= 16) + */ +#define HEX_MISS 16 /* miss _ */ +#define HEX_SOFT 17 /* soft error * */ +#define HEX_HARD 18 /* hard error = */ + +/* + * Status bits (status) + */ +#define RUNT 0x0001 /* runt burst */ +#define NOISE 0x0002 /* noise burst */ +#define BFRAME 0x0004 /* invalid format B frame sync */ +#define BFORMAT 0x0008 /* invalid format B data */ +#define AFRAME 0x0010 /* invalid format A frame sync */ +#define AFORMAT 0x0020 /* invalid format A data */ +#define DECODE 0x0040 /* invalid data decode */ +#define STAMP 0x0080 /* too few timestamps */ +#define AVALID 0x0100 /* valid A frame */ +#define BVALID 0x0200 /* valid B frame */ +#define INSYNC 0x0400 /* clock synchronized */ + +/* + * Alarm status bits (alarm) + * + * These alarms are set at the end of a minute in which at least one + * burst was received. SYNERR is raised if the AFRAME or BFRAME status + * bits are set during the minute, FMTERR is raised if the AFORMAT or + * BFORMAT status bits are set, DECERR is raised if the DECODE status + * bit is set and TSPERR is raised if the STAMP status bit is set. + */ +#define SYNERR 0x01 /* frame sync error */ +#define FMTERR 0x02 /* data format error */ +#define DECERR 0x04 /* data decoding error */ +#define TSPERR 0x08 /* insufficient data */ + +#ifdef HAVE_AUDIO +/* + * Maximum likelihood UART structure. There are eight of these + * corresponding to the number of phases. + */ +struct surv { + double shift[12]; /* mark register */ + double es_max, es_min; /* max/min envelope signals */ + double dist; /* sample distance */ + int uart; /* decoded character */ +}; +#endif /* HAVE_AUDIO */ + +#ifdef ICOM +/* + * CHU station structure. There are three of these corresponding to the + * three frequencies. + */ +struct xmtr { + double integ[ISTAGE]; /* circular integrator */ + double metric; /* integrator sum */ + int iptr; /* integrator pointer */ + int probe; /* dwells since last probe */ +}; +#endif /* ICOM */ + +/* + * CHU unit control structure + */ +struct chuunit { + u_char decode[20][16]; /* maximum likelihood decoding matrix */ + l_fp cstamp[BURST]; /* character timestamps */ + l_fp tstamp[MAXSTAGE]; /* timestamp samples */ + l_fp timestamp; /* current buffer timestamp */ + l_fp laststamp; /* last buffer timestamp */ + l_fp charstamp; /* character time as a l_fp */ + int errflg; /* error flags */ + int status; /* status bits */ + char ident[5]; /* station ID and channel */ +#ifdef ICOM + int fd_icom; /* ICOM file descriptor */ + int chan; /* data channel */ + int achan; /* active channel */ + int dwell; /* dwell cycle */ + struct xmtr xmtr[NCHAN]; /* station metric */ +#endif /* ICOM */ + + /* + * Character burst variables + */ + int cbuf[BURST]; /* character buffer */ + int ntstamp; /* number of timestamp samples */ + int ndx; /* buffer start index */ + int prevsec; /* previous burst second */ + int burdist; /* burst distance */ + int syndist; /* sync distance */ + int burstcnt; /* format A bursts this minute */ + + /* + * Format particulars + */ + int leap; /* leap/dut code */ + int dut; /* UTC1 correction */ + int tai; /* TAI - UTC correction */ + int dst; /* Canadian DST code */ + +#ifdef HAVE_AUDIO + /* + * Audio codec variables + */ + int fd_audio; /* audio port file descriptor */ + double comp[SIZE]; /* decompanding table */ + int port; /* codec port */ + int gain; /* codec gain */ + int mongain; /* codec monitor gain */ + int clipcnt; /* sample clip count */ + int seccnt; /* second interval counter */ + + /* + * Modem variables + */ + l_fp tick; /* audio sample increment */ + double bpf[9]; /* IIR bandpass filter */ + double disc[LAG]; /* discriminator shift register */ + double lpf[27]; /* FIR lowpass filter */ + double monitor; /* audio monitor */ + double maxsignal; /* signal level */ + int discptr; /* discriminator pointer */ + + /* + * Maximum likelihood UART variables + */ + double baud; /* baud interval */ + struct surv surv[8]; /* UART survivor structures */ + int decptr; /* decode pointer */ + int dbrk; /* holdoff counter */ +#endif /* HAVE_AUDIO */ +}; + +/* + * Function prototypes + */ +static int chu_start P((int, struct peer *)); +static void chu_shutdown P((int, struct peer *)); +static void chu_receive P((struct recvbuf *)); +static void chu_poll P((int, struct peer *)); + +/* + * More function prototypes + */ +static void chu_decode P((struct peer *, int)); +static void chu_burst P((struct peer *)); +static void chu_clear P((struct peer *)); +static void chu_a P((struct peer *, int)); +static void chu_b P((struct peer *, int)); +static int chu_dist P((int, int)); +static double chu_major P((struct peer *)); +#ifdef HAVE_AUDIO +static void chu_uart P((struct surv *, double)); +static void chu_rf P((struct peer *, double)); +static void chu_gain P((struct peer *)); +static void chu_audio_receive P((struct recvbuf *rbufp)); +#endif /* HAVE_AUDIO */ +#ifdef ICOM +static int chu_newchan P((struct peer *, double)); +#endif /* ICOM */ +static void chu_serial_receive P((struct recvbuf *rbufp)); + +/* + * Global variables + */ +static char hexchar[] = "0123456789abcdef_*="; + +#ifdef ICOM +/* + * Note the tuned frequencies are 1 kHz higher than the carrier. CHU + * transmits on USB with carrier so we can use AM and the narrow SSB + * filter. + */ +static double qsy[NCHAN] = {3.330, 7.335, 14.670}; /* freq (MHz) */ +#endif /* ICOM */ + +/* + * Transfer vector + */ +struct refclock refclock_chu = { + chu_start, /* start up driver */ + chu_shutdown, /* shut down driver */ + chu_poll, /* transmit poll message */ + noentry, /* not used (old chu_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old chu_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * chu_start - open the devices and initialize data for processing + */ +static int +chu_start( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct chuunit *up; + struct refclockproc *pp; + char device[20]; /* device name */ + int fd; /* file descriptor */ +#ifdef ICOM + int temp; +#endif /* ICOM */ +#ifdef HAVE_AUDIO + int fd_audio; /* audio port file descriptor */ + int i; /* index */ + double step; /* codec adjustment */ + + /* + * Open audio device. + */ + fd_audio = audio_init(DEVICE_AUDIO, AUDIO_BUFSIZ, unit); +#ifdef DEBUG + if (fd_audio > 0 && debug) + audio_show(); +#endif + + /* + * Open serial port in raw mode. + */ + if (fd_audio > 0) { + fd = fd_audio; + } else { + sprintf(device, DEVICE, unit); + fd = refclock_open(device, SPEED232, LDISC_RAW); + } +#else /* HAVE_AUDIO */ + + /* + * Open serial port in raw mode. + */ + sprintf(device, DEVICE, unit); + fd = refclock_open(device, SPEED232, LDISC_RAW); +#endif /* HAVE_AUDIO */ + if (fd <= 0) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct chuunit *) + emalloc(sizeof(struct chuunit)))) { + close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct chuunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = chu_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + strcpy(up->ident, "CHU"); + memcpy(&peer->refid, up->ident, 4); + DTOLFP(CHAR, &up->charstamp); +#ifdef HAVE_AUDIO + + /* + * The companded samples are encoded sign-magnitude. The table + * contains all the 256 values in the interest of speed. We do + * this even if the audio codec is not available. C'est la lazy. + */ + up->fd_audio = fd_audio; + up->gain = 127; + up->comp[0] = up->comp[OFFSET] = 0.; + up->comp[1] = 1; up->comp[OFFSET + 1] = -1.; + up->comp[2] = 3; up->comp[OFFSET + 2] = -3.; + step = 2.; + for (i = 3; i < OFFSET; i++) { + up->comp[i] = up->comp[i - 1] + step; + up->comp[OFFSET + i] = -up->comp[i]; + if (i % 16 == 0) + step *= 2.; + } + DTOLFP(1. / SECOND, &up->tick); +#endif /* HAVE_AUDIO */ +#ifdef ICOM + temp = 0; +#ifdef DEBUG + if (debug > 1) + temp = P_TRACE; +#endif + if (peer->ttl > 0) { + if (peer->ttl & 0x80) + up->fd_icom = icom_init("/dev/icom", B1200, + temp); + else + up->fd_icom = icom_init("/dev/icom", B9600, + temp); + } + if (up->fd_icom > 0) { + if (chu_newchan(peer, 0) != 0) { + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "icom: radio not found"); + up->errflg = CEVNT_FAULT; + close(up->fd_icom); + up->fd_icom = 0; + } else { + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "icom: autotune enabled"); + } + } +#endif /* ICOM */ + return (1); +} + + +/* + * chu_shutdown - shut down the clock + */ +static void +chu_shutdown( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct chuunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + if (up == NULL) + return; + + io_closeclock(&pp->io); +#ifdef ICOM + if (up->fd_icom > 0) + close(up->fd_icom); +#endif /* ICOM */ + free(up); +} + + +/* + * chu_receive - receive data from the audio or serial device + */ +static void +chu_receive( + struct recvbuf *rbufp /* receive buffer structure pointer */ + ) +{ +#ifdef HAVE_AUDIO + struct chuunit *up; + struct refclockproc *pp; + struct peer *peer; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * If the audio codec is warmed up, the buffer contains codec + * samples which need to be demodulated and decoded into CHU + * characters using the software UART. Otherwise, the buffer + * contains CHU characters from the serial port, so the software + * UART is bypassed. In this case the CPU will probably run a + * few degrees cooler. + */ + if (up->fd_audio > 0) + chu_audio_receive(rbufp); + else + chu_serial_receive(rbufp); +#else + chu_serial_receive(rbufp); +#endif /* HAVE_AUDIO */ +} + + +#ifdef HAVE_AUDIO +/* + * chu_audio_receive - receive data from the audio device + */ +static void +chu_audio_receive( + struct recvbuf *rbufp /* receive buffer structure pointer */ + ) +{ + struct chuunit *up; + struct refclockproc *pp; + struct peer *peer; + + double sample; /* codec sample */ + u_char *dpt; /* buffer pointer */ + int bufcnt; /* buffer counter */ + l_fp ltemp; /* l_fp temp */ + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Main loop - read until there ain't no more. Note codec + * samples are bit-inverted. + */ + DTOLFP((double)rbufp->recv_length / SECOND, <emp); + L_SUB(&rbufp->recv_time, <emp); + up->timestamp = rbufp->recv_time; + dpt = rbufp->recv_buffer; + for (bufcnt = 0; bufcnt < rbufp->recv_length; bufcnt++) { + sample = up->comp[~*dpt++ & 0xff]; + + /* + * Clip noise spikes greater than MAXSIG. If no clips, + * increase the gain a tad; if the clips are too high, + * decrease a tad. + */ + if (sample > MAXSIG) { + sample = MAXSIG; + up->clipcnt++; + } else if (sample < -MAXSIG) { + sample = -MAXSIG; + up->clipcnt++; + } + chu_rf(peer, sample); + L_ADD(&up->timestamp, &up->tick); + + /* + * Once each second ride gain. + */ + up->seccnt = (up->seccnt + 1) % SECOND; + if (up->seccnt == 0) { + pp->second = (pp->second + 1) % 60; + chu_gain(peer); + } + } + + /* + * Set the input port and monitor gain for the next buffer. + */ + if (pp->sloppyclockflag & CLK_FLAG2) + up->port = 2; + else + up->port = 1; + if (pp->sloppyclockflag & CLK_FLAG3) + up->mongain = MONGAIN; + else + up->mongain = 0; +} + + +/* + * chu_rf - filter and demodulate the FSK signal + * + * This routine implements a 300-baud Bell 103 modem with mark 2225 Hz + * and space 2025 Hz. It uses a bandpass filter followed by a soft + * limiter, FM discriminator and lowpass filter. A maximum likelihood + * decoder samples the baseband signal at eight times the baud rate and + * detects the start bit of each character. + * + * The filters are built for speed, which explains the rather clumsy + * code. Hopefully, the compiler will efficiently implement the move- + * and-muiltiply-and-add operations. + */ +static void +chu_rf( + struct peer *peer, /* peer structure pointer */ + double sample /* analog sample */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + struct surv *sp; + + /* + * Local variables + */ + double signal; /* bandpass signal */ + double limit; /* limiter signal */ + double disc; /* discriminator signal */ + double lpf; /* lowpass signal */ + double span; /* UART signal span */ + double dist; /* UART signal distance */ + int i, j; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Bandpass filter. 4th-order elliptic, 500-Hz bandpass centered + * at 2125 Hz. Passband ripple 0.3 dB, stopband ripple 50 dB. + */ + signal = (up->bpf[8] = up->bpf[7]) * 5.844676e-01; + signal += (up->bpf[7] = up->bpf[6]) * 4.884860e-01; + signal += (up->bpf[6] = up->bpf[5]) * 2.704384e+00; + signal += (up->bpf[5] = up->bpf[4]) * 1.645032e+00; + signal += (up->bpf[4] = up->bpf[3]) * 4.644557e+00; + signal += (up->bpf[3] = up->bpf[2]) * 1.879165e+00; + signal += (up->bpf[2] = up->bpf[1]) * 3.522634e+00; + signal += (up->bpf[1] = up->bpf[0]) * 7.315738e-01; + up->bpf[0] = sample - signal; + signal = up->bpf[0] * 6.176213e-03 + + up->bpf[1] * 3.156599e-03 + + up->bpf[2] * 7.567487e-03 + + up->bpf[3] * 4.344580e-03 + + up->bpf[4] * 1.190128e-02 + + up->bpf[5] * 4.344580e-03 + + up->bpf[6] * 7.567487e-03 + + up->bpf[7] * 3.156599e-03 + + up->bpf[8] * 6.176213e-03; + + up->monitor = signal / 4.; /* note monitor after filter */ + + /* + * Soft limiter/discriminator. The 11-sample discriminator lag + * interval corresponds to three cycles of 2125 Hz, which + * requires the sample frequency to be 2125 * 11 / 3 = 7791.7 + * Hz. The discriminator output varies +-0.5 interval for input + * frequency 2025-2225 Hz. However, we don't get to sample at + * this frequency, so the discriminator output is biased. Life + * at 8000 Hz sucks. + */ + limit = signal; + if (limit > LIMIT) + limit = LIMIT; + else if (limit < -LIMIT) + limit = -LIMIT; + disc = up->disc[up->discptr] * -limit; + up->disc[up->discptr] = limit; + up->discptr = (up->discptr + 1 ) % LAG; + if (disc >= 0) + disc = SQRT(disc); + else + disc = -SQRT(-disc); + + /* + * Lowpass filter. Raised cosine, Ts = 1 / 300, beta = 0.1. + */ + lpf = (up->lpf[26] = up->lpf[25]) * 2.538771e-02; + lpf += (up->lpf[25] = up->lpf[24]) * 1.084671e-01; + lpf += (up->lpf[24] = up->lpf[23]) * 2.003159e-01; + lpf += (up->lpf[23] = up->lpf[22]) * 2.985303e-01; + lpf += (up->lpf[22] = up->lpf[21]) * 4.003697e-01; + lpf += (up->lpf[21] = up->lpf[20]) * 5.028552e-01; + lpf += (up->lpf[20] = up->lpf[19]) * 6.028795e-01; + lpf += (up->lpf[19] = up->lpf[18]) * 6.973249e-01; + lpf += (up->lpf[18] = up->lpf[17]) * 7.831828e-01; + lpf += (up->lpf[17] = up->lpf[16]) * 8.576717e-01; + lpf += (up->lpf[16] = up->lpf[15]) * 9.183463e-01; + lpf += (up->lpf[15] = up->lpf[14]) * 9.631951e-01; + lpf += (up->lpf[14] = up->lpf[13]) * 9.907208e-01; + lpf += (up->lpf[13] = up->lpf[12]) * 1.000000e+00; + lpf += (up->lpf[12] = up->lpf[11]) * 9.907208e-01; + lpf += (up->lpf[11] = up->lpf[10]) * 9.631951e-01; + lpf += (up->lpf[10] = up->lpf[9]) * 9.183463e-01; + lpf += (up->lpf[9] = up->lpf[8]) * 8.576717e-01; + lpf += (up->lpf[8] = up->lpf[7]) * 7.831828e-01; + lpf += (up->lpf[7] = up->lpf[6]) * 6.973249e-01; + lpf += (up->lpf[6] = up->lpf[5]) * 6.028795e-01; + lpf += (up->lpf[5] = up->lpf[4]) * 5.028552e-01; + lpf += (up->lpf[4] = up->lpf[3]) * 4.003697e-01; + lpf += (up->lpf[3] = up->lpf[2]) * 2.985303e-01; + lpf += (up->lpf[2] = up->lpf[1]) * 2.003159e-01; + lpf += (up->lpf[1] = up->lpf[0]) * 1.084671e-01; + lpf += up->lpf[0] = disc * 2.538771e-02; + + /* + * Maximum likelihood decoder. The UART updates each of the + * eight survivors and determines the span, slice level and + * tentative decoded character. Valid 11-bit characters are + * framed so that bit 1 and bit 11 (stop bits) are mark and bit + * 2 (start bit) is space. When a valid character is found, the + * survivor with maximum distance determines the final decoded + * character. + */ + up->baud += 1. / SECOND; + if (up->baud > 1. / (BAUD * 8.)) { + up->baud -= 1. / (BAUD * 8.); + sp = &up->surv[up->decptr]; + span = sp->es_max - sp->es_min; + up->maxsignal += (span - up->maxsignal) / 80.; + if (up->dbrk > 0) { + up->dbrk--; + } else if ((sp->uart & 0x403) == 0x401 && span > 1000.) + { + dist = 0; + j = 0; + for (i = 0; i < 8; i++) { + if (up->surv[i].dist > dist) { + dist = up->surv[i].dist; + j = i; + } + } + chu_decode(peer, (up->surv[j].uart >> 2) & + 0xff); + up->dbrk = 80; + } + up->decptr = (up->decptr + 1) % 8; + chu_uart(sp, -lpf * AGAIN); + } +} + + +/* + * chu_uart - maximum likelihood UART + * + * This routine updates a shift register holding the last 11 envelope + * samples. It then computes the slice level and span over these samples + * and determines the tentative data bits and distance. The calling + * program selects over the last eight survivors the one with maximum + * distance to determine the decoded character. + */ +static void +chu_uart( + struct surv *sp, /* survivor structure pointer */ + double sample /* baseband signal */ + ) +{ + double es_max, es_min; /* max/min envelope */ + double slice; /* slice level */ + double dist; /* distance */ + double dtemp; + int i; + + /* + * Save the sample and shift right. At the same time, measure + * the maximum and minimum over all eleven samples. + */ + es_max = -1e6; + es_min = 1e6; + sp->shift[0] = sample; + for (i = 11; i > 0; i--) { + sp->shift[i] = sp->shift[i - 1]; + if (sp->shift[i] > es_max) + es_max = sp->shift[i]; + if (sp->shift[i] < es_min) + es_min = sp->shift[i]; + } + + /* + * Determine the slice level midway beteen the maximum and + * minimum and the span as the maximum less the minimum. Compute + * the distance on the assumption the first and last bits must + * be mark, the second space and the rest either mark or space. + */ + slice = (es_max + es_min) / 2.; + dist = 0; + sp->uart = 0; + for (i = 1; i < 12; i++) { + sp->uart <<= 1; + dtemp = sp->shift[i]; + if (dtemp > slice) + sp->uart |= 0x1; + if (i == 1 || i == 11) { + dist += dtemp - es_min; + } else if (i == 10) { + dist += es_max - dtemp; + } else { + if (dtemp > slice) + dist += dtemp - es_min; + else + dist += es_max - dtemp; + } + } + sp->es_max = es_max; + sp->es_min = es_min; + sp->dist = dist / (11 * (es_max - es_min)); +} +#endif /* HAVE_AUDIO */ + + +/* + * chu_serial_receive - receive data from the serial device + */ +static void +chu_serial_receive( + struct recvbuf *rbufp /* receive buffer structure pointer */ + ) +{ + struct chuunit *up; + struct refclockproc *pp; + struct peer *peer; + + u_char *dpt; /* receive buffer pointer */ + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Initialize pointers and read the timecode and timestamp. + */ + up->timestamp = rbufp->recv_time; + dpt = (u_char *)&rbufp->recv_space; + chu_decode(peer, *dpt); +} + + +/* + * chu_decode - decode the character data + */ +static void +chu_decode( + struct peer *peer, /* peer structure pointer */ + int hexhex /* data character */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + + l_fp tstmp; /* timestamp temp */ + double dtemp; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * If the interval since the last character is greater than the + * longest burst, process the last burst and start a new one. If + * the interval is less than this but greater than two + * characters, consider this a noise burst and reject it. + */ + tstmp = up->timestamp; + if (L_ISZERO(&up->laststamp)) + up->laststamp = up->timestamp; + L_SUB(&tstmp, &up->laststamp); + up->laststamp = up->timestamp; + LFPTOD(&tstmp, dtemp); + if (dtemp > BURST * CHAR) { + chu_burst(peer); + up->ndx = 0; + } else if (dtemp > 2.5 * CHAR) { + up->ndx = 0; + } + + /* + * Append the character to the current burst and append the + * timestamp to the timestamp list. + */ + if (up->ndx < BURST) { + up->cbuf[up->ndx] = hexhex & 0xff; + up->cstamp[up->ndx] = up->timestamp; + up->ndx++; + + } +} + + +/* + * chu_burst - search for valid burst format + */ +static void +chu_burst( + struct peer *peer + ) +{ + struct chuunit *up; + struct refclockproc *pp; + + int i; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Correlate a block of five characters with the next block of + * five characters. The burst distance is defined as the number + * of bits that match in the two blocks for format A and that + * match the inverse for format B. + */ + if (up->ndx < MINCHAR) { + up->status |= RUNT; + return; + } + up->burdist = 0; + for (i = 0; i < 5 && i < up->ndx - 5; i++) + up->burdist += chu_dist(up->cbuf[i], up->cbuf[i + 5]); + + /* + * If the burst distance is at least MINDIST, this must be a + * format A burst; if the value is not greater than -MINDIST, it + * must be a format B burst. If the B burst is perfect, we + * believe it; otherwise, it is a noise burst and of no use to + * anybody. + */ + if (up->burdist >= MINDIST) { + chu_a(peer, up->ndx); + } else if (up->burdist <= -MINDIST) { + chu_b(peer, up->ndx); + } else { + up->status |= NOISE; + return; + } + + /* + * If this is a valid burst, wait a guard time of ten seconds to + * allow for more bursts, then arm the poll update routine to + * process the minute. Don't do this if this is called from the + * timer interrupt routine. + */ + if (peer->outdate != current_time) + peer->nextdate = current_time + 10; +} + + +/* + * chu_b - decode format B burst + */ +static void +chu_b( + struct peer *peer, + int nchar + ) +{ + struct refclockproc *pp; + struct chuunit *up; + + u_char code[11]; /* decoded timecode */ + char tbuf[80]; /* trace buffer */ + l_fp offset; /* timestamp offset */ + int i; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * In a format B burst, a character is considered valid only if + * the first occurrence matches the last occurrence. The burst + * is considered valid only if all characters are valid; that + * is, only if the distance is 40. Note that once a valid frame + * has been found errors are ignored. + */ + sprintf(tbuf, "chuB %04x %2d %2d ", up->status, nchar, + -up->burdist); + for (i = 0; i < nchar; i++) + sprintf(&tbuf[strlen(tbuf)], "%02x", up->cbuf[i]); + if (pp->sloppyclockflag & CLK_FLAG4) + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif + if (up->burdist > -40) { + up->status |= BFRAME; + return; + } + up->status |= BVALID; + + /* + * Convert the burst data to internal format. If this succeeds, + * save the timestamps for later. + */ + for (i = 0; i < 5; i++) { + code[2 * i] = hexchar[up->cbuf[i] & 0xf]; + code[2 * i + 1] = hexchar[(up->cbuf[i] >> + 4) & 0xf]; + } + if (sscanf((char *)code, "%1x%1d%4d%2d%2x", &up->leap, &up->dut, + &pp->year, &up->tai, &up->dst) != 5) { + up->status |= BFORMAT; + return; + } + if (up->leap & 0x8) + up->dut = -up->dut; + offset.l_ui = 31; + offset.l_f = 0; + for (i = 0; i < nchar && i < 10; i++) { + up->tstamp[up->ntstamp] = up->cstamp[i]; + L_SUB(&up->tstamp[up->ntstamp], &offset); + L_ADD(&offset, &up->charstamp); + if (up->ntstamp < MAXSTAGE) + up->ntstamp++; + } +} + + +/* + * chu_a - decode format A burst + */ +static void +chu_a( + struct peer *peer, + int nchar + ) +{ + struct refclockproc *pp; + struct chuunit *up; + + char tbuf[80]; /* trace buffer */ + l_fp offset; /* timestamp offset */ + int val; /* distance */ + int temp; + int i, j, k; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Determine correct burst phase. There are three cases + * corresponding to in-phase, one character early or one + * character late. These cases are distinguished by the position + * of the framing digits x6 at positions 0 and 5 and x3 at + * positions 4 and 9. The correct phase is when the distance + * relative to the framing digits is maximum. The burst is valid + * only if the maximum distance is at least MINSYNC. + */ + up->syndist = k = 0; + val = -16; + for (i = -1; i < 2; i++) { + temp = up->cbuf[i + 4] & 0xf; + if (i >= 0) + temp |= (up->cbuf[i] & 0xf) << 4; + val = chu_dist(temp, 0x63); + temp = (up->cbuf[i + 5] & 0xf) << 4; + if (i + 9 < nchar) + temp |= up->cbuf[i + 9] & 0xf; + val += chu_dist(temp, 0x63); + if (val > up->syndist) { + up->syndist = val; + k = i; + } + } + temp = (up->cbuf[k + 4] >> 4) & 0xf; + if (temp > 9 || k + 9 >= nchar || temp != ((up->cbuf[k + 9] >> + 4) & 0xf)) + temp = 0; +#ifdef HAVE_AUDIO + if (up->fd_audio) + sprintf(tbuf, "chuA %04x %4.0f %2d %2d %2d %2d %1d ", + up->status, up->maxsignal, nchar, up->burdist, k, + up->syndist, temp); + else + sprintf(tbuf, "chuA %04x %2d %2d %2d %2d %1d ", + up->status, nchar, up->burdist, k, up->syndist, + temp); + +#else + sprintf(tbuf, "chuA %04x %2d %2d %2d %2d %1d ", up->status, + nchar, up->burdist, k, up->syndist, temp); +#endif /* HAVE_AUDIO */ + for (i = 0; i < nchar; i++) + sprintf(&tbuf[strlen(tbuf)], "%02x", + up->cbuf[i]); + if (pp->sloppyclockflag & CLK_FLAG4) + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif + if (up->syndist < MINSYNC) { + up->status |= AFRAME; + return; + } + + /* + * A valid burst requires the first seconds number to match the + * last seconds number. If so, the burst timestamps are + * corrected to the current minute and saved for later + * processing. In addition, the seconds decode is advanced from + * the previous burst to the current one. + */ + if (temp != 0) { + pp->second = 30 + temp; + offset.l_ui = 30 + temp; + offset.l_f = 0; + i = 0; + if (k < 0) + offset = up->charstamp; + else if (k > 0) + i = 1; + for (; i < nchar && i < k + 10; i++) { + up->tstamp[up->ntstamp] = up->cstamp[i]; + L_SUB(&up->tstamp[up->ntstamp], &offset); + L_ADD(&offset, &up->charstamp); + if (up->ntstamp < MAXSTAGE) + up->ntstamp++; + } + while (temp > up->prevsec) { + for (j = 15; j > 0; j--) { + up->decode[9][j] = up->decode[9][j - 1]; + up->decode[19][j] = + up->decode[19][j - 1]; + } + up->decode[9][j] = up->decode[19][j] = 0; + up->prevsec++; + } + } + i = -(2 * k); + for (j = 0; j < nchar; j++) { + if (i < 0 || i > 19) { + i += 2; + continue; + } + up->decode[i][up->cbuf[j] & 0xf]++; + i++; + up->decode[i][(up->cbuf[j] >> 4) & 0xf]++; + i++; + } + up->status |= AVALID; + up->burstcnt++; +} + + +/* + * chu_poll - called by the transmit procedure + */ +static void +chu_poll( + int unit, + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + l_fp offset; + char synchar, qual, leapchar; + int minset, i; + double dtemp; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + if (pp->coderecv == pp->codeproc) + up->errflg = CEVNT_TIMEOUT; + else + pp->polls++; + + /* + * If once in sync and the radio has not been heard for awhile + * (30 m), it is no longer reachable. If not heard in a long + * while (one day), turn out the lights and start from scratch. + */ + minset = ((current_time - peer->update) + 30) / 60; + if (up->status & INSYNC) { + if (minset > PANIC) + up->status = 0; + else if (minset <= HOLD) + peer->reach |= 1; + } + + /* + * Process the last burst, if still in the burst buffer. + * Don't mess with anything if nothing has been heard. If the + * minute contains a valid A frame and valid B frame, assume + * synchronized; however, believe the time only if within metric + * threshold. Note the quality indicator is only for + * diagnostics; the data are used only if in sync and above + * metric threshold. + */ + chu_burst(peer); + if (up->burstcnt == 0) { +#ifdef ICOM + chu_newchan(peer, 0); +#endif /* ICOM */ + return; + } + dtemp = chu_major(peer); + qual = 0; + if (up->status & (BFRAME | AFRAME)) + qual |= SYNERR; + if (up->status & (BFORMAT | AFORMAT)) + qual |= FMTERR; + if (up->status & DECODE) + qual |= DECERR; + if (up->status & STAMP) + qual |= TSPERR; + if (up->status & AVALID && up->status & BVALID) + up->status |= INSYNC; + synchar = leapchar = ' '; + if (!(up->status & INSYNC)) { + pp->leap = LEAP_NOTINSYNC; + synchar = '?'; + } else if (up->leap & 0x2) { + pp->leap = LEAP_ADDSECOND; + leapchar = 'L'; + } else if (up->leap & 0x4) { + pp->leap = LEAP_DELSECOND; + leapchar = 'l'; + } else { + pp->leap = LEAP_NOWARNING; + } +#ifdef HAVE_AUDIO + if (up->fd_audio) + sprintf(pp->a_lastcode, + "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %d %s %.0f %d", + synchar, qual, pp->year, pp->day, pp->hour, + pp->minute, pp->second, leapchar, up->dst, up->dut, + minset, up->gain, up->ident, dtemp, up->ntstamp); + else + sprintf(pp->a_lastcode, + "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %.0f %d", + synchar, qual, pp->year, pp->day, pp->hour, + pp->minute, pp->second, leapchar, up->dst, up->dut, + minset, up->ident, dtemp, up->ntstamp); +#else + sprintf(pp->a_lastcode, + "%c%1X %04d %3d %02d:%02d:%02d %c%x %+d %d %s %.0f %d", + synchar, qual, pp->year, pp->day, pp->hour, pp->minute, + pp->second, leapchar, up->dst, up->dut, minset, up->ident, + dtemp, up->ntstamp); +#endif /* HAVE_AUDIO */ + pp->lencode = strlen(pp->a_lastcode); + + /* + * If in sync and the signal metric is above threshold, the + * timecode is ipso fatso valid and can be selected to + * discipline the clock. Be sure not to leave stray timestamps + * around if signals are too weak or the clock time is invalid. + */ + if (up->status & INSYNC && dtemp > METRIC) { + if (!clocktime(pp->day, pp->hour, pp->minute, 0, GMT, + up->tstamp[0].l_ui, &pp->yearstart, &offset.l_ui)) { + up->errflg = CEVNT_BADTIME; + } else { + offset.l_uf = 0; + for (i = 0; i < up->ntstamp; i++) + refclock_process_offset(pp, offset, + up->tstamp[i], FUDGE + + pp->fudgetime1); + pp->lastref = up->timestamp; + refclock_receive(peer); + } + record_clock_stats(&peer->srcadr, pp->a_lastcode); + } else if (pp->sloppyclockflag & CLK_FLAG4) { + record_clock_stats(&peer->srcadr, pp->a_lastcode); + } +#ifdef DEBUG + if (debug) + printf("chu: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif +#ifdef ICOM + chu_newchan(peer, dtemp); +#endif /* ICOM */ + chu_clear(peer); + if (up->errflg) + refclock_report(peer, up->errflg); + up->errflg = 0; +} + + +/* + * chu_major - majority decoder + */ +static double +chu_major( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + + u_char code[11]; /* decoded timecode */ + int mindist; /* minimum distance */ + int val1, val2; /* maximum distance */ + int synchar; /* stray cat */ + int temp; + int i, j, k; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Majority decoder. Each burst encodes two replications at each + * digit position in the timecode. Each row of the decoding + * matrix encodes the number of occurrences of each digit found + * at the corresponding position. The maximum over all + * occurrences at each position is the distance for this + * position and the corresponding digit is the maximum + * likelihood candidate. If the distance is zero, assume a miss + * '_'; if the distance is not more than half the total number + * of occurrences, assume a soft error '*'; if two different + * digits with the same distance are found, assume a hard error + * '='. These will later cause a format error when the timecode + * is interpreted. The decoding distance is defined as the + * minimum distance over the first nine digits. The tenth digit + * varies over the seconds, so we don't count it. + */ + mindist = 16; + for (i = 0; i < 9; i++) { + val1 = val2 = 0; + k = 0; + for (j = 0; j < 16; j++) { + temp = up->decode[i][j] + up->decode[i + 10][j]; + if (temp > val1) { + val2 = val1; + val1 = temp; + k = j; + } + } + if (val1 == 0) + code[i] = HEX_MISS; + else if (val1 == val2) + code[i] = HEX_HARD; + else if (val1 <= up->burstcnt) + code[i] = HEX_SOFT; + else + code[i] = k; + if (val1 < mindist) + mindist = val1; + code[i] = hexchar[code[i]]; + } + code[i] = 0; + + /* + * A valid timecode requires a minimum distance at least half + * the total number of occurrences. A valid timecode also + * requires at least 20 valid timestamps. + */ + if (up->burstcnt < MINBURST || mindist < up->burstcnt) + up->status |= DECODE; + if (up->ntstamp < MINSTAMP) + up->status |= STAMP; + + /* + * Compute the timecode timestamp from the days, hours and + * minutes of the timecode. Use clocktime() for the aggregate + * minutes and the minute offset computed from the burst + * seconds. Note that this code relies on the filesystem time + * for the years and does not use the years of the timecode. + */ + if (sscanf((char *)code, "%1x%3d%2d%2d", &synchar, &pp->day, + &pp->hour, &pp->minute) != 4) { + up->status |= AFORMAT; + return (0); + } + if (up->status & (DECODE | STAMP)) { + up->errflg = CEVNT_BADREPLY; + return (0); + } + return (mindist * 100. / (2. * up->burstcnt)); +} + + +/* + * chu_clear - clear decoding matrix + */ +static void +chu_clear( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + int i, j; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Clear stuff for the minute. + */ + up->ndx = up->prevsec = 0; + up->burstcnt = up->ntstamp = 0; + up->status &= INSYNC; + for (i = 0; i < 20; i++) { + for (j = 0; j < 16; j++) + up->decode[i][j] = 0; + } +} + +#ifdef ICOM +/* + * chu_newchan - called once per minute to find the best channel; + * returns zero on success, nonzero if ICOM error. + */ +static int +chu_newchan( + struct peer *peer, + double met + ) +{ + struct chuunit *up; + struct refclockproc *pp; + struct xmtr *sp; + char tbuf[80]; /* trace buffer */ + int rval; + double metric; + int i, j; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * The radio can be tuned to three channels: 0 (3330 kHz), 1 + * (7335 kHz) and 2 (14670 kHz). There are five one-minute + * dwells in each cycle. During the first dwell the radio is + * tuned to one of three probe channels; during the remaining + * four dwells the radio is tuned to the data channel. The probe + * channel is selects as the least recently used. At the end of + * each dwell the channel metrics are measured and the highest + * one is selected as the data channel. + */ + if (up->fd_icom <= 0) + return (0); + + sp = &up->xmtr[up->achan]; + sp->metric -= sp->integ[sp->iptr]; + sp->integ[sp->iptr] = met; + sp->metric += sp->integ[sp->iptr]; + sp->iptr = (sp->iptr + 1) % ISTAGE; + metric = 0; + j = 0; + for (i = 0; i < NCHAN; i++) { + up->xmtr[i].probe++; + if (i == up->achan) + up->xmtr[i].probe = 0; + if (up->xmtr[i].metric < metric) + continue; + metric = up->xmtr[i].metric; + j = i; + } + if (j != up->chan && metric > 0) { + up->chan = j; + sprintf(tbuf, "chu: QSY to %.3f MHz metric %.0f", + qsy[up->chan], metric); + if (pp->sloppyclockflag & CLK_FLAG4) + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif + } + + /* + * Start the next dwell. We speed up the initial sync a little. + * If not in sync and no bursts were heard the previous dwell, + * restart the probe. + */ + rval = 0; + if (up->burstcnt == 0 && !(up->status & INSYNC)) + up->dwell = 0; +#ifdef DEBUG + if (debug) + printf( + "chu: at %ld dwell %d achan %d metric %.0f chan %d\n", + current_time, up->dwell, up->achan, sp->metric, + up->chan); +#endif + if (up->dwell == 0) { + rval = 0; + for (i = 0; i < NCHAN; i++) { + if (up->xmtr[i].probe < rval) + continue; + rval = up->xmtr[i].probe; + up->achan = i; + } + rval = icom_freq(up->fd_icom, peer->ttl & 0x7f, + qsy[up->achan] + TUNE); +#ifdef DEBUG + if (debug) + printf("chu: at %ld probe channel %d\n", + current_time, up->achan); +#endif + } else { + if (up->achan != up->chan) { + rval = icom_freq(up->fd_icom, peer->ttl & 0x7f, + qsy[up->chan] + TUNE); + up->achan = up->chan; + } + } + sprintf(up->ident, "CHU%d", up->achan); + memcpy(&peer->refid, up->ident, 4); + up->dwell = (up->dwell + 1) % DWELL; + return (rval); +} +#endif /* ICOM */ + +/* + * chu_dist - determine the distance of two octet arguments + */ +static int +chu_dist( + int x, /* an octet of bits */ + int y /* another octet of bits */ + ) +{ + int val; /* bit count */ + int temp; + int i; + + /* + * The distance is determined as the weight of the exclusive OR + * of the two arguments. The weight is determined by the number + * of one bits in the result. Each one bit increases the weight, + * while each zero bit decreases it. + */ + temp = x ^ y; + val = 0; + for (i = 0; i < 8; i++) { + if ((temp & 0x1) == 0) + val++; + else + val--; + temp >>= 1; + } + return (val); +} + + +#ifdef HAVE_AUDIO +/* + * chu_gain - adjust codec gain + * + * This routine is called once each second. If the signal envelope + * amplitude is too low, the codec gain is bumped up by four units; if + * too high, it is bumped down. The decoder is relatively insensitive to + * amplitude, so this crudity works just fine. The input port is set and + * the error flag is cleared, mostly to be ornery. + */ +static void +chu_gain( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct chuunit *up; + + pp = peer->procptr; + up = (struct chuunit *)pp->unitptr; + + /* + * Apparently, the codec uses only the high order bits of the + * gain control field. Thus, it may take awhile for changes to + * wiggle the hardware bits. + */ + if (up->clipcnt == 0) { + up->gain += 4; + if (up->gain > MAXGAIN) + up->gain = MAXGAIN; + } else if (up->clipcnt > MAXCLP) { + up->gain -= 4; + if (up->gain < 0) + up->gain = 0; + } + audio_gain(up->gain, up->mongain, up->port); + up->clipcnt = 0; +} +#endif /* HAVE_AUDIO */ + + +#else +int refclock_chu_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_conf.c b/ntpd/refclock_conf.c new file mode 100644 index 0000000..8a424f0 --- /dev/null +++ b/ntpd/refclock_conf.c @@ -0,0 +1,331 @@ +/* + * refclock_conf.c - reference clock configuration + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <sys/types.h> + +#include "ntpd.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#ifdef REFCLOCK + +static struct refclock refclock_none = { + noentry, noentry, noentry, noentry, noentry, noentry, NOFLAGS +}; + +#ifdef CLOCK_LOCAL +extern struct refclock refclock_local; +#else +#define refclock_local refclock_none +#endif + +#if defined(CLOCK_TRAK) && defined(PPS) +extern struct refclock refclock_trak; +#else +#define refclock_trak refclock_none +#endif + +#ifdef CLOCK_PST +extern struct refclock refclock_pst; +#else +#define refclock_pst refclock_none +#endif + +#ifdef CLOCK_CHU +extern struct refclock refclock_chu; +#else +#define refclock_chu refclock_none +#endif + +#ifdef CLOCK_WWV +extern struct refclock refclock_wwv; +#else +#define refclock_wwv refclock_none +#endif + +#ifdef CLOCK_SPECTRACOM +extern struct refclock refclock_wwvb; +#else +#define refclock_wwvb refclock_none +#endif + +#ifdef CLOCK_PARSE +extern struct refclock refclock_parse; +#else +#define refclock_parse refclock_none +#endif + +#if defined(CLOCK_MX4200) && defined(HAVE_PPSAPI) +extern struct refclock refclock_mx4200; +#else +#define refclock_mx4200 refclock_none +#endif + +#ifdef CLOCK_AS2201 +extern struct refclock refclock_as2201; +#else +#define refclock_as2201 refclock_none +#endif + +#ifdef CLOCK_ARBITER +extern struct refclock refclock_arbiter; +#else +#define refclock_arbiter refclock_none +#endif + +#ifdef CLOCK_TPRO +extern struct refclock refclock_tpro; +#else +#define refclock_tpro refclock_none +#endif + +#ifdef CLOCK_LEITCH +extern struct refclock refclock_leitch; +#else +#define refclock_leitch refclock_none +#endif + +#ifdef CLOCK_IRIG +extern struct refclock refclock_irig; +#else +#define refclock_irig refclock_none +#endif + +#if defined(CLOCK_MSFEES) && defined(PPS) +extern struct refclock refclock_msfees; +#else +#define refclock_msfees refclock_none +#endif + +#ifdef CLOCK_BANC +extern struct refclock refclock_bancomm; +#else +#define refclock_bancomm refclock_none +#endif + +#ifdef CLOCK_TRUETIME +extern struct refclock refclock_true; +#else +#define refclock_true refclock_none +#endif + +#ifdef CLOCK_DATUM +extern struct refclock refclock_datum; +#else +#define refclock_datum refclock_none +#endif + +#ifdef CLOCK_ACTS +extern struct refclock refclock_acts; +#else +#define refclock_acts refclock_none +#endif + +#ifdef CLOCK_HEATH +extern struct refclock refclock_heath; +#else +#define refclock_heath refclock_none +#endif + +#ifdef CLOCK_NMEA +extern struct refclock refclock_nmea; +#else +#define refclock_nmea refclock_none +#endif + +#ifdef CLOCK_ATOM +extern struct refclock refclock_atom; +#else +#define refclock_atom refclock_none +#endif + +#ifdef CLOCK_PTBACTS +extern struct refclock refclock_ptb; +#else +#define refclock_ptb refclock_none +#endif + +#ifdef CLOCK_USNO +extern struct refclock refclock_usno; +#else +#define refclock_usno refclock_none +#endif + +#ifdef CLOCK_HPGPS +extern struct refclock refclock_hpgps; +#else +#define refclock_hpgps refclock_none +#endif + +#ifdef CLOCK_GPSVME +extern struct refclock refclock_gpsvme; +#else +#define refclock_gpsvme refclock_none +#endif + +#ifdef CLOCK_ARCRON_MSF +extern struct refclock refclock_arc; +#else +#define refclock_arc refclock_none +#endif + +#ifdef CLOCK_SHM +extern struct refclock refclock_shm; +#else +#define refclock_shm refclock_none +#endif + +#ifdef CLOCK_PALISADE +extern struct refclock refclock_palisade; +#else +#define refclock_palisade refclock_none +#endif + +#if defined(CLOCK_ONCORE) && defined(HAVE_PPSAPI) +extern struct refclock refclock_oncore; +#else +#define refclock_oncore refclock_none +#endif + +#if defined(CLOCK_JUPITER) && defined(HAVE_PPSAPI) +extern struct refclock refclock_jupiter; +#else +#define refclock_jupiter refclock_none +#endif + +#if defined(CLOCK_CHRONOLOG) +extern struct refclock refclock_chronolog; +#else +#define refclock_chronolog refclock_none +#endif + +#if defined(CLOCK_DUMBCLOCK) +extern struct refclock refclock_dumbclock; +#else +#define refclock_dumbclock refclock_none +#endif + +#ifdef CLOCK_ULINK +extern struct refclock refclock_ulink; +#else +#define refclock_ulink refclock_none +#endif + +#ifdef CLOCK_PCF +extern struct refclock refclock_pcf; +#else +#define refclock_pcf refclock_none +#endif + +#ifdef CLOCK_FG +extern struct refclock refclock_fg; +#else +#define refclock_fg refclock_none +#endif + +#ifdef CLOCK_HOPF_SERIAL +extern struct refclock refclock_hopfser; +#else +#define refclock_hopfser refclock_none +#endif + +#ifdef CLOCK_HOPF_PCI +extern struct refclock refclock_hopfpci; +#else +#define refclock_hopfpci refclock_none +#endif + +#ifdef CLOCK_JJY +extern struct refclock refclock_jjy; +#else +#define refclock_jjy refclock_none +#endif + +#ifdef CLOCK_TT560 +extern struct refclock refclock_tt560; +#else +#define refclock_tt560 refclock_none +#endif + +#ifdef CLOCK_ZYFER +extern struct refclock refclock_zyfer; +#else +#define refclock_zyfer refclock_none +#endif + +#ifdef CLOCK_RIPENCC +extern struct refclock refclock_ripencc; +#else +#define refclock_ripencc refclock_none +#endif + +#ifdef CLOCK_NEOCLOCK4X +extern struct refclock refclock_neoclock4x; +#else +#define refclock_neoclock4x refclock_none +#endif + +/* + * Order is clock_start(), clock_shutdown(), clock_poll(), + * clock_control(), clock_init(), clock_buginfo, clock_flags; + * + * Types are defined in ntp.h. The index must match this. + */ +struct refclock *refclock_conf[] = { + &refclock_none, /* 0 REFCLK_NONE */ + &refclock_local, /* 1 REFCLK_LOCAL */ + &refclock_trak, /* 2 REFCLK_GPS_TRAK */ + &refclock_pst, /* 3 REFCLK_WWV_PST */ + &refclock_wwvb, /* 4 REFCLK_SPECTRACOM */ + &refclock_true, /* 5 REFCLK_TRUETIME */ + &refclock_irig, /* 6 REFCLK_IRIG_AUDIO */ + &refclock_chu, /* 7 REFCLK_CHU_AUDIO */ + &refclock_parse, /* 8 REFCLK_PARSE */ + &refclock_mx4200, /* 9 REFCLK_GPS_MX4200 */ + &refclock_as2201, /* 10 REFCLK_GPS_AS2201 */ + &refclock_arbiter, /* 11 REFCLK_GPS_ARBITER */ + &refclock_tpro, /* 12 REFCLK_IRIG_TPRO */ + &refclock_leitch, /* 13 REFCLK_ATOM_LEITCH */ + &refclock_msfees, /* 14 REFCLK_MSF_EES */ + &refclock_true, /* 15 alias for REFCLK_TRUETIME */ + &refclock_bancomm, /* 16 REFCLK_IRIG_BANCOMM */ + &refclock_datum, /* 17 REFCLK_GPS_DATUM */ + &refclock_acts, /* 18 REFCLK_NIST_ACTS */ + &refclock_heath, /* 19 REFCLK_WWV_HEATH */ + &refclock_nmea, /* 20 REFCLK_GPS_NMEA */ + &refclock_gpsvme, /* 21 REFCLK_GPS_VME */ + &refclock_atom, /* 22 REFCLK_ATOM_PPS */ + &refclock_ptb, /* 23 REFCLK_PTB_ACTS */ + &refclock_usno, /* 24 REFCLK_USNO */ + &refclock_true, /* 25 alias for REFCLK_TRUETIME */ + &refclock_hpgps, /* 26 REFCLK_GPS_HP */ + &refclock_arc, /* 27 REFCLK_ARCRON_MSF */ + &refclock_shm, /* 28 REFCLK_SHM */ + &refclock_palisade, /* 29 REFCLK_PALISADE */ + &refclock_oncore, /* 30 REFCLK_ONCORE */ + &refclock_jupiter, /* 31 REFCLK_GPS_JUPITER */ + &refclock_chronolog, /* 32 REFCLK_CHRONOLOG */ + &refclock_dumbclock, /* 33 REFCLK_DUMBCLOCK */ + &refclock_ulink, /* 34 REFCLOCK_ULINK */ + &refclock_pcf, /* 35 REFCLOCK_PCF */ + &refclock_wwv, /* 36 REFCLOCK_WWV_AUDIO */ + &refclock_fg, /* 37 REFCLOCK_FG */ + &refclock_hopfser, /* 38 REFCLK_HOPF_SERIAL */ + &refclock_hopfpci, /* 39 REFCLK_HOPF_PCI */ + &refclock_jjy, /* 40 REFCLK_JJY */ + &refclock_tt560, /* 41 REFCLK_TT560 */ + &refclock_zyfer, /* 42 REFCLK_ZYFER */ + &refclock_ripencc, /* 43 REFCLK_RIPENCC */ + &refclock_neoclock4x /* 44 REFCLK_NEOCLOCK4X */ +}; + +u_char num_refclock_conf = sizeof(refclock_conf)/sizeof(struct refclock *); + +#else +int refclock_conf_bs; +#endif diff --git a/ntpd/refclock_datum.c b/ntpd/refclock_datum.c new file mode 100644 index 0000000..82b7369 --- /dev/null +++ b/ntpd/refclock_datum.c @@ -0,0 +1,869 @@ +/* +** refclock_datum - clock driver for the Datum Programmable Time Server +** +** Important note: This driver assumes that you have termios. If you have +** a system that does not have termios, you will have to modify this driver. +** +** Sorry, I have only tested this driver on SUN and HP platforms. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_DATUM) + +/* +** Include Files +*/ + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#if defined(HAVE_BSD_TTYS) +#include <sgtty.h> +#endif /* HAVE_BSD_TTYS */ + +#if defined(HAVE_SYSV_TTYS) +#include <termio.h> +#endif /* HAVE_SYSV_TTYS */ + +#if defined(HAVE_TERMIOS) +#include <termios.h> +#endif +#if defined(STREAM) +#include <stropts.h> +#if defined(WWVBCLK) +#include <sys/clkdefs.h> +#endif /* WWVBCLK */ +#endif /* STREAM */ + +#include "ntp_stdlib.h" + +/* +** This driver supports the Datum Programmable Time System (PTS) clock. +** The clock works in very straight forward manner. When it receives a +** time code request (e.g., the ascii string "//k/mn"), it responds with +** a seven byte BCD time code. This clock only responds with a +** time code after it first receives the "//k/mn" message. It does not +** periodically send time codes back at some rate once it is started. +** the returned time code can be broken down into the following fields. +** +** _______________________________ +** Bit Index | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +** =============================== +** byte 0: | - - - - | H D | +** =============================== +** byte 1: | T D | U D | +** =============================== +** byte 2: | - - | T H | U H | +** =============================== +** byte 3: | - | T M | U M | +** =============================== +** byte 4: | - | T S | U S | +** =============================== +** byte 5: | t S | h S | +** =============================== +** byte 6: | m S | - - - - | +** =============================== +** +** In the table above: +** +** "-" means don't care +** "H D", "T D", and "U D" means Hundreds, Tens, and Units of Days +** "T H", and "UH" means Tens and Units of Hours +** "T M", and "U M" means Tens and Units of Minutes +** "T S", and "U S" means Tens and Units of Seconds +** "t S", "h S", and "m S" means tenths, hundredths, and thousandths +** of seconds +** +** The Datum PTS communicates throught the RS232 port on your machine. +** Right now, it assumes that you have termios. This driver has been tested +** on SUN and HP workstations. The Datum PTS supports various IRIG and +** NASA input codes. This driver assumes that the name of the device is +** /dev/datum. You will need to make a soft link to your RS232 device or +** create a new driver to use this refclock. +*/ + +/* +** Datum PTS defines +*/ + +/* +** Note that if GMT is defined, then the Datum PTS must use Greenwich +** time. Otherwise, this driver allows the Datum PTS to use the current +** wall clock for its time. It determines the time zone offset by minimizing +** the error after trying several time zone offsets. If the Datum PTS +** time is Greenwich time and GMT is not defined, everything should still +** work since the time zone will be found to be 0. What this really means +** is that your system time (at least to start with) must be within the +** correct time by less than +- 30 minutes. The default is for GMT to not +** defined. If you really want to force GMT without the funny +- 30 minute +** stuff then you must define (uncomment) GMT below. +*/ + +/* +#define GMT +#define DEBUG_DATUM_PTC +#define LOG_TIME_ERRORS +*/ + + +#define PRECISION (-10) /* precision assumed 1/1024 ms */ +#define REFID "DATM" /* reference id */ +#define DATUM_DISPERSION 0 /* fixed dispersion = 0 ms */ +#define DATUM_MAX_ERROR 0.100 /* limits on sigma squared */ + +#define DATUM_MAX_ERROR2 (DATUM_MAX_ERROR*DATUM_MAX_ERROR) + +/* +** The Datum PTS structure +*/ + +/* +** I don't use a fixed array of MAXUNITS like everyone else just because +** I don't like to program that way. Sorry if this bothers anyone. I assume +** that you can use any id for your unit and I will search for it in a +** dynamic array of units until I find it. I was worried that users might +** enter a bad id in their configuration file (larger than MAXUNITS) and +** besides, it is just cleaner not to have to assume that you have a fixed +** number of anything in a program. +*/ + +struct datum_pts_unit { + struct peer *peer; /* peer used by ntp */ + struct refclockio io; /* io structure used by ntp */ + int PTS_fd; /* file descriptor for PTS */ + u_int unit; /* id for unit */ + u_long timestarted; /* time started */ + l_fp lastrec; /* time tag for the receive time (system) */ + l_fp lastref; /* reference time (Datum time) */ + u_long yearstart; /* the year that this clock started */ + int coderecv; /* number of time codes received */ + int day; /* day */ + int hour; /* hour */ + int minute; /* minutes */ + int second; /* seconds */ + int msec; /* miliseconds */ + int usec; /* miliseconds */ + u_char leap; /* funny leap character code */ + char retbuf[8]; /* returned time from the datum pts */ + char nbytes; /* number of bytes received from datum pts */ + double sigma2; /* average squared error (roughly) */ + int tzoff; /* time zone offest from GMT */ +}; + +/* +** PTS static constant variables for internal use +*/ + +static char TIME_REQUEST[6]; /* request message sent to datum for time */ +static int nunits; /* number of active units */ +static struct datum_pts_unit +**datum_pts_unit; /* dynamic array of datum PTS structures */ + +/* +** Callback function prototypes that ntpd needs to know about. +*/ + +static int datum_pts_start P((int, struct peer *)); +static void datum_pts_shutdown P((int, struct peer *)); +static void datum_pts_poll P((int, struct peer *)); +static void datum_pts_control P((int, struct refclockstat *, + struct refclockstat *, struct peer *)); +static void datum_pts_init P((void)); +static void datum_pts_buginfo P((int, struct refclockbug *, struct peer *)); + +/* +** This is the call back function structure that ntpd actually uses for +** this refclock. +*/ + +struct refclock refclock_datum = { + datum_pts_start, /* start up a new Datum refclock */ + datum_pts_shutdown, /* shutdown a Datum refclock */ + datum_pts_poll, /* sends out the time request */ + datum_pts_control, /* not used */ + datum_pts_init, /* initialization (called first) */ + datum_pts_buginfo, /* not used */ + NOFLAGS /* we are not setting any special flags */ +}; + +/* +** The datum_pts_receive callback function is handled differently from the +** rest. It is passed to the ntpd io data structure. Basically, every +** 64 seconds, the datum_pts_poll() routine is called. It sends out the time +** request message to the Datum Programmable Time System. Then, ntpd +** waits on a select() call to receive data back. The datum_pts_receive() +** function is called as data comes back. We expect a seven byte time +** code to be returned but the datum_pts_receive() function may only get +** a few bytes passed to it at a time. In other words, this routine may +** get called by the io stuff in ntpd a few times before we get all seven +** bytes. Once the last byte is received, we process it and then pass the +** new time measurement to ntpd for updating the system time. For now, +** there is no 3 state filtering done on the time measurements. The +** jitter may be a little high but at least for its current use, it is not +** a problem. We have tried to keep things as simple as possible. This +** clock should not jitter more than 1 or 2 mseconds at the most once +** things settle down. It is important to get the right drift calibrated +** in the ntpd.drift file as well as getting the right tick set up right +** using tickadj for SUNs. Tickadj is not used for the HP but you need to +** remember to bring up the adjtime daemon because HP does not support +** the adjtime() call. +*/ + +static void datum_pts_receive P((struct recvbuf *)); + +/*......................................................................*/ +/* datum_pts_start - start up the datum PTS. This means open the */ +/* RS232 device and set up the data structure for my unit. */ +/*......................................................................*/ + +static int +datum_pts_start( + int unit, + struct peer *peer + ) +{ + struct datum_pts_unit **temp_datum_pts_unit; + struct datum_pts_unit *datum_pts; +#ifdef HAVE_TERMIOS + struct termios arg; +#endif + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Starting Datum PTS unit %d\n", unit); +#endif + + /* + ** Create the memory for the new unit + */ + + temp_datum_pts_unit = (struct datum_pts_unit **) + malloc((nunits+1)*sizeof(struct datum_pts_unit *)); + if (nunits > 0) memcpy(temp_datum_pts_unit, datum_pts_unit, + nunits*sizeof(struct datum_pts_unit *)); + free(datum_pts_unit); + datum_pts_unit = temp_datum_pts_unit; + datum_pts_unit[nunits] = (struct datum_pts_unit *) + malloc(sizeof(struct datum_pts_unit)); + datum_pts = datum_pts_unit[nunits]; + + datum_pts->unit = unit; /* set my unit id */ + datum_pts->yearstart = 0; /* initialize the yearstart to 0 */ + datum_pts->sigma2 = 0.0; /* initialize the sigma2 to 0 */ + + /* + ** Open the Datum PTS device + */ + + datum_pts->PTS_fd = open("/dev/datum",O_RDWR); + + fcntl(datum_pts->PTS_fd, F_SETFL, 0); /* clear the descriptor flags */ + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Opening RS232 port with file descriptor %d\n", + datum_pts->PTS_fd); +#endif + + /* + ** Set up the RS232 terminal device information. Note that we assume that + ** we have termios. This code has only been tested on SUNs and HPs. If your + ** machine does not have termios this driver cannot be initialized. You can change this + ** if you want by editing this source. Please give the changes back to the + ** ntp folks so that it can become part of their regular distribution. + */ + +#ifdef HAVE_TERMIOS + + arg.c_iflag = IGNBRK; + arg.c_oflag = 0; + arg.c_cflag = B9600 | CS8 | CREAD | PARENB | CLOCAL; + arg.c_lflag = 0; + arg.c_cc[VMIN] = 0; /* start timeout timer right away (not used) */ + arg.c_cc[VTIME] = 30; /* 3 second timout on reads (not used) */ + + tcsetattr(datum_pts->PTS_fd, TCSANOW, &arg); + +#else + + msyslog(LOG_ERR, "Datum_PTS: Termios not supported in this driver"); + (void)close(datum_pts->PTS_fd); + + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + + return 0; + +#endif + + /* + ** Initialize the ntpd IO structure + */ + + datum_pts->peer = peer; + datum_pts->io.clock_recv = datum_pts_receive; + datum_pts->io.srcclock = (caddr_t)datum_pts; + datum_pts->io.datalen = 0; + datum_pts->io.fd = datum_pts->PTS_fd; + + if (!io_addclock(&(datum_pts->io))) { + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Problem adding clock\n"); +#endif + + msyslog(LOG_ERR, "Datum_PTS: Problem adding clock"); + (void)close(datum_pts->PTS_fd); + + return 0; + } + + /* + ** Now add one to the number of units and return a successful code + */ + + nunits++; + return 1; + +} + + +/*......................................................................*/ +/* datum_pts_shutdown - this routine shuts doen the device and */ +/* removes the memory for the unit. */ +/*......................................................................*/ + +static void +datum_pts_shutdown( + int unit, + struct peer *peer + ) +{ + int i,j; + struct datum_pts_unit **temp_datum_pts_unit; + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Shutdown Datum PTS\n"); +#endif + + msyslog(LOG_ERR, "Datum_PTS: Shutdown Datum PTS"); + + /* + ** First we have to find the right unit (i.e., the one with the same id). + ** We do this by looping through the dynamic array of units intil we find + ** it. Note, that I don't simply use an array with a maximimum number of + ** Datum PTS units. Everything is completely dynamic. + */ + + for (i=0; i<nunits; i++) { + if (datum_pts_unit[i]->unit == unit) { + + /* + ** We found the unit so close the file descriptor and free up the memory used + ** by the structure. + */ + + io_closeclock(&datum_pts_unit[i]->io); + close(datum_pts_unit[i]->PTS_fd); + free(datum_pts_unit[i]); + + /* + ** Now clean up the datum_pts_unit dynamic array so that there are no holes. + ** This may mean moving pointers around, etc., to keep things compact. + */ + + if (nunits > 1) { + + temp_datum_pts_unit = (struct datum_pts_unit **) + malloc((nunits-1)*sizeof(struct datum_pts_unit *)); + if (i!= 0) memcpy(temp_datum_pts_unit, datum_pts_unit, + i*sizeof(struct datum_pts_unit *)); + + for (j=i+1; j<nunits; j++) { + temp_datum_pts_unit[j-1] = datum_pts_unit[j]; + } + + free(datum_pts_unit); + datum_pts_unit = temp_datum_pts_unit; + + }else{ + + free(datum_pts_unit); + datum_pts_unit = NULL; + + } + + return; + + } + } + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Error, could not shut down unit %d\n",unit); +#endif + + msyslog(LOG_ERR, "Datum_PTS: Could not shut down Datum PTS unit %d",unit); + +} + +/*......................................................................*/ +/* datum_pts_poll - this routine sends out the time request to the */ +/* Datum PTS device. The time will be passed back in the */ +/* datum_pts_receive() routine. */ +/*......................................................................*/ + +static void +datum_pts_poll( + int unit, + struct peer *peer + ) +{ + int i; + int unit_index; + int error_code; + struct datum_pts_unit *datum_pts; + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Poll Datum PTS\n"); +#endif + + /* + ** Find the right unit and send out a time request once it is found. + */ + + unit_index = -1; + for (i=0; i<nunits; i++) { + if (datum_pts_unit[i]->unit == unit) { + unit_index = i; + datum_pts = datum_pts_unit[i]; + error_code = write(datum_pts->PTS_fd, TIME_REQUEST, 6); + if (error_code != 6) perror("TIME_REQUEST"); + datum_pts->nbytes = 0; + break; + } + } + + /* + ** Print out an error message if we could not find the right unit. + */ + + if (unit_index == -1) { + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Error, could not poll unit %d\n",unit); +#endif + + msyslog(LOG_ERR, "Datum_PTS: Could not poll unit %d",unit); + return; + + } + +} + + +/*......................................................................*/ +/* datum_pts_control - not used */ +/*......................................................................*/ + +static void +datum_pts_control( + int unit, + struct refclockstat *in, + struct refclockstat *out, + struct peer *peer + ) +{ + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Control Datum PTS\n"); +#endif + +} + + +/*......................................................................*/ +/* datum_pts_init - initializes things for all possible Datum */ +/* time code generators that might be used. In practice, this is */ +/* only called once at the beginning before anything else is */ +/* called. */ +/*......................................................................*/ + +static void +datum_pts_init(void) +{ + + /* */ + /*...... open up the log file if we are debugging ......................*/ + /* */ + + /* + ** Open up the log file if we are debugging. For now, send data out to the + ** screen (stdout). + */ + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Init Datum PTS\n"); +#endif + + /* + ** Initialize the time request command string. This is the only message + ** that we ever have to send to the Datum PTS (although others are defined). + */ + + memcpy(TIME_REQUEST, "//k/mn",6); + + /* + ** Initialize the number of units to 0 and set the dynamic array of units to + ** NULL since there are no units defined yet. + */ + + datum_pts_unit = NULL; + nunits = 0; + +} + + +/*......................................................................*/ +/* datum_pts_buginfo - not used */ +/*......................................................................*/ + +static void +datum_pts_buginfo( + int unit, + register struct refclockbug *bug, + register struct peer *peer + ) +{ + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Buginfo Datum PTS\n"); +#endif + +} + + +/*......................................................................*/ +/* datum_pts_receive - receive the time buffer that was read in */ +/* by the ntpd io handling routines. When 7 bytes have been */ +/* received (it may take several tries before all 7 bytes are */ +/* received), then the time code must be unpacked and sent to */ +/* the ntpd clock_receive() routine which causes the systems */ +/* clock to be updated (several layers down). */ +/*......................................................................*/ + +static void +datum_pts_receive( + struct recvbuf *rbufp + ) +{ + int i; + l_fp tstmp; + struct datum_pts_unit *datum_pts; + char *dpt; + int dpend; + int tzoff; + int timerr; + double ftimerr, abserr; +#ifdef DEBUG_DATUM_PTC + double dispersion; +#endif + int goodtime; + /*double doffset;*/ + + /* + ** Get the time code (maybe partial) message out of the rbufp buffer. + */ + + datum_pts = (struct datum_pts_unit *)rbufp->recv_srcclock; + dpt = (char *)&rbufp->recv_space; + dpend = rbufp->recv_length; + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Receive Datum PTS: %d bytes\n", dpend); +#endif + + /* */ + /*...... save the ntp system time when the first byte is received ......*/ + /* */ + + /* + ** Save the ntp system time when the first byte is received. Note that + ** because it may take several calls to this routine before all seven + ** bytes of our return message are finally received by the io handlers in + ** ntpd, we really do want to use the time tag when the first byte is + ** received to reduce the jitter. + */ + + if (datum_pts->nbytes == 0) { + datum_pts->lastrec = rbufp->recv_time; + } + + /* + ** Increment our count to the number of bytes received so far. Return if we + ** haven't gotten all seven bytes yet. + */ + + for (i=0; i<dpend; i++) { + datum_pts->retbuf[datum_pts->nbytes+i] = dpt[i]; + } + + datum_pts->nbytes += dpend; + + if (datum_pts->nbytes != 7) { + return; + } + + /* + ** Convert the seven bytes received in our time buffer to day, hour, minute, + ** second, and msecond values. The usec value is not used for anything + ** currently. It is just the fractional part of the time stored in units + ** of microseconds. + */ + + datum_pts->day = 100*(datum_pts->retbuf[0] & 0x0f) + + 10*((datum_pts->retbuf[1] & 0xf0)>>4) + + (datum_pts->retbuf[1] & 0x0f); + + datum_pts->hour = 10*((datum_pts->retbuf[2] & 0x30)>>4) + + (datum_pts->retbuf[2] & 0x0f); + + datum_pts->minute = 10*((datum_pts->retbuf[3] & 0x70)>>4) + + (datum_pts->retbuf[3] & 0x0f); + + datum_pts->second = 10*((datum_pts->retbuf[4] & 0x70)>>4) + + (datum_pts->retbuf[4] & 0x0f); + + datum_pts->msec = 100*((datum_pts->retbuf[5] & 0xf0) >> 4) + + 10*(datum_pts->retbuf[5] & 0x0f) + + ((datum_pts->retbuf[6] & 0xf0)>>4); + + datum_pts->usec = 1000*datum_pts->msec; + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("day %d, hour %d, minute %d, second %d, msec %d\n", + datum_pts->day, + datum_pts->hour, + datum_pts->minute, + datum_pts->second, + datum_pts->msec); +#endif + + /* + ** Get the GMT time zone offset. Note that GMT should be zero if the Datum + ** reference time is using GMT as its time base. Otherwise we have to + ** determine the offset if the Datum PTS is using time of day as its time + ** base. + */ + + goodtime = 0; /* We are not sure about the time and offset yet */ + +#ifdef GMT + + /* + ** This is the case where the Datum PTS is using GMT so there is no time + ** zone offset. + */ + + tzoff = 0; /* set time zone offset to 0 */ + +#else + + /* + ** This is the case where the Datum PTS is using regular time of day for its + ** time so we must compute the time zone offset. The way we do it is kind of + ** funny but it works. We loop through different time zones (0 to 24) and + ** pick the one that gives the smallest error (+- one half hour). The time + ** zone offset is stored in the datum_pts structure for future use. Normally, + ** the clocktime() routine is only called once (unless the time zone offset + ** changes due to daylight savings) since the goodtime flag is set when a + ** good time is found (with a good offset). Note that even if the Datum + ** PTS is using GMT, this mechanism will still work since it should come up + ** with a value for tzoff = 0 (assuming that your system clock is within + ** a half hour of the Datum time (even with time zone differences). + */ + + for (tzoff=0; tzoff<24; tzoff++) { + if (clocktime( datum_pts->day, + datum_pts->hour, + datum_pts->minute, + datum_pts->second, + (tzoff + datum_pts->tzoff) % 24, + datum_pts->lastrec.l_ui, + &datum_pts->yearstart, + &datum_pts->lastref.l_ui) ) { + + datum_pts->lastref.l_uf = 0; + error = datum_pts->lastref.l_ui - datum_pts->lastrec.l_ui; + +#ifdef DEBUG_DATUM_PTC + printf("Time Zone (clocktime method) = %d, error = %d\n", tzoff, error); +#endif + + if ((error < 1799) && (error > -1799)) { + tzoff = (tzoff + datum_pts->tzoff) % 24; + datum_pts->tzoff = tzoff; + goodtime = 1; + +#ifdef DEBUG_DATUM_PTC + printf("Time Zone found (clocktime method) = %d\n",tzoff); +#endif + + break; + } + + } + } + +#endif + + /* + ** Make sure that we have a good time from the Datum PTS. Clocktime() also + ** sets yearstart and lastref.l_ui. We will have to set astref.l_uf (i.e., + ** the fraction of a second) stuff later. + */ + + if (!goodtime) { + + if (!clocktime( datum_pts->day, + datum_pts->hour, + datum_pts->minute, + datum_pts->second, + tzoff, + datum_pts->lastrec.l_ui, + &datum_pts->yearstart, + &datum_pts->lastref.l_ui) ) { + +#ifdef DEBUG_DATUM_PTC + if (debug) + { + printf("Error: bad clocktime\n"); + printf("GMT %d, lastrec %d, yearstart %d, lastref %d\n", + tzoff, + datum_pts->lastrec.l_ui, + datum_pts->yearstart, + datum_pts->lastref.l_ui); + } +#endif + + msyslog(LOG_ERR, "Datum_PTS: Bad clocktime"); + + return; + + }else{ + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Good clocktime\n"); +#endif + + } + + } + + /* + ** We have datum_pts->lastref.l_ui set (which is the integer part of the + ** time. Now set the microseconds field. + */ + + TVUTOTSF(datum_pts->usec, datum_pts->lastref.l_uf); + + /* + ** Compute the time correction as the difference between the reference + ** time (i.e., the Datum time) minus the receive time (system time). + */ + + tstmp = datum_pts->lastref; /* tstmp is the datum ntp time */ + L_SUB(&tstmp, &datum_pts->lastrec); /* tstmp is now the correction */ + datum_pts->coderecv++; /* increment a counter */ + +#ifdef DEBUG_DATUM_PTC + dispersion = DATUM_DISPERSION; /* set the dispersion to 0 */ + ftimerr = dispersion; + ftimerr /= (1024.0 * 64.0); + if (debug) + printf("dispersion = %d, %f\n", dispersion, ftimerr); +#endif + + /* + ** Pass the new time to ntpd through the refclock_receive function. Note + ** that we are not trying to make any corrections due to the time it takes + ** for the Datum PTS to send the message back. I am (erroneously) assuming + ** that the time for the Datum PTS to send the time back to us is negligable. + ** I suspect that this time delay may be as much as 15 ms or so (but probably + ** less). For our needs at JPL, this kind of error is ok so it is not + ** necessary to use fudge factors in the ntp.conf file. Maybe later we will. + */ + /*LFPTOD(&tstmp, doffset);*/ + datum_pts->lastref = datum_pts->lastrec; + refclock_receive(datum_pts->peer); + + /* + ** Compute sigma squared (not used currently). Maybe later, this could be + ** used for the dispersion estimate. The problem is that ntpd does not link + ** in the math library so sqrt() is not available. Anyway, this is useful + ** for debugging. Maybe later I will just use absolute values for the time + ** error to come up with my dispersion estimate. Anyway, for now my dispersion + ** is set to 0. + */ + + timerr = tstmp.l_ui<<20; + timerr |= (tstmp.l_uf>>12) & 0x000fffff; + ftimerr = timerr; + ftimerr /= 1024*1024; + abserr = ftimerr; + if (ftimerr < 0.0) abserr = -ftimerr; + + if (datum_pts->sigma2 == 0.0) { + if (abserr < DATUM_MAX_ERROR) { + datum_pts->sigma2 = abserr*abserr; + }else{ + datum_pts->sigma2 = DATUM_MAX_ERROR2; + } + }else{ + if (abserr < DATUM_MAX_ERROR) { + datum_pts->sigma2 = 0.95*datum_pts->sigma2 + 0.05*abserr*abserr; + }else{ + datum_pts->sigma2 = 0.95*datum_pts->sigma2 + 0.05*DATUM_MAX_ERROR2; + } + } + +#ifdef DEBUG_DATUM_PTC + if (debug) + printf("Time error = %f seconds\n", ftimerr); +#endif + +#if defined(DEBUG_DATUM_PTC) || defined(LOG_TIME_ERRORS) + if (debug) + printf("PTS: day %d, hour %d, minute %d, second %d, msec %d, Time Error %f\n", + datum_pts->day, + datum_pts->hour, + datum_pts->minute, + datum_pts->second, + datum_pts->msec, + ftimerr); +#endif + +} +#else +int refclock_datum_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_dumbclock.c b/ntpd/refclock_dumbclock.c new file mode 100644 index 0000000..2788649 --- /dev/null +++ b/ntpd/refclock_dumbclock.c @@ -0,0 +1,385 @@ +/* + * refclock_dumbclock - clock driver for a unknown time distribution system + * that only provides hh:mm:ss (in local time, yet!). + */ + +/* + * Must interpolate back to local time. Very annoying. + */ +#define GET_LOCALTIME + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(SYS_WINNT) +#undef close +#define close closesocket +#endif + +#if defined(REFCLOCK) && defined(CLOCK_DUMBCLOCK) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports a generic dumb clock that only outputs hh:mm:ss, + * in local time, no less. + * + * Input format: + * + * hh:mm:ss <cr> + * + * hh:mm:ss -- what you'd expect, with a 24 hour clock. (Heck, that's the only + * way it could get stupider.) We take time on the <cr>. + * + * The original source of this module was the WWVB module. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/dumbclock%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-13) /* precision assumed (about 100 us) */ +#define REFID "dumbclock" /* reference ID */ +#define DESCRIPTION "Dumb clock" /* WRU */ + + +/* + * Insanity check. Since the time is local, we need to make sure that during midnight + * transitions, we can convert back to Unix time. If the conversion results in some number + * worse than this number of seconds away, assume the next day and retry. + */ +#define INSANE_SECONDS 3600 + +/* + * Dumb clock control structure + */ +struct dumbclock_unit { + u_char tcswitch; /* timecode switch */ + l_fp laststamp; /* last receive timestamp */ + u_char lasthour; /* last hour (for monitor) */ + u_char linect; /* count ignored lines (for monitor */ + struct tm ymd; /* struct tm for y/m/d only */ +}; + +/* + * Function prototypes + */ +static int dumbclock_start P((int, struct peer *)); +static void dumbclock_shutdown P((int, struct peer *)); +static void dumbclock_receive P((struct recvbuf *)); +#if 0 +static void dumbclock_poll P((int, struct peer *)); +#endif + +/* + * Transfer vector + */ +struct refclock refclock_dumbclock = { + dumbclock_start, /* start up driver */ + dumbclock_shutdown, /* shut down driver */ + noentry, /* poll the driver -- a nice fabrication */ + noentry, /* not used */ + noentry, /* not used */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + + +/* + * dumbclock_start - open the devices and initialize data for processing + */ +static int +dumbclock_start( + int unit, + struct peer *peer + ) +{ + register struct dumbclock_unit *up; + struct refclockproc *pp; + int fd; + char device[20]; + struct tm *tm_time_p; + time_t now; + + /* + * Open serial port. Don't bother with CLK line discipline, since + * it's not available. + */ + (void)sprintf(device, DEVICE, unit); +#ifdef DEBUG + if (debug) + printf ("starting Dumbclock with device %s\n",device); +#endif + fd = refclock_open(device, SPEED232, 0); + if (fd < 0) + return (0); + + /* + * Allocate and initialize unit structure + */ + up = (struct dumbclock_unit *)emalloc(sizeof(struct dumbclock_unit)); + if (up == NULL) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct dumbclock_unit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = dumbclock_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + + + time(&now); +#ifdef GET_LOCALTIME + tm_time_p = localtime(&now); +#else + tm_time_p = gmtime(&now); +#endif + if (tm_time_p) + { + up->ymd = *tm_time_p; + } + else + { + return 0; + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * dumbclock_shutdown - shut down the clock + */ +static void +dumbclock_shutdown( + int unit, + struct peer *peer + ) +{ + register struct dumbclock_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct dumbclock_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * dumbclock_receive - receive data from the serial interface + */ +static void +dumbclock_receive( + struct recvbuf *rbufp + ) +{ + struct dumbclock_unit *up; + struct refclockproc *pp; + struct peer *peer; + + l_fp trtmp; /* arrival timestamp */ + int hours; /* hour-of-day */ + int minutes; /* minutes-past-the-hour */ + int seconds; /* seconds */ + int temp; /* int temp */ + int got_good; /* got a good time flag */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct dumbclock_unit *)pp->unitptr; + temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + + if (temp == 0) { + if (up->tcswitch == 0) { + up->tcswitch = 1; + up->laststamp = trtmp; + } else + up->tcswitch = 0; + return; + } + pp->lencode = (u_short)temp; + pp->lastrec = up->laststamp; + up->laststamp = trtmp; + up->tcswitch = 1; + +#ifdef DEBUG + if (debug) + printf("dumbclock: timecode %d %s\n", + pp->lencode, pp->a_lastcode); +#endif + + /* + * We get down to business. Check the timecode format... + */ + got_good=0; + if (sscanf(pp->a_lastcode,"%02d:%02d:%02d", + &hours,&minutes,&seconds) == 3) + { + struct tm *gmtp; + struct tm *lt_p; + time_t asserted_time; /* the SPM time based on the composite time+date */ + struct tm asserted_tm; /* the struct tm of the same */ + int adjyear; + int adjmon; + int reality_delta; + time_t now; + + + /* + * Convert to GMT for sites that distribute localtime. This + * means we have to figure out what day it is. Easier said + * than done... + */ + + asserted_tm.tm_year = up->ymd.tm_year; + asserted_tm.tm_mon = up->ymd.tm_mon; + asserted_tm.tm_mday = up->ymd.tm_mday; + asserted_tm.tm_hour = hours; + asserted_tm.tm_min = minutes; + asserted_tm.tm_sec = seconds; + asserted_tm.tm_isdst = -1; + +#ifdef GET_LOCALTIME + asserted_time = mktime (&asserted_tm); + time(&now); +#else +#include "GMT unsupported for dumbclock!" +#endif + reality_delta = asserted_time - now; + + /* + * We assume that if the time is grossly wrong, it's because we got the + * year/month/day wrong. + */ + if (reality_delta > INSANE_SECONDS) + { + asserted_time -= SECSPERDAY; /* local clock behind real time */ + } + else if (-reality_delta > INSANE_SECONDS) + { + asserted_time += SECSPERDAY; /* local clock ahead of real time */ + } + lt_p = localtime(&asserted_time); + if (lt_p) + { + up->ymd = *lt_p; + } + else + { + refclock_report (peer, CEVNT_FAULT); + return; + } + + if ((gmtp = gmtime (&asserted_time)) == NULL) + { + refclock_report (peer, CEVNT_FAULT); + return; + } + adjyear = gmtp->tm_year+1900; + adjmon = gmtp->tm_mon+1; + pp->day = ymd2yd (adjyear, adjmon, gmtp->tm_mday); + pp->hour = gmtp->tm_hour; + pp->minute = gmtp->tm_min; + pp->second = gmtp->tm_sec; +#ifdef DEBUG + if (debug) + printf ("time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", + adjyear,adjmon,gmtp->tm_mday,pp->hour,pp->minute, + pp->second); +#endif + + got_good=1; + } + + if (!got_good) + { + if (up->linect > 0) + up->linect--; + else + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + up->lasthour = (u_char)pp->hour; +} + +#if 0 +/* + * dumbclock_poll - called by the transmit procedure + */ +static void +dumbclock_poll( + int unit, + struct peer *peer + ) +{ + register struct dumbclock_unit *up; + struct refclockproc *pp; + char pollchar; + + /* + * Time to poll the clock. The Chrono-log clock is supposed to + * respond to a 'T' by returning a timecode in the format(s) + * specified above. Ours does (can?) not, but this seems to be + * an installation-specific problem. This code is dyked out, + * but may be re-enabled if anyone ever finds a Chrono-log that + * actually listens to this command. + */ +#if 0 + pp = peer->procptr; + up = (struct dumbclock_unit *)pp->unitptr; + if (peer->reach == 0) + refclock_report(peer, CEVNT_TIMEOUT); + if (up->linect > 0) + pollchar = 'R'; + else + pollchar = 'T'; + if (write(pp->io.fd, &pollchar, 1) != 1) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; +#endif +} +#endif + +#else +int refclock_dumbclock_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_fg.c b/ntpd/refclock_fg.c new file mode 100644 index 0000000..ebcf1b5 --- /dev/null +++ b/ntpd/refclock_fg.c @@ -0,0 +1,348 @@ +/* + * refclock_fg - clock driver for the Forum Graphic GPS datating station + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_FG) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +/* + * This driver supports the Forum Graphic GPS dating station. + * More information about FG GPS is available on http://www.forumgraphic.com + * Contact das@amt.ru for any question about this driver. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/fgclock%d" +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "GPS" +#define DESCRIPTION "Forum Graphic GPS dating station" +#define LENFG 26 /* timecode length */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ + +/* + * Function prototypes + */ +static int fg_init P((int)); +static int fg_start P((int, struct peer *)); +static void fg_shutdown P((int, struct peer *)); +static void fg_poll P((int, struct peer *)); +static void fg_receive P((struct recvbuf *)); + +/* + * Forum Graphic unit control structure + */ + +struct fgunit { + int pollnum; /* Use peer.poll instead? */ + int status; /* Hug to check status information on GPS */ + int y2kwarn; /* Y2K bug */ +}; + +/* + * Queries definition + */ +static char fginit[] = { 0x10, 0x48, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static char fgdate[] = { 0x10, 0x44, 0x10, 0x0D, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +/* + * Transfer vector + */ +struct refclock refclock_fg = { + fg_start, /* start up driver */ + fg_shutdown, /* shut down driver */ + fg_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +/* + * fg_init - Initialization of FG GPS. + */ + +static int +fg_init( + int fd + ) +{ + if (write(fd, fginit, LENFG) != LENFG) + return 0; + + return (1); + +} + +/* + * fg_start - open the device and initialize data for processing + */ +static int +fg_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + struct fgunit *up; + int fd; + char device[20]; + + + /* + * Open device file for reading. + */ + (void)sprintf(device, DEVICE, unit); + +#ifdef DEBUG + if (debug) + printf ("starting FG with device %s\n",device); +#endif + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + + if (!(up = (struct fgunit *) + emalloc(sizeof(struct fgunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct fgunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = fg_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + return (0); + } + + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 3); + up->pollnum = 0; + + /* + * Setup dating station to use GPS receiver. + * GPS receiver should work before this operation. + */ + if(!fg_init(pp->io.fd)) + refclock_report(peer, CEVNT_FAULT); + + return (1); +} + + +/* + * fg_shutdown - shut down the clock + */ +static void +fg_shutdown( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + struct fgunit *up; + + pp = peer->procptr; + up = (struct fgunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * fg_poll - called by the transmit procedure + */ +static void +fg_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + + /* + * Time to poll the clock. The FG clock responds to a + * "<DLE>D<DLE><CR>" by returning a timecode in the format specified + * above. If nothing is heard from the clock for two polls, + * declare a timeout and keep going. + */ + + if (write(pp->io.fd, fgdate, LENFG) != LENFG) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; + + if (peer->burst > 0) + return; + /* + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + */ + peer->burst = NSTAGE; + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + + + return; + +} + +/* + * fg_receive - receive data from the serial interface + */ +static void +fg_receive( + struct recvbuf *rbufp + ) +{ + struct refclockproc *pp; + struct fgunit *up; + struct peer *peer; + char *bpt; + + /* + * Initialize pointers and read the timecode and timestamp + * We can't use gtlin function because we need bynary data in buf */ + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct fgunit *)pp->unitptr; + + /* + * Below hug to implement receiving of status information + */ + if(!up->pollnum) + { + up->pollnum++; + return; + } + + + if (rbufp->recv_length < (LENFG-2)) + { + refclock_report(peer, CEVNT_BADREPLY); + return; /* The reply is invalid discard it. */ + } + + /* Below I trying to find a correct reply in buffer. + * Sometime GPS reply located in the beginnig of buffer, + * sometime you can find it with some offset. + */ + + bpt = (char *)rbufp->recv_space.X_recv_buffer; + while(*bpt != '') + bpt++; + +#define BP2(x) ( bpt[x] & 15 ) +#define BP1(x) (( bpt[x] & 240 ) >> 4) + + pp->year = BP1(2)*10 + BP2(2); + + if(pp->year == 94) + { + refclock_report(peer, CEVNT_BADREPLY); + if(!fg_init(pp->io.fd)) + refclock_report(peer, CEVNT_FAULT); + return; + /* GPS is just powered up. The date is invalid - + discarding it. Initilize GPS one more time */ + /* Sorry - this driver will broken in 2094 ;) */ + } + + if (pp->year < 99) + pp->year += 100; + + pp->year += 1900; + pp->day = 100 * BP2(3) + 10 * BP1(4) + BP2(4); + +/* + After Jan, 10 2000 Forum Graphic GPS receiver had a very strange + benahour. It doubles day number for an hours in replys after 10:10:10 UTC + and doubles min every hour at HH:10:ss for a minute. + Hope it is a problem of my unit only and not a Y2K problem of FG GPS. + Below small code to avoid such situation. +*/ + if(up->y2kwarn > 10) + pp->hour = BP1(6)*10 + BP2(6); + else + pp->hour = BP1(5)*10 + BP2(5); + + if((up->y2kwarn > 10) && (pp->hour == 10)) + { + pp->minute = BP1(7)*10 + BP2(7); + pp->second = BP1(8)*10 + BP2(8); + pp->nsec = (BP1(9)*10 + BP2(9)) * 1000000; + pp->nsec += BP1(10) * 1000; + } else { + pp->hour = BP1(5)*10 + BP2(5); + pp->minute = BP1(6)*10 + BP2(6); + pp->second = BP1(7)*10 + BP2(7); + pp->nsec = (BP1(8)*10 + BP2(8)) * 1000000; + pp->nsec += BP1(9) * 1000; + } + + if((pp->hour == 10) && (pp->minute == 10)) + { + up->y2kwarn++; + } + + sprintf(pp->a_lastcode, "%d %d %d %d %d", pp->year, pp->day, pp->hour, pp->minute, pp->second); + pp->lencode = strlen(pp->a_lastcode); + /*get_systime(&pp->lastrec);*/ + +#ifdef DEBUG + if (debug) + printf ("fg: time is %04d/%03d %02d:%02d:%02d UTC\n", + pp->year, pp->day, pp->hour, pp->minute, pp->second); +#endif + + if (peer->stratum <= 1) + peer->refid = pp->refid; + pp->disp = (10e-6); + pp->lastrec = rbufp->recv_time; /* Is it better than get_systime()? */ + /* pp->leap = LEAP_NOWARNING; */ + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + + if (!refclock_process(pp)) + refclock_report(peer, CEVNT_BADTIME); + pp->lastref = pp->lastrec; + refclock_receive(peer); + return; +} + + +#else +int refclock_fg_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_gpsvme.c b/ntpd/refclock_gpsvme.c new file mode 100644 index 0000000..a7fcf5f --- /dev/null +++ b/ntpd/refclock_gpsvme.c @@ -0,0 +1,622 @@ +/* + * refclock_gpsvme.c NTP clock driver for the TrueTime GPS-VME + * R. Schmidt, Time Service, US Naval Obs. res@tuttle.usno.navy.mil + * + * The refclock type has been defined as 16 (until new id assigned). + * These DEFS are included in the Makefile: + * DEFS= -DHAVE_TERMIOS -DSYS_HPUX=9 + * DEFS_LOCAL= -DREFCLOCK + * CLOCKDEFS= -DGPSVME + * The file map_vme.c does the VME memory mapping, and includes vme_init(). + * map_vme.c is HP-UX specific, because HPUX cannot mmap() device files! Boo! + * The file gps.h provides TrueTime register info. + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_GPSVME) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <syslog.h> +#include <ctype.h> + +#include "gps.h" +#include "/etc/conf/h/io.h" + +/* GLOBAL STUFF BY RES */ + +#include <time.h> + +#define PRIO 120 /* set the realtime priority */ +#define NREGS 7 /* number of registers we will use */ + +extern int init_vme(); /* This is just a call to map_vme() */ + /* It doesn't have to be extern */ + /* the map_vme() call */ +extern unsigned short *greg[NREGS]; /* made extern to avoid being in both map_vme.c and this file */ +extern void *gps_base; /* mjb lmco 12/20/99 */ + +extern caddr_t map_vme (); +extern void unmap_vme(); /* Unmaps the VME space */ + +struct vmedate { /* structure needed by ntp */ + unsigned short year; /* *tptr is a pointer to this */ + unsigned short doy; + unsigned short hr; + unsigned short mn; + unsigned short sec; + unsigned long frac; + unsigned short status; +}; + +struct vmedate *get_gpsvme_time(); +struct vmedate * time_vme; /* added to emulate LM refclock_gpsvme + (Made global per RES suggestion to fix mem leak DW lmco) mjb lmco 12/15/99 */ + +/* END OF STUFF FROM RES */ + +/* + * Definitions + */ +#define MAXUNITS 2 /* max number of VME units */ +#define BMAX 50 /* timecode buffer length */ + +/* + * VME interface parameters. + */ +#define VMEPRECISION (-21) /* precision assumed (1 us) */ +#define USNOREFID "USNO\0" /* Or whatever? */ +#define VMEREFID "GPS" /* reference id */ +#define VMEDESCRIPTION "GPS" /* who we are */ +#define VMEHSREFID 0x7f7f1001 /* 127.127.16.01 refid hi strata */ + +/* I'm using clock type 16 until one is assigned */ +/* This is set also in vme_control, below */ + + +#define GMT 0 /* hour offset from Greenwich */ + +/* + * VME unit control structure. + */ +struct vmeunit { + struct peer *peer; /* associated peer structure */ + struct refclockio io; /* given to the I/O handler */ + struct vmedate vmedata; /* data returned from vme read */ + l_fp lastrec; /* last local time */ + l_fp lastref; /* last timecode time */ + char lastcode[BMAX]; /* last timecode received */ + u_short lencode; /* length of last timecode */ + u_long lasttime; /* last time clock heard from */ + u_short unit; /* unit number for this guy */ + u_short status; /* clock status */ + u_short lastevent; /* last clock event */ + u_short year; /* year of eternity */ + u_short day; /* day of year */ + u_short hour; /* hour of day */ + u_short minute; /* minute of hour */ + u_short second; /* seconds of minute */ + u_long usec; /* microsecond of second */ + u_long yearstart; /* start of current year */ + u_short leap; /* leap indicators */ + /* + * Status tallies + */ + u_long polls; /* polls sent */ + u_long noreply; /* no replies to polls */ + u_long coderecv; /* timecodes received */ + u_long badformat; /* bad format */ + u_long baddata; /* bad data */ + u_long timestarted; /* time we started this */ +}; + +/* + * Data space for the unit structures. Note that we allocate these on + * the fly, but never give them back. + */ +static struct vmeunit *vmeunits[MAXUNITS]; +static u_char unitinuse[MAXUNITS]; + +/* + * Keep the fudge factors separately so they can be set even + * when no clock is configured. + */ +static l_fp fudgefactor[MAXUNITS]; +static u_char stratumtouse[MAXUNITS]; +static u_char sloppyclockflag[MAXUNITS]; + +/* + * Function prototypes + */ +static void vme_init (void); +static int vme_start (int, struct peer *); +static void vme_shutdown (int, struct peer *); +static void vme_report_event (struct vmeunit *, int); +static void vme_receive (struct recvbuf *); +static void vme_poll (int unit, struct peer *); +static void vme_control (int, struct refclockstat *, struct refclockstat *, struct peer *); +static void vme_buginfo (int, struct refclockbug *, struct peer *); + +/* + * Transfer vector + */ +struct refclock refclock_gpsvme = { + vme_start, vme_shutdown, vme_poll, + vme_control, vme_init, vme_buginfo, NOFLAGS +}; + +int fd_vme; /* file descriptor for ioctls */ +int regvalue; + +/* + * vme_init - initialize internal vme driver data + */ +static void +vme_init(void) +{ + register int i; + /* + * Just zero the data arrays + */ + /* + bzero((char *)vmeunits, sizeof vmeunits); + bzero((char *)unitinuse, sizeof unitinuse); + */ + + /* + * Initialize fudge factors to default. + */ + for (i = 0; i < MAXUNITS; i++) { + fudgefactor[i].l_ui = 0; + fudgefactor[i].l_uf = 0; + stratumtouse[i] = 0; + sloppyclockflag[i] = 0; + } +} + +/* + * vme_start - open the VME device and initialize data for processing + */ +static int +vme_start( + u_int unit, + struct peer *peer + ) +{ + register struct vmeunit *vme; + register int i; + int dummy; + char vmedev[20]; + + /* + * Check configuration info. + */ + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "vme_start: unit %d invalid", unit); + return (0); + } + if (unitinuse[unit]) { + msyslog(LOG_ERR, "vme_start: unit %d in use", unit); + return (0); + } + + /* + * Open VME device + */ +#ifdef DEBUG + + printf("Opening VME DEVICE \n"); +#endif + init_vme(); /* This is in the map_vme.c external file */ + + /* + * Allocate unit structure + */ + if (vmeunits[unit] != 0) { + vme = vmeunits[unit]; /* The one we want is okay */ + } else { + for (i = 0; i < MAXUNITS; i++) { + if (!unitinuse[i] && vmeunits[i] != 0) + break; + } + if (i < MAXUNITS) { + /* + * Reclaim this one + */ + vme = vmeunits[i]; + vmeunits[i] = 0; + } else { + vme = (struct vmeunit *) + emalloc(sizeof(struct vmeunit)); + time_vme = (struct vmedate *)malloc(sizeof(struct vmedate)); /* Added to emulate LM's refclock_gpsvme + (added to fix mem lead DW lmco) mjb lmco 12/22/99 */ + } + } + bzero((char *)vme, sizeof(struct vmeunit)); + vmeunits[unit] = vme; + + /* + * Set up the structures + */ + vme->peer = peer; + vme->unit = (u_short)unit; + vme->timestarted = current_time; + + vme->io.clock_recv = vme_receive; + vme->io.srcclock = (caddr_t)vme; + vme->io.datalen = 0; + vme->io.fd = fd_vme; + + /* + * All done. Initialize a few random peer variables, then + * return success. + */ + peer->precision = VMEPRECISION; + peer->stratum = stratumtouse[unit]; + memcpy( (char *)&peer->refid, USNOREFID,4); + + /* peer->refid = htonl(VMEHSREFID); */ + + unitinuse[unit] = 1; + return (1); +} + + +/* + * vme_shutdown - shut down a VME clock + */ +static void +vme_shutdown( + int unit + ) +{ + register struct vmeunit *vme; + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "vme_shutdown: unit %d invalid", unit); + return; + } + if (!unitinuse[unit]) { + msyslog(LOG_ERR, "vme_shutdown: unit %d not in use", unit); + return; + } + + /* + * Tell the I/O module to turn us off. We're history. + */ + unmap_vme(); + vme = vmeunits[unit]; + io_closeclock(&vme->io); + unitinuse[unit] = 0; +} + +/* + * vme_report_event - note the occurance of an event + * + * This routine presently just remembers the report and logs it, but + * does nothing heroic for the trap handler. + */ +static void +vme_report_event( + struct vmeunit *vme, + int code + ) +{ + struct peer *peer; + + peer = vme->peer; + if (vme->status != (u_short)code) { + vme->status = (u_short)code; + if (code != CEVNT_NOMINAL) + vme->lastevent = (u_short)code; + msyslog(LOG_INFO, + "clock %s event %x", ntoa(&peer->srcadr), code); + } +} + + +/* + * vme_receive - receive data from the VME device. + * + * Note: This interface would be interrupt-driven. We don't use that + * now, but include a dummy routine for possible future adventures. + */ +static void +vme_receive( + struct recvbuf *rbufp + ) +{ +} + +/* + * vme_poll - called by the transmit procedure + */ +static void +vme_poll( + int unit, + struct peer *peer + ) +{ + struct vmedate *tptr; + struct vmeunit *vme; + l_fp tstmp; + time_t tloc; + struct tm *tadr; + long ltemp; + + + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "vme_poll: unit %d invalid", unit); + return; + } + if (!unitinuse[unit]) { + msyslog(LOG_ERR, "vme_poll: unit %d not in use", unit); + return; + } + vme = vmeunits[unit]; /* Here is the structure */ + vme->polls++; + + tptr = &vme->vmedata; + + if ((tptr = get_gpsvme_time()) == NULL ) { + vme_report_event(vme, CEVNT_BADREPLY); + return; + } + + get_systime(&vme->lastrec); + vme->lasttime = current_time; + + /* + * Get VME time and convert to timestamp format. + * The year must come from the system clock. + */ + /* + time(&tloc); + tadr = gmtime(&tloc); + tptr->year = (unsigned short)(tadr->tm_year + 1900); + */ + + sprintf(vme->lastcode, + "%3.3d %2.2d:%2.2d:%2.2d.%.6d %1d\0", + tptr->doy, tptr->hr, tptr->mn, + tptr->sec, tptr->frac, tptr->status); + + record_clock_stats(&(vme->peer->srcadr), vme->lastcode); + vme->lencode = (u_short) strlen(vme->lastcode); + + vme->day = tptr->doy; + vme->hour = tptr->hr; + vme->minute = tptr->mn; + vme->second = tptr->sec; + vme->nsec = tptr->frac * 1000; + +#ifdef DEBUG + if (debug) + printf("vme: %3d %02d:%02d:%02d.%06ld %1x\n", + vme->day, vme->hour, vme->minute, vme->second, + vme->nsec, tptr->status); +#endif + if (tptr->status ) { /* Status 0 is locked to ref., 1 is not */ + vme_report_event(vme, CEVNT_BADREPLY); + return; + } + + /* + * Now, compute the reference time value. Use the heavy + * machinery for the seconds and the millisecond field for the + * fraction when present. If an error in conversion to internal + * format is found, the program declares bad data and exits. + * Note that this code does not yet know how to do the years and + * relies on the clock-calendar chip for sanity. + */ + if (!clocktime(vme->day, vme->hour, vme->minute, + vme->second, GMT, vme->lastrec.l_ui, + &vme->yearstart, &vme->lastref.l_ui)) { + vme->baddata++; + vme_report_event(vme, CEVNT_BADTIME); + msyslog(LOG_ERR, "refclock_gpsvme: bad data!!"); + return; + } + vme->lastref.l_uf = 0; + DTOLFP(vme->nsec / 1e9, <emp); + L_ADD(&vme->lastrec, <emp); + tstmp = vme->lastref; + + L_SUB(&tstmp, &vme->lastrec); + vme->coderecv++; + + L_ADD(&tstmp, &(fudgefactor[vme->unit])); + vme->lastref = vme->lastrec; + refclock_receive(vme->peer); +} + +/* + * vme_control - set fudge factors, return statistics2 + */ +static void +vme_control( + u_int unit, + struct refclockstat *in, + struct refclockstat *out, + struct peer * peer + ) +{ + register struct vmeunit *vme; + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "vme_control: unit %d invalid)", unit); + return; + } + + if (in != 0) { + if (in->haveflags & CLK_HAVETIME1) + DTOLFP(in->fudgetime1, &fudgefactor[unit]); /* added mjb lmco 12/20/99 */ + + if (in->haveflags & CLK_HAVEVAL1) { + stratumtouse[unit] = (u_char)(in->fudgeval1 & 0xf); + if (unitinuse[unit]) { + struct peer *peer; + + /* + * Should actually reselect clock, but + * will wait for the next timecode + */ + vme = vmeunits[unit]; + peer = vme->peer; + peer->stratum = stratumtouse[unit]; + if (stratumtouse[unit] <= 1) + memcpy( (char *)&peer->refid, USNOREFID,4); + else + peer->refid = htonl(VMEHSREFID); + } + } + if (in->haveflags & CLK_HAVEFLAG1) { + sloppyclockflag[unit] = in->flags & CLK_FLAG1; + } + } + + if (out != 0) { + out->type = 16; /*set by RES SHOULD BE CHANGED */ + out->haveflags + = CLK_HAVETIME1|CLK_HAVEVAL1|CLK_HAVEVAL2|CLK_HAVEFLAG1; + out->clockdesc = VMEDESCRIPTION; + LFPTOD(&fudgefactor[unit], out->fudgetime1); /* added mjb lmco 12/20/99 */ + + out ->fudgetime2 = 0; /* should do what above was supposed to do mjb lmco 12/20/99 */ + + out->fudgeval1 = (long)stratumtouse[unit]; /*changed from above LONG was not + defined mjb lmco 12/15/99 */ + + out->fudgeval2 = 0; + out->flags = sloppyclockflag[unit]; + if (unitinuse[unit]) { + vme = vmeunits[unit]; + out->lencode = vme->lencode; + out->p_lastcode = vme->lastcode; + out->timereset = current_time - vme->timestarted; + out->polls = vme->polls; + out->noresponse = vme->noreply; + out->badformat = vme->badformat; + out->baddata = vme->baddata; + out->lastevent = vme->lastevent; + out->currentstatus = vme->status; + } else { + out->lencode = 0; + out->p_lastcode = ""; + out->polls = out->noresponse = 0; + out->badformat = out->baddata = 0; + out->timereset = 0; + out->currentstatus = out->lastevent = CEVNT_NOMINAL; + } + } +} + +/* + * vme_buginfo - return clock dependent debugging info + */ +static void +vme_buginfo( + int unit, + register struct refclockbug *bug, + struct peer * peer + ) +{ + register struct vmeunit *vme; + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "vme_buginfo: unit %d invalid)", unit); + return; + } + + if (!unitinuse[unit]) + return; + vme = vmeunits[unit]; + + bug->nvalues = 11; + bug->ntimes = 5; + if (vme->lasttime != 0) + bug->values[0] = current_time - vme->lasttime; + else + bug->values[0] = 0; + bug->values[2] = (u_long)vme->year; + bug->values[3] = (u_long)vme->day; + bug->values[4] = (u_long)vme->hour; + bug->values[5] = (u_long)vme->minute; + bug->values[6] = (u_long)vme->second; + bug->values[7] = (u_long)vme->nsec; + bug->values[9] = vme->yearstart; + bug->stimes = 0x1c; + bug->times[0] = vme->lastref; + bug->times[1] = vme->lastrec; +} +/* -------------------------------------------------------*/ +/* get_gpsvme_time() */ +/* R. Schmidt, USNO, 1995 */ +/* It's ugly, but hey, it works and its free */ + +#include "gps.h" /* defines for TrueTime GPS-VME */ + +#define PBIAS 193 /* 193 microsecs to read the GPS experimentally found */ + +struct vmedate * +get_gpsvme_time(void) +{ + extern struct vmedate *time_vme; + unsigned short set, hr, min, sec, ums, hms, status; + int ret; + char ti[3]; + + long tloc ; + time_t mktime(),time(); + struct tm *gmtime(), *gmt; + char *gpsmicro; + gpsmicro = (char *) malloc(7); + + *greg = (unsigned short *)malloc(sizeof(short) * NREGS); + + + /* reference the freeze command address general register 1 */ + set = *greg[0]; + /* read the registers : */ + /* get year */ + time_vme->year = (unsigned short) *greg[6]; + /* Get doy */ + time_vme->doy = (unsigned short) (*greg[5] & MASKDAY); + /* Get hour */ + time_vme->hr = (unsigned short) ((*greg[4] & MASKHI) >>8); + /* Get minutes */ + time_vme->mn = (unsigned short) (*greg[4] & MASKLO); + /* Get seconds */ + time_vme->sec = (unsigned short) (*greg[3] & MASKHI) >>8; + /* get microseconds in 2 parts and put together */ + ums = *greg[2]; + hms = *greg[3] & MASKLO; + + time_vme->status = (unsigned short) *greg[5] >>13; + + /* reference the unfreeze command address general register 1 */ + set = *greg[1]; + + sprintf(gpsmicro,"%2.2x%4.4x\0", hms, ums); + time_vme->frac = (u_long) gpsmicro; + + /* unmap_vme(); */ + + if (!status) { + return (NULL); /* fixed mjb lmco 12/20/99 */ + } + else + return (time_vme); +} + +#else +int refclock_gpsvme_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_heath.c b/ntpd/refclock_heath.c new file mode 100644 index 0000000..c16cef3 --- /dev/null +++ b/ntpd/refclock_heath.c @@ -0,0 +1,421 @@ +/* + * refclock_heath - clock driver for Heath GC-1000 and and GC-1000 II + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_HEATH) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* not HAVE_SYS_IOCTL_H */ + +/* + * This driver supports the Heath GC-1000 Most Accurate Clock, with + * RS232C Output Accessory. This is a WWV/WWVH receiver somewhat less + * robust than other supported receivers. Its claimed accuracy is 100 ms + * when actually synchronized to the broadcast signal, but this doesn't + * happen even most of the time, due to propagation conditions, ambient + * noise sources, etc. When not synchronized, the accuracy is at the + * whim of the internal clock oscillator, which can wander into the + * sunset without warning. Since the indicated precision is 100 ms, + * expect a host synchronized only to this thing to wander to and fro, + * occasionally being rudely stepped when the offset exceeds the default + * clock_max of 128 ms. + * + * There are two GC-1000 versions supported by this driver. The original + * GC-1000 with RS-232 output first appeared in 1983, but dissapeared + * from the market a few years later. The GC-1000 II with RS-232 output + * first appeared circa 1990, but apparently is no longer manufactured. + * The two models differ considerably, both in interface and commands. + * The GC-1000 has a pseudo-bipolar timecode output triggered by a RTS + * transition. The timecode includes both the day of year and time of + * day. The GC-1000 II has a true bipolar output and a complement of + * single character commands. The timecode includes only the time of + * day. + * + * GC-1000 + * + * The internal DIPswitches should be set to operate in MANUAL mode. The + * external DIPswitches should be set to GMT and 24-hour format. + * + * In MANUAL mode the clock responds to a rising edge of the request to + * send (RTS) modem control line by sending the timecode. Therefore, it + * is necessary that the operating system implement the TIOCMBIC and + * TIOCMBIS ioctl system calls and TIOCM_RTS control bit. Present + * restrictions require the use of a POSIX-compatible programming + * interface, although other interfaces may work as well. + * + * A simple hardware modification to the clock can be made which + * prevents the clock hearing the request to send (RTS) if the HI SPEC + * lamp is out. Route the HISPEC signal to the tone decoder board pin + * 19, from the display, pin 19. Isolate pin 19 of the decoder board + * first, but maintain connection with pin 10. Also isolate pin 38 of + * the CPU on the tone board, and use half an added 7400 to gate the + * original signal to pin 38 with that from pin 19. + * + * The clock message consists of 23 ASCII printing characters in the + * following format: + * + * hh:mm:ss.f AM dd/mm/yr<cr> + * + * hh:mm:ss.f = hours, minutes, seconds + * f = deciseconds ('?' when out of spec) + * AM/PM/bb = blank in 24-hour mode + * dd/mm/yr = day, month, year + * + * The alarm condition is indicated by '?', rather than a digit, at f. + * Note that 0?:??:??.? is displayed before synchronization is first + * established and hh:mm:ss.? once synchronization is established and + * then lost again for about a day. + * + * GC-1000 II + * + * Commands consist of a single letter and are case sensitive. When + * enterred in lower case, a description of the action performed is + * displayed. When enterred in upper case the action is performed. + * Following is a summary of descriptions as displayed by the clock: + * + * The clock responds with a command The 'A' command returns an ASCII + * local time string: HH:MM:SS.T xx<CR>, where + * + * HH = hours + * MM = minutes + * SS = seconds + * T = tenths-of-seconds + * xx = 'AM', 'PM', or ' ' + * <CR> = carriage return + * + * The 'D' command returns 24 pairs of bytes containing the variable + * divisor value at the end of each of the previous 24 hours. This + * allows the timebase trimming process to be observed. UTC hour 00 is + * always returned first. The first byte of each pair is the high byte + * of (variable divisor * 16); the second byte is the low byte of + * (variable divisor * 16). For example, the byte pair 3C 10 would be + * returned for a divisor of 03C1 hex (961 decimal). + * + * The 'I' command returns: | TH | TL | ER | DH | DL | U1 | I1 | I2 | , + * where + * + * TH = minutes since timebase last trimmed (high byte) + * TL = minutes since timebase last trimmed (low byte) + * ER = last accumulated error in 1.25 ms increments + * DH = high byte of (current variable divisor * 16) + * DL = low byte of (current variable divisor * 16) + * U1 = UT1 offset (/.1 s): | + | 4 | 2 | 1 | 0 | 0 | 0 | 0 | + * I1 = information byte 1: | W | C | D | I | U | T | Z | 1 | , + * where + * + * W = set by WWV(H) + * C = CAPTURE LED on + * D = TRIM DN LED on + * I = HI SPEC LED on + * U = TRIM UP LED on + * T = DST switch on + * Z = UTC switch on + * 1 = UT1 switch on + * + * I2 = information byte 2: | 8 | 8 | 4 | 2 | 1 | D | d | S | , + * where + * + * 8, 8, 4, 2, 1 = TIME ZONE switch settings + * D = DST bit (#55) in last-received frame + * d = DST bit (#2) in last-received frame + * S = clock is in simulation mode + * + * The 'P' command returns 24 bytes containing the number of frames + * received without error during UTC hours 00 through 23, providing an + * indication of hourly propagation. These bytes are updated each hour + * to reflect the previous 24 hour period. UTC hour 00 is always + * returned first. + * + * The 'T' command returns the UTC time: | HH | MM | SS | T0 | , where + * HH = tens-of-hours and hours (packed BCD) + * MM = tens-of-minutes and minutes (packed BCD) + * SS = tens-of-seconds and seconds (packed BCD) + * T = tenths-of-seconds (BCD) + * + * Fudge Factors + * + * A fudge time1 value of .04 s appears to center the clock offset + * residuals. The fudge time2 parameter is the local time offset east of + * Greenwich, which depends on DST. Sorry about that, but the clock + * gives no hint on what the DIPswitches say. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/heath%d" /* device name and unit */ +#define PRECISION (-4) /* precision assumed (about 100 ms) */ +#define REFID "WWV\0" /* reference ID */ +#define DESCRIPTION "Heath GC-1000 Most Accurate Clock" /* WRU */ + +#define LENHEATH1 23 /* min timecode length */ +#define LENHEATH2 13 /* min timecode length */ + +/* + * Tables to compute the ddd of year form icky dd/mm timecode. Viva la + * leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Baud rate table. The GC-1000 supports 1200, 2400 and 4800; the + * GC-1000 II supports only 9600. + */ +static int speed[] = {B1200, B2400, B4800, B9600}; + +/* + * Function prototypes + */ +static int heath_start P((int, struct peer *)); +static void heath_shutdown P((int, struct peer *)); +static void heath_receive P((struct recvbuf *)); +static void heath_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_heath = { + heath_start, /* start up driver */ + heath_shutdown, /* shut down driver */ + heath_poll, /* transmit poll message */ + noentry, /* not used (old heath_control) */ + noentry, /* initialize driver */ + noentry, /* not used (old heath_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * heath_start - open the devices and initialize data for processing + */ +static int +heath_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, speed[peer->ttl & 0x3], 0))) + return (0); + pp = peer->procptr; + pp->io.clock_recv = heath_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + peer->burst = NSTAGE; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * heath_shutdown - shut down the clock + */ +static void +heath_shutdown( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + io_closeclock(&pp->io); +} + + +/* + * heath_receive - receive data from the serial interface + */ +static void +heath_receive( + struct recvbuf *rbufp + ) +{ + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + int month, day; + int i; + char dsec, a[5]; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, + &trtmp); + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + switch (pp->lencode) { + + /* + * GC-1000 timecode format: "hh:mm:ss.f AM mm/dd/yy" + * GC-1000 II timecode format: "hh:mm:ss.f " + */ + case LENHEATH1: + if (sscanf(pp->a_lastcode, + "%2d:%2d:%2d.%c%5c%2d/%2d/%2d", &pp->hour, + &pp->minute, &pp->second, &dsec, a, &month, &day, + &pp->year) != 8) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + break; + + /* + * GC-1000 II timecode format: "hh:mm:ss.f " + */ + case LENHEATH2: + if (sscanf(pp->a_lastcode, "%2d:%2d:%2d.%c", &pp->hour, + &pp->minute, &pp->second, &dsec) != 4) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + break; + + default: + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * We determine the day of the year from the DIPswitches. This + * should be fixed, since somebody might forget to set them. + * Someday this hazard will be fixed by a fiendish scheme that + * looks at the timecode and year the radio shows, then computes + * the residue of the seconds mod the seconds in a leap cycle. + * If in the third year of that cycle and the third and later + * months of that year, add one to the day. Then, correct the + * timecode accordingly. Icky pooh. This bit of nonsense could + * be avoided if the engineers had been required to write a + * device driver before finalizing the timecode format. + */ + if (month < 1 || month > 12 || day < 1) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + if (pp->year % 4) { + if (day > day1tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } else { + if (day > day2tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } + pp->day = day; + + /* + * Determine synchronization and last update + */ + if (!isdigit((int)dsec)) + pp->leap = LEAP_NOTINSYNC; + else { + pp->nsec = (dsec - '0') * 100000000; + pp->leap = LEAP_NOWARNING; + } + if (!refclock_process(pp)) + refclock_report(peer, CEVNT_BADTIME); +} + + +/* + * heath_poll - called by the transmit procedure + */ +static void +heath_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + int bits = TIOCM_RTS; + + /* + * At each poll we check for timeout and toggle the RTS modem + * control line, then take a timestamp. Presumably, this is the + * event the radio captures to generate the timecode. + * Apparently, the radio takes about a second to make up its + * mind to send a timecode, so the receive timestamp is + * worthless. + */ + pp = peer->procptr; + + /* + * We toggle the RTS modem control lead (GC-1000) and sent a T + * (GC-1000 II) to kick a timecode loose from the radio. This + * code works only for POSIX and SYSV interfaces. With bsd you + * are on your own. We take a timestamp between the up and down + * edges to lengthen the pulse, which should be about 50 usec on + * a Sun IPC. With hotshot CPUs, the pulse might get too short. + * Later. + */ + if (ioctl(pp->io.fd, TIOCMBIC, (char *)&bits) < 0) + refclock_report(peer, CEVNT_FAULT); + get_systime(&pp->lastrec); + if (write(pp->io.fd, "T", 1) != 1) + refclock_report(peer, CEVNT_FAULT); + ioctl(pp->io.fd, TIOCMBIS, (char *)&bits); + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("heath: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + peer->burst = MAXSTAGE; + pp->polls++; +} + +#else +int refclock_heath_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_hopfpci.c b/ntpd/refclock_hopfpci.c new file mode 100644 index 0000000..1b02319 --- /dev/null +++ b/ntpd/refclock_hopfpci.c @@ -0,0 +1,273 @@ +/* + * refclock_hopfpci.c + * + * - clock driver for hopf 6039 PCI board (GPS or DCF77) + * Bernd Altmeier altmeier@atlsoft.de + * + * latest source and further information can be found at: + * http://www.ATLSoft.de/ntp + * + * In order to run this driver you have to install and test + * the PCI-board driver for your system first. + * + * On Linux/UNIX + * + * The driver attempts to open the device /dev/hopf6039 . + * The device entry will be made by the installation process of + * the kernel module for the PCI-bus board. The driver sources + * belongs to the delivery equipment of the PCI-board. + * + * On Windows NT/2000 + * + * The driver attempts to open the device by calling the function + * "OpenHopfDevice()". This function will be installed by the + * Device Driver for the PCI-bus board. The driver belongs to the + * delivery equipment of the PCI-board. + * + * + * Start 21.03.2000 Revision: 01.20 + * changes 22.12.2000 Revision: 01.40 flag1 = 1 sync even if Quarz + * + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_HOPF_PCI) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#undef fileno +#include <ctype.h> +#undef fileno + +#ifndef SYS_WINNT +# include <sys/ipc.h> +# include <sys/ioctl.h> +# include <assert.h> +# include <unistd.h> +# include <stdio.h> +# include "hopf6039.h" +#else +# include "hopf_PCI_io.h" +#endif + +/* + * hopfpci interface definitions + */ +#define PRECISION (-10) /* precision assumed (1 ms) */ +#define REFID "hopf" /* reference ID */ +#define DESCRIPTION "hopf Elektronik PCI radio board" + +#define NSAMPLES 3 /* stages of median filter */ +#ifndef SYS_WINNT +# define DEVICE "/dev/hopf6039" /* device name inode*/ +#else +# define DEVICE "hopf6039" /* device name WinNT */ +#endif + +#define LEWAPWAR 0x20 /* leap second warning bit */ + +#define HOPF_OPMODE 0xC0 /* operation mode mask */ +#define HOPF_INVALID 0x00 /* no time code available */ +#define HOPF_INTERNAL 0x40 /* internal clock */ +#define HOPF_RADIO 0x80 /* radio clock */ +#define HOPF_RADIOHP 0xC0 /* high precision radio clock */ + + +/* + * hopfclock unit control structure. + */ +struct hopfclock_unit { + short unit; /* NTP refclock unit number */ + char leap_status; /* leap second flag */ +}; +int fd; /* file descr. */ + +/* + * Function prototypes + */ +static int hopfpci_start (int, struct peer *); +static void hopfpci_shutdown (int, struct peer *); +static void hopfpci_poll (int unit, struct peer *); + +/* + * Transfer vector + */ +struct refclock refclock_hopfpci = { + hopfpci_start, /* start up driver */ + hopfpci_shutdown, /* shut down driver */ + hopfpci_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +/* + * hopfpci_start - attach to hopf PCI board 6039 + */ +static int +hopfpci_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + struct hopfclock_unit *up; + + /* + * Allocate and initialize unit structure + */ + up = (struct hopfclock_unit *) emalloc(sizeof(struct hopfclock_unit)); + + if (!(up)) { + msyslog(LOG_ERR, "hopfPCIClock(%d) emalloc: %m",unit); +#ifdef DEBUG + printf("hopfPCIClock(%d) emalloc\n",unit); +#endif + return (0); + } + memset((char *)up, 0, sizeof(struct hopfclock_unit)); + +#ifndef SYS_WINNT + + fd = open(DEVICE,O_RDWR); /* try to open hopf clock device */ + +#else + if (!OpenHopfDevice()){ + msyslog(LOG_ERR,"Start: %s unit: %d failed!",DEVICE,unit); + return (0); + } +#endif + + pp = peer->procptr; + pp->io.clock_recv = noentry; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = INVALID_SOCKET; + pp->unitptr = (caddr_t)up; + + get_systime(&pp->lastrec); + + /* + * Initialize miscellaneous peer variables + */ + if (pp->unitptr!=0) { + memcpy((char *)&pp->refid, REFID, 4); + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + up->leap_status = 0; + up->unit = (short) unit; + return (1); + } + else { + return 0; + } +} + + +/* + * hopfpci_shutdown - shut down the clock + */ +static void +hopfpci_shutdown( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + register struct hopfpciTime *up; + + pp = peer->procptr; + up = (struct hopfpciTime *)pp->unitptr; + +#ifndef SYS_WINNT + close(fd); +#else + CloseHopfDevice(); +/* UnmapViewOfFile (up); */ +#endif +} + + +/* + * hopfpci_poll - called by the transmit procedure + */ +static void +hopfpci_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + register struct hopfpciTime *up; + HOPFTIME m_time; + + pp = peer->procptr; + up = (struct hopfpciTime *)pp->unitptr; + +#ifndef SYS_WINNT + ioctl(fd,HOPF_CLOCK_GET_UTC,&m_time); +#else + GetHopfSystemTime(&m_time); +#endif + pp->polls++; + + pp->day = ymd2yd(m_time.wYear,m_time.wMonth,m_time.wDay); + pp->hour = m_time.wHour; + pp->minute = m_time.wMinute; + pp->second = m_time.wSecond; + pp->nsec = m_time.wMilliseconds * 1000000; + if (m_time.wStatus & LEWAPWAR) + pp->leap = LEAP_ADDSECOND; + else + pp->leap = LEAP_NOWARNING; + + sprintf(pp->a_lastcode,"ST: %02X T: %02d:%02d:%02d.%03ld D: %02d.%02d.%04d", + m_time.wStatus, pp->hour, pp->minute, pp->second, + pp->nsec / 1000000, m_time.wDay, m_time.wMonth, m_time.wYear); + pp->lencode = (u_short)strlen(pp->a_lastcode); + + get_systime(&pp->lastrec); + + /* + * If clock has no valid status then report error and exit + */ + if ((m_time.wStatus & HOPF_OPMODE) == HOPF_INVALID) { /* time ok? */ + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } + + /* + * Test if time is running on internal quarz + * if CLK_FLAG1 is set, sychronize even if no radio operation + */ + + if ((m_time.wStatus & HOPF_OPMODE) == HOPF_INTERNAL){ + if ((pp->sloppyclockflag & CLK_FLAG1) == 0) { + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } + } + + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + return; +} + +#else +int refclock_hopfpci_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_hopfser.c b/ntpd/refclock_hopfser.c new file mode 100644 index 0000000..1d27333 --- /dev/null +++ b/ntpd/refclock_hopfser.c @@ -0,0 +1,378 @@ +/* + * + * refclock_hopfser.c + * - clock driver for hopf serial boards (GPS or DCF77) + * + * Date: 30.03.2000 Revision: 01.10 + * + * latest source and further information can be found at: + * http://www.ATLSoft.de/ntp + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(SYS_WINNT) +#undef close +#define close closesocket +#endif + +#if defined(REFCLOCK) && (defined(CLOCK_HOPF_SERIAL)) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_control.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#if defined HAVE_SYS_MODEM_H +# include <sys/modem.h> +# define TIOCMSET MCSETA +# define TIOCMGET MCGETA +# define TIOCM_RTS MRTS +#endif + +#ifdef HAVE_TERMIOS_H +# ifdef TERMIOS_NEEDS__SVID3 +# define _SVID3 +# endif +# include <termios.h> +# ifdef TERMIOS_NEEDS__SVID3 +# undef _SVID3 +# endif +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +/* + * clock definitions + */ +#define DESCRIPTION "hopf Elektronik serial clock" /* Long name */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "hopf\0" /* reference ID */ +/* + * I/O definitions + */ +#define DEVICE "/dev/hopfclock%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ + + +#define STX 0x02 +#define ETX 0x03 +#define CR 0x0c +#define LF 0x0a + +/* parse states */ +#define REC_QUEUE_EMPTY 0 +#define REC_QUEUE_FULL 1 + +#define HOPF_OPMODE 0x0C /* operation mode mask */ +#define HOPF_INVALID 0x00 /* no time code available */ +#define HOPF_INTERNAL 0x04 /* internal clock */ +#define HOPF_RADIO 0x08 /* radio clock */ +#define HOPF_RADIOHP 0x0C /* high precision radio clock */ + +/* + * hopfclock unit control structure. + */ +struct hopfclock_unit { + l_fp laststamp; /* last receive timestamp */ + short unit; /* NTP refclock unit number */ + u_long polled; /* flag to detect noreplies */ + char leap_status; /* leap second flag */ + int rpt_next; +}; + +/* + * Function prototypes + */ + +static int hopfserial_start P((int, struct peer *)); +static void hopfserial_shutdown P((int, struct peer *)); +static void hopfserial_receive P((struct recvbuf *)); +static void hopfserial_poll P((int, struct peer *)); +/* static void hopfserial_io P((struct recvbuf *)); */ +/* + * Transfer vector + */ +struct refclock refclock_hopfser = { + hopfserial_start, /* start up driver */ + hopfserial_shutdown, /* shut down driver */ + hopfserial_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +/* + * hopfserial_start - open the devices and initialize data for processing + */ +static int +hopfserial_start ( + int unit, + struct peer *peer + ) +{ + register struct hopfclock_unit *up; + struct refclockproc *pp; + int fd; + char gpsdev[20]; + +#ifdef SYS_WINNT + (void) sprintf(gpsdev, "COM%d:", unit); +#else + (void) sprintf(gpsdev, DEVICE, unit); +#endif + /* LDISC_STD, LDISC_RAW + * Open serial port. Use CLK line discipline, if available. + */ + fd = refclock_open(gpsdev, SPEED232, LDISC_CLK); + if (fd <= 0) { +#ifdef DEBUG + printf("hopfSerialClock(%d) start: open %s failed\n", unit, gpsdev); +#endif + return 0; + } + + msyslog(LOG_NOTICE, "hopfSerialClock(%d) fd: %d dev: %s", unit, fd, + gpsdev); + + /* + * Allocate and initialize unit structure + */ + up = (struct hopfclock_unit *) emalloc(sizeof(struct hopfclock_unit)); + + if (!(up)) { + msyslog(LOG_ERR, "hopfSerialClock(%d) emalloc: %m",unit); +#ifdef DEBUG + printf("hopfSerialClock(%d) emalloc\n",unit); +#endif + (void) close(fd); + return (0); + } + + memset((char *)up, 0, sizeof(struct hopfclock_unit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = hopfserial_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { +#ifdef DEBUG + printf("hopfSerialClock(%d) io_addclock\n",unit); +#endif + (void) close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + pp->clockdesc = DESCRIPTION; + peer->precision = PRECISION; + peer->burst = NSTAGE; + memcpy((char *)&pp->refid, REFID, 4); + + up->leap_status = 0; + up->unit = (short) unit; + + return (1); +} + + +/* + * hopfserial_shutdown - shut down the clock + */ +static void +hopfserial_shutdown ( + int unit, + struct peer *peer + ) +{ + register struct hopfclock_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct hopfclock_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + + +/* + * hopfserial_receive - receive data from the serial interface + */ + +static void +hopfserial_receive ( + struct recvbuf *rbufp + ) +{ + struct hopfclock_unit *up; + struct refclockproc *pp; + struct peer *peer; + + int synch; /* synchhronization indicator */ + int DoW; /* Dow */ + + int day, month; /* ddd conversion */ + + /* + * Initialize pointers and read the timecode and timestamp. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct hopfclock_unit *)pp->unitptr; + + if (up->rpt_next == 0 ) + return; + + + up->rpt_next = 0; /* wait until next poll interval occur */ + + pp->lencode = (u_short)refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); + + if (pp->lencode == 0) + return; + + sscanf(pp->a_lastcode, +#if 1 + "%1x%1x%2d%2d%2d%2d%2d%2d", /* ...cr,lf */ +#else + "%*c%1x%1x%2d%2d%2d%2d%2d%2d", /* stx...cr,lf,etx */ +#endif + &synch, + &DoW, + &pp->hour, + &pp->minute, + &pp->second, + &day, + &month, + &pp->year); + + + /* + Validate received values at least enough to prevent internal + array-bounds problems, etc. + */ + if((pp->hour < 0) || (pp->hour > 23) || + (pp->minute < 0) || (pp->minute > 59) || + (pp->second < 0) || (pp->second > 60) /*Allow for leap seconds.*/ || + (day < 1) || (day > 31) || + (month < 1) || (month > 12) || + (pp->year < 0) || (pp->year > 99)) { + /* Data out of range. */ + refclock_report(peer, CEVNT_BADREPLY); + return; + } + /* + some preparations + */ + pp->day = ymd2yd(pp->year,month,day); + pp->leap=0; + + /* Year-2000 check! */ + /* wrap 2-digit date into 4-digit */ + + if(pp->year < YEAR_PIVOT) { pp->year += 100; } /* < 98 */ + pp->year += 1900; + + /* preparation for timecode ntpq rl command ! */ + +#if 0 + wsprintf(pp->a_lastcode, + "STATUS: %1X%1X, DATE: %02d.%02d.%04d TIME: %02d:%02d:%02d", + synch, + DoW, + day, + month, + pp->year, + pp->hour, + pp->minute, + pp->second); + + pp->lencode = strlen(pp->a_lastcode); + if ((synch && 0xc) == 0 ){ /* time ok? */ + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } +#endif + /* + * If clock has no valid status then report error and exit + */ + if ((synch & HOPF_OPMODE) == HOPF_INVALID ){ /* time ok? */ + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } + + /* + * Test if time is running on internal quarz + * if CLK_FLAG1 is set, sychronize even if no radio operation + */ + + if ((synch & HOPF_OPMODE) == HOPF_INTERNAL){ + if ((pp->sloppyclockflag & CLK_FLAG1) == 0) { + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } + } + + + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + +#if 0 + msyslog(LOG_ERR, " D:%x D:%d D:%d",synch,pp->minute,pp->second); +#endif + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + + return; +} + + +/* + * hopfserial_poll - called by the transmit procedure + * + */ +static void +hopfserial_poll ( + int unit, + struct peer *peer + ) +{ + register struct hopfclock_unit *up; + struct refclockproc *pp; + pp = peer->procptr; + + up = (struct hopfclock_unit *)pp->unitptr; + + pp->polls++; + up->rpt_next = 1; + +#if 0 + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#endif + + return; +} + +#else +int refclock_hopfser_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_hpgps.c b/ntpd/refclock_hpgps.c new file mode 100644 index 0000000..7c801bb --- /dev/null +++ b/ntpd/refclock_hpgps.c @@ -0,0 +1,604 @@ +/* + * refclock_hpgps - clock driver for HP 58503A GPS receiver + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_HPGPS) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* Version 0.1 April 1, 1995 + * 0.2 April 25, 1995 + * tolerant of missing timecode response prompt and sends + * clear status if prompt indicates error; + * can use either local time or UTC from receiver; + * can get receiver status screen via flag4 + * + * WARNING!: This driver is UNDER CONSTRUCTION + * Everything in here should be treated with suspicion. + * If it looks wrong, it probably is. + * + * Comments and/or questions to: Dave Vitanye + * Hewlett Packard Company + * dave@scd.hp.com + * (408) 553-2856 + * + * Thanks to the author of the PST driver, which was the starting point for + * this one. + * + * This driver supports the HP 58503A Time and Frequency Reference Receiver. + * This receiver uses HP SmartClock (TM) to implement an Enhanced GPS receiver. + * The receiver accuracy when locked to GPS in normal operation is better + * than 1 usec. The accuracy when operating in holdover is typically better + * than 10 usec. per day. + * + * The receiver should be operated with factory default settings. + * Initial driver operation: expects the receiver to be already locked + * to GPS, configured and able to output timecode format 2 messages. + * + * The driver uses the poll sequence :PTIME:TCODE? to get a response from + * the receiver. The receiver responds with a timecode string of ASCII + * printing characters, followed by a <cr><lf>, followed by a prompt string + * issued by the receiver, in the following format: + * T#yyyymmddhhmmssMFLRVcc<cr><lf>scpi > + * + * The driver processes the response at the <cr> and <lf>, so what the + * driver sees is the prompt from the previous poll, followed by this + * timecode. The prompt from the current poll is (usually) left unread until + * the next poll. So (except on the very first poll) the driver sees this: + * + * scpi > T#yyyymmddhhmmssMFLRVcc<cr><lf> + * + * The T is the on-time character, at 980 msec. before the next 1PPS edge. + * The # is the timecode format type. We look for format 2. + * Without any of the CLK or PPS stuff, then, the receiver buffer timestamp + * at the <cr> is 24 characters later, which is about 25 msec. at 9600 bps, + * so the first approximation for fudge time1 is nominally -0.955 seconds. + * This number probably needs adjusting for each machine / OS type, so far: + * -0.955000 on an HP 9000 Model 712/80 HP-UX 9.05 + * -0.953175 on an HP 9000 Model 370 HP-UX 9.10 + * + * This receiver also provides a 1PPS signal, but I haven't figured out + * how to deal with any of the CLK or PPS stuff yet. Stay tuned. + * + */ + +/* + * Fudge Factors + * + * Fudge time1 is used to accomodate the timecode serial interface adjustment. + * Fudge flag4 can be set to request a receiver status screen summary, which + * is recorded in the clockstats file. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/hpgps%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "GPS\0" /* reference ID */ +#define DESCRIPTION "HP 58503A GPS Time and Frequency Reference Receiver" + +#define SMAX 23*80+1 /* for :SYSTEM:PRINT? status screen response */ + +#define MTZONE 2 /* number of fields in timezone reply */ +#define MTCODET2 12 /* number of fields in timecode format T2 */ +#define NTCODET2 21 /* number of chars to checksum in format T2 */ + +/* + * Tables to compute the day of year from yyyymmdd timecode. + * Viva la leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Unit control structure + */ +struct hpgpsunit { + int pollcnt; /* poll message counter */ + int tzhour; /* timezone offset, hours */ + int tzminute; /* timezone offset, minutes */ + int linecnt; /* set for expected multiple line responses */ + char *lastptr; /* pointer to receiver response data */ + char statscrn[SMAX]; /* receiver status screen buffer */ +}; + +/* + * Function prototypes + */ +static int hpgps_start P((int, struct peer *)); +static void hpgps_shutdown P((int, struct peer *)); +static void hpgps_receive P((struct recvbuf *)); +static void hpgps_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_hpgps = { + hpgps_start, /* start up driver */ + hpgps_shutdown, /* shut down driver */ + hpgps_poll, /* transmit poll message */ + noentry, /* not used (old hpgps_control) */ + noentry, /* initialize driver */ + noentry, /* not used (old hpgps_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * hpgps_start - open the devices and initialize data for processing + */ +static int +hpgps_start( + int unit, + struct peer *peer + ) +{ + register struct hpgpsunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct hpgpsunit *) + emalloc(sizeof(struct hpgpsunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct hpgpsunit)); + pp = peer->procptr; + pp->io.clock_recv = hpgps_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->tzhour = 0; + up->tzminute = 0; + + *up->statscrn = '\0'; + up->lastptr = up->statscrn; + up->pollcnt = 2; + + /* + * Get the identifier string, which is logged but otherwise ignored, + * and get the local timezone information + */ + up->linecnt = 1; + if (write(pp->io.fd, "*IDN?\r:PTIME:TZONE?\r", 20) != 20) + refclock_report(peer, CEVNT_FAULT); + + return (1); +} + + +/* + * hpgps_shutdown - shut down the clock + */ +static void +hpgps_shutdown( + int unit, + struct peer *peer + ) +{ + register struct hpgpsunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct hpgpsunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * hpgps_receive - receive data from the serial interface + */ +static void +hpgps_receive( + struct recvbuf *rbufp + ) +{ + register struct hpgpsunit *up; + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + char tcodechar1; /* identifies timecode format */ + char tcodechar2; /* identifies timecode format */ + char timequal; /* time figure of merit: 0-9 */ + char freqqual; /* frequency figure of merit: 0-3 */ + char leapchar; /* leapsecond: + or 0 or - */ + char servchar; /* request for service: 0 = no, 1 = yes */ + char syncchar; /* time info is invalid: 0 = no, 1 = yes */ + short expectedsm; /* expected timecode byte checksum */ + short tcodechksm; /* computed timecode byte checksum */ + int i,m,n; + int month, day, lastday; + char *tcp; /* timecode pointer (skips over the prompt) */ + char prompt[BMAX]; /* prompt in response from receiver */ + + /* + * Initialize pointers and read the receiver response + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct hpgpsunit *)pp->unitptr; + *pp->a_lastcode = '\0'; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + +#ifdef DEBUG + if (debug) + printf("hpgps: lencode: %d timecode:%s\n", + pp->lencode, pp->a_lastcode); +#endif + + /* + * If there's no characters in the reply, we can quit now + */ + if (pp->lencode == 0) + return; + + /* + * If linecnt is greater than zero, we are getting information only, + * such as the receiver identification string or the receiver status + * screen, so put the receiver response at the end of the status + * screen buffer. When we have the last line, write the buffer to + * the clockstats file and return without further processing. + * + * If linecnt is zero, we are expecting either the timezone + * or a timecode. At this point, also write the response + * to the clockstats file, and go on to process the prompt (if any), + * timezone, or timecode and timestamp. + */ + + + if (up->linecnt-- > 0) { + if ((int)(pp->lencode + 2) <= (SMAX - (up->lastptr - up->statscrn))) { + *up->lastptr++ = '\n'; + (void)strcpy(up->lastptr, pp->a_lastcode); + up->lastptr += pp->lencode; + } + if (up->linecnt == 0) + record_clock_stats(&peer->srcadr, up->statscrn); + + return; + } + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + pp->lastrec = trtmp; + + up->lastptr = up->statscrn; + *up->lastptr = '\0'; + up->pollcnt = 2; + + /* + * We get down to business: get a prompt if one is there, issue + * a clear status command if it contains an error indication. + * Next, check for either the timezone reply or the timecode reply + * and decode it. If we don't recognize the reply, or don't get the + * proper number of decoded fields, or get an out of range timezone, + * or if the timecode checksum is bad, then we declare bad format + * and exit. + * + * Timezone format (including nominal prompt): + * scpi > -H,-M<cr><lf> + * + * Timecode format (including nominal prompt): + * scpi > T2yyyymmddhhmmssMFLRVcc<cr><lf> + * + */ + + (void)strcpy(prompt,pp->a_lastcode); + tcp = strrchr(pp->a_lastcode,'>'); + if (tcp == NULL) + tcp = pp->a_lastcode; + else + tcp++; + prompt[tcp - pp->a_lastcode] = '\0'; + while ((*tcp == ' ') || (*tcp == '\t')) tcp++; + + /* + * deal with an error indication in the prompt here + */ + if (strrchr(prompt,'E') > strrchr(prompt,'s')){ +#ifdef DEBUG + if (debug) + printf("hpgps: error indicated in prompt: %s\n", prompt); +#endif + if (write(pp->io.fd, "*CLS\r\r", 6) != 6) + refclock_report(peer, CEVNT_FAULT); + } + + /* + * make sure we got a timezone or timecode format and + * then process accordingly + */ + m = sscanf(tcp,"%c%c", &tcodechar1, &tcodechar2); + + if (m != 2){ +#ifdef DEBUG + if (debug) + printf("hpgps: no format indicator\n"); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + switch (tcodechar1) { + + case '+': + case '-': + m = sscanf(tcp,"%d,%d", &up->tzhour, &up->tzminute); + if (m != MTZONE) { +#ifdef DEBUG + if (debug) + printf("hpgps: only %d fields recognized in timezone\n", m); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } + if ((up->tzhour < -12) || (up->tzhour > 13) || + (up->tzminute < -59) || (up->tzminute > 59)){ +#ifdef DEBUG + if (debug) + printf("hpgps: timezone %d, %d out of range\n", + up->tzhour, up->tzminute); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } + return; + + case 'T': + break; + + default: +#ifdef DEBUG + if (debug) + printf("hpgps: unrecognized reply format %c%c\n", + tcodechar1, tcodechar2); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } /* end of tcodechar1 switch */ + + + switch (tcodechar2) { + + case '2': + m = sscanf(tcp,"%*c%*c%4d%2d%2d%2d%2d%2d%c%c%c%c%c%2hx", + &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second, + &timequal, &freqqual, &leapchar, &servchar, &syncchar, + &expectedsm); + n = NTCODET2; + + if (m != MTCODET2){ +#ifdef DEBUG + if (debug) + printf("hpgps: only %d fields recognized in timecode\n", m); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } + break; + + default: +#ifdef DEBUG + if (debug) + printf("hpgps: unrecognized timecode format %c%c\n", + tcodechar1, tcodechar2); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } /* end of tcodechar2 format switch */ + + /* + * Compute and verify the checksum. + * Characters are summed starting at tcodechar1, ending at just + * before the expected checksum. Bail out if incorrect. + */ + tcodechksm = 0; + while (n-- > 0) tcodechksm += *tcp++; + tcodechksm &= 0x00ff; + + if (tcodechksm != expectedsm) { +#ifdef DEBUG + if (debug) + printf("hpgps: checksum %2hX doesn't match %2hX expected\n", + tcodechksm, expectedsm); +#endif + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Compute the day of year from the yyyymmdd format. + */ + if (month < 1 || month > 12 || day < 1) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + if ( ! isleap_4(pp->year) ) { /* Y2KFixes */ + /* not a leap year */ + if (day > day1tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) day += day1tab[i]; + lastday = 365; + } else { + /* a leap year */ + if (day > day2tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) day += day2tab[i]; + lastday = 366; + } + + /* + * Deal with the timezone offset here. The receiver timecode is in + * local time = UTC + :PTIME:TZONE, so SUBTRACT the timezone values. + * For example, Pacific Standard Time is -8 hours , 0 minutes. + * Deal with the underflows and overflows. + */ + pp->minute -= up->tzminute; + pp->hour -= up->tzhour; + + if (pp->minute < 0) { + pp->minute += 60; + pp->hour--; + } + if (pp->minute > 59) { + pp->minute -= 60; + pp->hour++; + } + if (pp->hour < 0) { + pp->hour += 24; + day--; + if (day < 1) { + pp->year--; + if ( isleap_4(pp->year) ) /* Y2KFixes */ + day = 366; + else + day = 365; + } + } + + if (pp->hour > 23) { + pp->hour -= 24; + day++; + if (day > lastday) { + pp->year++; + day = 1; + } + } + + pp->day = day; + + /* + * Decode the MFLRV indicators. + * NEED TO FIGURE OUT how to deal with the request for service, + * time quality, and frequency quality indicators some day. + */ + if (syncchar != '0') { + pp->leap = LEAP_NOTINSYNC; + } + else { + switch (leapchar) { + + case '+': + pp->leap = LEAP_ADDSECOND; + break; + + case '0': + pp->leap = LEAP_NOWARNING; + break; + + case '-': + pp->leap = LEAP_DELSECOND; + break; + + default: +#ifdef DEBUG + if (debug) + printf("hpgps: unrecognized leap indicator: %c\n", + leapchar); +#endif + refclock_report(peer, CEVNT_BADTIME); + return; + } /* end of leapchar switch */ + } + + /* + * Process the new sample in the median filter and determine the + * reference clock offset and dispersion. We use lastrec as both + * the reference time and receive time in order to avoid being + * cute, like setting the reference time later than the receive + * time, which may cause a paranoid protocol module to chuck out + * the data. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + + /* + * If CLK_FLAG4 is set, ask for the status screen response. + */ + if (pp->sloppyclockflag & CLK_FLAG4){ + up->linecnt = 22; + if (write(pp->io.fd, ":SYSTEM:PRINT?\r", 15) != 15) + refclock_report(peer, CEVNT_FAULT); + } +} + + +/* + * hpgps_poll - called by the transmit procedure + */ +static void +hpgps_poll( + int unit, + struct peer *peer + ) +{ + register struct hpgpsunit *up; + struct refclockproc *pp; + + /* + * Time to poll the clock. The HP 58503A responds to a + * ":PTIME:TCODE?" by returning a timecode in the format specified + * above. If nothing is heard from the clock for two polls, + * declare a timeout and keep going. + */ + pp = peer->procptr; + up = (struct hpgpsunit *)pp->unitptr; + if (up->pollcnt == 0) + refclock_report(peer, CEVNT_TIMEOUT); + else + up->pollcnt--; + if (write(pp->io.fd, ":PTIME:TCODE?\r", 14) != 14) { + refclock_report(peer, CEVNT_FAULT); + } + else + pp->polls++; +} + +#else +int refclock_hpgps_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_irig.c b/ntpd/refclock_irig.c new file mode 100644 index 0000000..0b35368 --- /dev/null +++ b/ntpd/refclock_irig.c @@ -0,0 +1,1049 @@ +/* + * refclock_irig - audio IRIG-B/E demodulator/decoder + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_IRIG) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> +#include <math.h> +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +#include "audio.h" + +/* + * Audio IRIG-B/E demodulator/decoder + * + * This driver receives, demodulates and decodes IRIG-B/E signals when + * connected to the audio codec /dev/audio. The IRIG signal format is an + * amplitude-modulated carrier with pulse-width modulated data bits. For + * IRIG-B, the carrier frequency is 1000 Hz and bit rate 100 b/s; for + * IRIG-E, the carrier frequenchy is 100 Hz and bit rate 10 b/s. The + * driver automatically recognizes which format is in use. + * + * The program processes 8000-Hz mu-law companded samples using separate + * signal filters for IRIG-B and IRIG-E, a comb filter, envelope + * detector and automatic threshold corrector. Cycle crossings relative + * to the corrected slice level determine the width of each pulse and + * its value - zero, one or position identifier. The data encode 20 BCD + * digits which determine the second, minute, hour and day of the year + * and sometimes the year and synchronization condition. The comb filter + * exponentially averages the corresponding samples of successive baud + * intervals in order to reliably identify the reference carrier cycle. + * A type-II phase-lock loop (PLL) performs additional integration and + * interpolation to accurately determine the zero crossing of that + * cycle, which determines the reference timestamp. A pulse-width + * discriminator demodulates the data pulses, which are then encoded as + * the BCD digits of the timecode. + * + * The timecode and reference timestamp are updated once each second + * with IRIG-B (ten seconds with IRIG-E) and local clock offset samples + * saved for later processing. At poll intervals of 64 s, the saved + * samples are processed by a trimmed-mean filter and used to update the + * system clock. + * + * An automatic gain control feature provides protection against + * overdriven or underdriven input signal amplitudes. It is designed to + * maintain adequate demodulator signal amplitude while avoiding + * occasional noise spikes. In order to assure reliable capture, the + * decompanded input signal amplitude must be greater than 100 units and + * the codec sample frequency error less than 250 PPM (.025 percent). + * + * The program performs a number of error checks to protect against + * overdriven or underdriven input signal levels, incorrect signal + * format or improper hardware configuration. Specifically, if any of + * the following errors occur for a time measurement, the data are + * rejected. + * + * o The peak carrier amplitude is less than DRPOUT (100). This usually + * means dead IRIG signal source, broken cable or wrong input port. + * + * o The frequency error is greater than MAXFREQ +-250 PPM (.025%). This + * usually means broken codec hardware or wrong codec configuration. + * + * o The modulation index is less than MODMIN (0.5). This usually means + * overdriven IRIG signal or wrong IRIG format. + * + * o A frame synchronization error has occurred. This usually means + * wrong IRIG signal format or the IRIG signal source has lost + * synchronization (signature control). + * + * o A data decoding error has occurred. This usually means wrong IRIG + * signal format. + * + * o The current second of the day is not exactly one greater than the + * previous one. This usually means a very noisy IRIG signal or + * insufficient CPU resources. + * + * o An audio codec error (overrun) occurred. This usually means + * insufficient CPU resources, as sometimes happens with Sun SPARC + * IPCs when doing something useful. + * + * Note that additional checks are done elsewhere in the reference clock + * interface routines. + * + * Debugging aids + * + * The timecode format used for debugging and data recording includes + * data helpful in diagnosing problems with the IRIG signal and codec + * connections. With debugging enabled (-d on the ntpd command line), + * the driver produces one line for each timecode in the following + * format: + * + * 00 1 98 23 19:26:52 721 143 0.694 20 0.1 66.5 3094572411.00027 + * + * The most recent line is also written to the clockstats file at 64-s + * intervals. + * + * The first field contains the error flags in hex, where the hex bits + * are interpreted as below. This is followed by the IRIG status + * indicator, year of century, day of year and time of day. The status + * indicator and year are not produced by some IRIG devices. Following + * these fields are the signal amplitude (0-8100), codec gain (0-255), + * modulation index (0-1), time constant (2-20), carrier phase error + * (us) and carrier frequency error (PPM). The last field is the on-time + * timestamp in NTP format. + * + * The fraction part of the on-time timestamp is a good indicator of how + * well the driver is doing. With an UltrSPARC 30 and Solaris 2.7, this + * thing can keep the clock within a few tens of microseconds relative + * to the IRIG-B signal. Accuracy with IRIG-E is about ten times worse. + * Unfortunately, Sun broke the 2.7 audio driver in 2.8, which has a + * 10-ms sawtooth modulation. The driver attempts to remove the + * modulation by some clever estimation techniques which mostly work. + * Your experience may vary. + * + * Unlike other drivers, which can have multiple instantiations, this + * one supports only one. It does not seem likely that more than one + * audio codec would be useful in a single machine. More than one would + * probably chew up too much CPU time anyway. + * + * Fudge factors + * + * Fudge flag4 causes the dubugging output described above to be + * recorded in the clockstats file. When the audio driver is compiled, + * fudge flag2 selects the audio input port, where 0 is the mike port + * (default) and 1 is the line-in port. It does not seem useful to + * select the compact disc player port. Fudge flag3 enables audio + * monitoring of the input signal. For this purpose, the monitor gain is + * set to a default value. Fudgetime2 is used as a frequency vernier for + * broken codec sample frequency. + */ +/* + * Interface definitions + */ +#define DEVICE_AUDIO "/dev/audio" /* audio device name */ +#define PRECISION (-17) /* precision assumed (about 10 us) */ +#define REFID "IRIG" /* reference ID */ +#define DESCRIPTION "Generic IRIG Audio Driver" /* WRU */ +#define AUDIO_BUFSIZ 320 /* audio buffer size (40 ms) */ +#define SECOND 8000 /* nominal sample rate (Hz) */ +#define BAUD 80 /* samples per baud interval */ +#define OFFSET 128 /* companded sample offset */ +#define SIZE 256 /* decompanding table size */ +#define CYCLE 8 /* samples per carrier cycle */ +#define SUBFLD 10 /* bits per subfield */ +#define FIELD 10 /* subfields per field */ +#define MINTC 2 /* min PLL time constant */ +#define MAXTC 20 /* max PLL time constant max */ +#define MAXSIG 6000. /* maximum signal level */ +#define MAXCLP 100 /* max clips above reference per s */ +#define DRPOUT 100. /* dropout signal level */ +#define MODMIN 0.5 /* minimum modulation index */ +#define MAXFREQ (250e-6 * SECOND) /* freq tolerance (.025%) */ +#define PI 3.1415926535 /* the real thing */ +#ifdef IRIG_SUCKS +#define WIGGLE 11 /* wiggle filter length */ +#endif /* IRIG_SUCKS */ + +/* + * Experimentally determined filter delays + */ +#define IRIG_B .0019 /* IRIG-B filter delay */ +#define IRIG_E .0019 /* IRIG-E filter delay */ + +/* + * Data bit definitions + */ +#define BIT0 0 /* zero */ +#define BIT1 1 /* one */ +#define BITP 2 /* position identifier */ + +/* + * Error flags (up->errflg) + */ +#define IRIG_ERR_AMP 0x01 /* low carrier amplitude */ +#define IRIG_ERR_FREQ 0x02 /* frequency tolerance exceeded */ +#define IRIG_ERR_MOD 0x04 /* low modulation index */ +#define IRIG_ERR_SYNCH 0x08 /* frame synch error */ +#define IRIG_ERR_DECODE 0x10 /* frame decoding error */ +#define IRIG_ERR_CHECK 0x20 /* second numbering discrepancy */ +#define IRIG_ERR_ERROR 0x40 /* codec error (overrun) */ +#define IRIG_ERR_SIGERR 0x80 /* IRIG status error (Spectracom) */ + +/* + * IRIG unit control structure + */ +struct irigunit { + u_char timecode[21]; /* timecode string */ + l_fp timestamp; /* audio sample timestamp */ + l_fp tick; /* audio sample increment */ + double integ[BAUD]; /* baud integrator */ + double phase, freq; /* logical clock phase and frequency */ + double zxing; /* phase detector integrator */ + double yxing; /* cycle phase */ + double exing; /* envelope phase */ + double modndx; /* modulation index */ + double irig_b; /* IRIG-B signal amplitude */ + double irig_e; /* IRIG-E signal amplitude */ + int errflg; /* error flags */ + /* + * Audio codec variables + */ + double comp[SIZE]; /* decompanding table */ + int port; /* codec port */ + int gain; /* codec gain */ + int mongain; /* codec monitor gain */ + int clipcnt; /* sample clipped count */ + int seccnt; /* second interval counter */ + + /* + * RF variables + */ + double hpf[5]; /* IRIG-B filter shift register */ + double lpf[5]; /* IRIG-E filter shift register */ + double intmin, intmax; /* integrated envelope min and max */ + double envmax; /* peak amplitude */ + double envmin; /* noise amplitude */ + double maxsignal; /* integrated peak amplitude */ + double noise; /* integrated noise amplitude */ + double lastenv[CYCLE]; /* last cycle amplitudes */ + double lastint[CYCLE]; /* last integrated cycle amplitudes */ + double lastsig; /* last carrier sample */ + double fdelay; /* filter delay */ + int decim; /* sample decimation factor */ + int envphase; /* envelope phase */ + int envptr; /* envelope phase pointer */ + int carphase; /* carrier phase */ + int envsw; /* envelope state */ + int envxing; /* envelope slice crossing */ + int tc; /* time constant */ + int tcount; /* time constant counter */ + int badcnt; /* decimation interval counter */ + + /* + * Decoder variables + */ + int pulse; /* cycle counter */ + int cycles; /* carrier cycles */ + int dcycles; /* data cycles */ + int xptr; /* translate table pointer */ + int lastbit; /* last code element length */ + int second; /* previous second */ + int fieldcnt; /* subfield count in field */ + int bits; /* demodulated bits */ + int bitcnt; /* bit count in subfield */ +#ifdef IRIG_SUCKS + l_fp wigwag; /* wiggle accumulator */ + int wp; /* wiggle filter pointer */ + l_fp wiggle[WIGGLE]; /* wiggle filter */ + l_fp wigbot[WIGGLE]; /* wiggle bottom fisher*/ +#endif /* IRIG_SUCKS */ + l_fp wuggle; +}; + +/* + * Function prototypes + */ +static int irig_start P((int, struct peer *)); +static void irig_shutdown P((int, struct peer *)); +static void irig_receive P((struct recvbuf *)); +static void irig_poll P((int, struct peer *)); + +/* + * More function prototypes + */ +static void irig_base P((struct peer *, double)); +static void irig_rf P((struct peer *, double)); +static void irig_decode P((struct peer *, int)); +static void irig_gain P((struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_irig = { + irig_start, /* start up driver */ + irig_shutdown, /* shut down driver */ + irig_poll, /* transmit poll message */ + noentry, /* not used (old irig_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old irig_buginfo) */ + NOFLAGS /* not used */ +}; + +/* + * Global variables + */ +static char hexchar[] = { /* really quick decoding table */ + '0', '8', '4', 'c', /* 0000 0001 0010 0011 */ + '2', 'a', '6', 'e', /* 0100 0101 0110 0111 */ + '1', '9', '5', 'd', /* 1000 1001 1010 1011 */ + '3', 'b', '7', 'f' /* 1100 1101 1110 1111 */ +}; + + +/* + * irig_start - open the devices and initialize data for processing + */ +static int +irig_start( + int unit, /* instance number (used for PCM) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + /* + * Local variables + */ + int fd; /* file descriptor */ + int i; /* index */ + double step; /* codec adjustment */ + + /* + * Open audio device + */ + fd = audio_init(DEVICE_AUDIO, AUDIO_BUFSIZ, unit); + if (fd < 0) + return (0); +#ifdef DEBUG + if (debug) + audio_show(); +#endif + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct irigunit *) + emalloc(sizeof(struct irigunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct irigunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = irig_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void)close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->tc = MINTC; + up->decim = 1; + up->fdelay = IRIG_B; + up->gain = 127; + + /* + * The companded samples are encoded sign-magnitude. The table + * contains all the 256 values in the interest of speed. + */ + up->comp[0] = up->comp[OFFSET] = 0.; + up->comp[1] = 1; up->comp[OFFSET + 1] = -1.; + up->comp[2] = 3; up->comp[OFFSET + 2] = -3.; + step = 2.; + for (i = 3; i < OFFSET; i++) { + up->comp[i] = up->comp[i - 1] + step; + up->comp[OFFSET + i] = -up->comp[i]; + if (i % 16 == 0) + step *= 2.; + } + DTOLFP(1. / SECOND, &up->tick); + return (1); +} + + +/* + * irig_shutdown - shut down the clock + */ +static void +irig_shutdown( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * irig_receive - receive data from the audio device + * + * This routine reads input samples and adjusts the logical clock to + * track the irig clock by dropping or duplicating codec samples. + */ +static void +irig_receive( + struct recvbuf *rbufp /* receive buffer structure pointer */ + ) +{ + struct peer *peer; + struct refclockproc *pp; + struct irigunit *up; + + /* + * Local variables + */ + double sample; /* codec sample */ + u_char *dpt; /* buffer pointer */ + int bufcnt; /* buffer counter */ + l_fp ltemp; /* l_fp temp */ + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + /* + * Main loop - read until there ain't no more. Note codec + * samples are bit-inverted. + */ + DTOLFP((double)rbufp->recv_length / SECOND, <emp); + L_SUB(&rbufp->recv_time, <emp); + up->timestamp = rbufp->recv_time; + dpt = rbufp->recv_buffer; + for (bufcnt = 0; bufcnt < rbufp->recv_length; bufcnt++) { + sample = up->comp[~*dpt++ & 0xff]; + + /* + * Clip noise spikes greater than MAXSIG. If no clips, + * increase the gain a tad; if the clips are too high, + * decrease a tad. + */ + if (sample > MAXSIG) { + sample = MAXSIG; + up->clipcnt++; + } else if (sample < -MAXSIG) { + sample = -MAXSIG; + up->clipcnt++; + } + + /* + * Variable frequency oscillator. The codec oscillator + * runs at the nominal rate of 8000 samples per second, + * or 125 us per sample. A frequency change of one unit + * results in either duplicating or deleting one sample + * per second, which results in a frequency change of + * 125 PPM. + */ + up->phase += up->freq / SECOND; + up->phase += pp->fudgetime2 / 1e6; + if (up->phase >= .5) { + up->phase -= 1.; + } else if (up->phase < -.5) { + up->phase += 1.; + irig_rf(peer, sample); + irig_rf(peer, sample); + } else { + irig_rf(peer, sample); + } + L_ADD(&up->timestamp, &up->tick); + + /* + * Once each second, determine the IRIG format and gain. + */ + up->seccnt = (up->seccnt + 1) % SECOND; + if (up->seccnt == 0) { + if (up->irig_b > up->irig_e) { + up->decim = 1; + up->fdelay = IRIG_B; + } else { + up->decim = 10; + up->fdelay = IRIG_E; + } + irig_gain(peer); + up->irig_b = up->irig_e = 0; + } + } + + /* + * Set the input port and monitor gain for the next buffer. + */ + if (pp->sloppyclockflag & CLK_FLAG2) + up->port = 2; + else + up->port = 1; + if (pp->sloppyclockflag & CLK_FLAG3) + up->mongain = MONGAIN; + else + up->mongain = 0; +} + +/* + * irig_rf - RF processing + * + * This routine filters the RF signal using a highpass filter for IRIG-B + * and a lowpass filter for IRIG-E. In case of IRIG-E, the samples are + * decimated by a factor of ten. The lowpass filter functions also as a + * decimation filter in this case. Note that the codec filters function + * as roofing filters to attenuate both the high and low ends of the + * passband. IIR filter coefficients were determined using Matlab Signal + * Processing Toolkit. + */ +static void +irig_rf( + struct peer *peer, /* peer structure pointer */ + double sample /* current signal sample */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + /* + * Local variables + */ + double irig_b, irig_e; /* irig filter outputs */ + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + /* + * IRIG-B filter. 4th-order elliptic, 800-Hz highpass, 0.3 dB + * passband ripple, -50 dB stopband ripple, phase delay .0022 + * s) + */ + irig_b = (up->hpf[4] = up->hpf[3]) * 2.322484e-01; + irig_b += (up->hpf[3] = up->hpf[2]) * -1.103929e+00; + irig_b += (up->hpf[2] = up->hpf[1]) * 2.351081e+00; + irig_b += (up->hpf[1] = up->hpf[0]) * -2.335036e+00; + up->hpf[0] = sample - irig_b; + irig_b = up->hpf[0] * 4.335855e-01 + + up->hpf[1] * -1.695859e+00 + + up->hpf[2] * 2.525004e+00 + + up->hpf[3] * -1.695859e+00 + + up->hpf[4] * 4.335855e-01; + up->irig_b += irig_b * irig_b; + + /* + * IRIG-E filter. 4th-order elliptic, 130-Hz lowpass, 0.3 dB + * passband ripple, -50 dB stopband ripple, phase delay .0219 s. + */ + irig_e = (up->lpf[4] = up->lpf[3]) * 8.694604e-01; + irig_e += (up->lpf[3] = up->lpf[2]) * -3.589893e+00; + irig_e += (up->lpf[2] = up->lpf[1]) * 5.570154e+00; + irig_e += (up->lpf[1] = up->lpf[0]) * -3.849667e+00; + up->lpf[0] = sample - irig_e; + irig_e = up->lpf[0] * 3.215696e-03 + + up->lpf[1] * -1.174951e-02 + + up->lpf[2] * 1.712074e-02 + + up->lpf[3] * -1.174951e-02 + + up->lpf[4] * 3.215696e-03; + up->irig_e += irig_e * irig_e; + + /* + * Decimate by a factor of either 1 (IRIG-B) or 10 (IRIG-E). + */ + up->badcnt = (up->badcnt + 1) % up->decim; + if (up->badcnt == 0) { + if (up->decim == 1) + irig_base(peer, irig_b); + else + irig_base(peer, irig_e); + } +} + +/* + * irig_base - baseband processing + * + * This routine processes the baseband signal and demodulates the AM + * carrier using a synchronous detector. It then synchronizes to the + * data frame at the baud rate and decodes the data pulses. + */ +static void +irig_base( + struct peer *peer, /* peer structure pointer */ + double sample /* current signal sample */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + /* + * Local variables + */ + double xxing; /* phase detector interpolated output */ + double lope; /* integrator output */ + double env; /* envelope detector output */ + double dtemp; /* double temp */ + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + /* + * Synchronous baud integrator. Corresponding samples of current + * and past baud intervals are integrated to refine the envelope + * amplitude and phase estimate. We keep one cycle of both the + * raw and integrated data for later use. + */ + up->envphase = (up->envphase + 1) % BAUD; + up->carphase = (up->carphase + 1) % CYCLE; + up->integ[up->envphase] += (sample - up->integ[up->envphase]) / + (5 * up->tc); + lope = up->integ[up->envphase]; + up->lastenv[up->carphase] = sample; + up->lastint[up->carphase] = lope; + + /* + * Phase detector. Sample amplitudes are integrated over the + * baud interval. Cycle phase is determined from these + * amplitudes using an eight-sample cyclic buffer. A phase + * change of 360 degrees produces an output change of one unit. + */ + if (up->lastsig > 0 && lope <= 0) { + xxing = lope / (up->lastsig - lope); + up->zxing += (up->carphase - 4 + xxing) / CYCLE; + } + up->lastsig = lope; + + /* + * Update signal/noise estimates and PLL phase/frequency. + */ + if (up->envphase == 0) { + + /* + * Update envelope signal and noise estimates and mess + * with error bits. + */ + up->maxsignal = up->intmax; + up->noise = up->intmin; + if (up->maxsignal < DRPOUT) + up->errflg |= IRIG_ERR_AMP; + if (up->maxsignal > 0) + up->modndx = (up->intmax - up->intmin) / + up->intmax; + else + up->modndx = 0; + if (up->modndx < MODMIN) + up->errflg |= IRIG_ERR_MOD; + up->intmin = 1e6; up->intmax = 0; + if (up->errflg & (IRIG_ERR_AMP | IRIG_ERR_FREQ | + IRIG_ERR_MOD | IRIG_ERR_SYNCH)) { + up->tc = MINTC; + up->tcount = 0; + } + + /* + * Update PLL phase and frequency. The PLL time constant + * is set initially to stabilize the frequency within a + * minute or two, then increases to the maximum. The + * frequency is clamped so that the PLL capture range + * cannot be exceeded. + */ + dtemp = up->zxing * up->decim / BAUD; + up->yxing = dtemp; + up->zxing = 0.; + up->phase += dtemp / up->tc; + up->freq += dtemp / (4. * up->tc * up->tc); + if (up->freq > MAXFREQ) { + up->freq = MAXFREQ; + up->errflg |= IRIG_ERR_FREQ; + } else if (up->freq < -MAXFREQ) { + up->freq = -MAXFREQ; + up->errflg |= IRIG_ERR_FREQ; + } + } + + /* + * Synchronous demodulator. There are eight samples in the cycle + * and ten cycles in the baud interval. The amplitude of each + * cycle is determined at the last sample in the cycle. The + * beginning of the data pulse is determined from the integrated + * samples, while the end of the pulse is determined from the + * raw samples. The raw data bits are demodulated relative to + * the slice level and left-shifted in the decoding register. + */ + if (up->carphase != 7) + return; + env = (up->lastenv[2] - up->lastenv[6]) / 2.; + lope = (up->lastint[2] - up->lastint[6]) / 2.; + if (lope > up->intmax) + up->intmax = lope; + if (lope < up->intmin) + up->intmin = lope; + + /* + * Pulse code demodulator and reference timestamp. The decoder + * looks for a sequence of ten bits; the first two bits must be + * one, the last two bits must be zero. Frame synch is asserted + * when three correct frames have been found. + */ + up->pulse = (up->pulse + 1) % 10; + if (up->pulse == 1) + up->envmax = env; + else if (up->pulse == 9) + up->envmin = env; + up->dcycles <<= 1; + if (env >= (up->envmax + up->envmin) / 2.) + up->dcycles |= 1; + up->cycles <<= 1; + if (lope >= (up->maxsignal + up->noise) / 2.) + up->cycles |= 1; + if ((up->cycles & 0x303c0f03) == 0x300c0300) { + l_fp ltemp; + int bitz; + + /* + * The PLL time constant starts out small, in order to + * sustain a frequency tolerance of 250 PPM. It + * gradually increases as the loop settles down. Note + * that small wiggles are not believed, unless they + * persist for lots of samples. + */ + if (up->pulse != 9) + up->errflg |= IRIG_ERR_SYNCH; + up->pulse = 9; + up->exing = -up->yxing; + if (fabs(up->envxing - up->envphase) <= 1) { + up->tcount++; + if (up->tcount > 50 * up->tc) { + up->tc++; + if (up->tc > MAXTC) + up->tc = MAXTC; + up->tcount = 0; + up->envxing = up->envphase; + } else { + up->exing -= up->envxing - up->envphase; + } + } else { + up->tcount = 0; + up->envxing = up->envphase; + } + + /* + * Determine a reference timestamp, accounting for the + * codec delay and filter delay. Note the timestamp is + * for the previous frame, so we have to backtrack for + * this plus the delay since the last carrier positive + * zero crossing. + */ + dtemp = up->decim * ((up->exing + BAUD) / SECOND + 1.) + + up->fdelay; + DTOLFP(dtemp, <emp); + pp->lastrec = up->timestamp; + L_SUB(&pp->lastrec, <emp); + + /* + * The data bits are collected in ten-bit frames. The + * first two and last two bits are determined by frame + * sync and ignored here; the resulting patterns + * represent zero (0-1 bits), one (2-4 bits) and + * position identifier (5-6 bits). The remaining + * patterns represent errors and are treated as zeros. + */ + bitz = up->dcycles & 0xfc; + switch(bitz) { + + case 0x00: + case 0x80: + irig_decode(peer, BIT0); + break; + + case 0xc0: + case 0xe0: + case 0xf0: + irig_decode(peer, BIT1); + break; + + case 0xf8: + case 0xfc: + irig_decode(peer, BITP); + break; + + default: + irig_decode(peer, 0); + up->errflg |= IRIG_ERR_DECODE; + } + } +} + + +/* + * irig_decode - decode the data + * + * This routine assembles bits into digits, digits into subfields and + * subfields into the timecode field. Bits can have values of zero, one + * or position identifier. There are four bits per digit, two digits per + * subfield and ten subfields per field. The last bit in every subfield + * and the first bit in the first subfield are position identifiers. + */ +static void +irig_decode( + struct peer *peer, /* peer structure pointer */ + int bit /* data bit (0, 1 or 2) */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; +#ifdef IRIG_SUCKS + int i; +#endif /* IRIG_SUCKS */ + + /* + * Local variables + */ + char syncchar; /* sync character (Spectracom) */ + char sbs[6]; /* binary seconds since 0h */ + char spare[2]; /* mulligan digits */ + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + /* + * Assemble subfield bits. + */ + up->bits <<= 1; + if (bit == BIT1) { + up->bits |= 1; + } else if (bit == BITP && up->lastbit == BITP) { + + /* + * Frame sync - two adjacent position identifiers. + * Monitor the reference timestamp and wiggle the + * clock, but only if no errors have occurred. + */ + up->bitcnt = 1; + up->fieldcnt = 0; + up->lastbit = 0; + if (up->errflg == 0) { +#ifdef IRIG_SUCKS + l_fp ltemp; + + /* + * You really don't wanna know what comes down + * here. Leave it to say Solaris 2.8 broke the + * nice clean audio stream, apparently affected + * by a 5-ms sawtooth jitter. Sundown on + * Solaris. This leaves a little twilight. + * + * The scheme involves differentiation, forward + * learning and integration. The sawtooth has a + * period of 11 seconds. The timestamp + * differences are integrated and subtracted + * from the signal. + */ + ltemp = pp->lastrec; + L_SUB(<emp, &pp->lastref); + if (ltemp.l_f < 0) + ltemp.l_i = -1; + else + ltemp.l_i = 0; + pp->lastref = pp->lastrec; + if (!L_ISNEG(<emp)) + L_CLR(&up->wigwag); + else + L_ADD(&up->wigwag, <emp); + L_SUB(&pp->lastrec, &up->wigwag); + up->wiggle[up->wp] = ltemp; + + /* + * Bottom fisher. To understand this, you have + * to know about velocity microphones and AM + * transmitters. No further explanation is + * offered, as this is truly a black art. + */ + up->wigbot[up->wp] = pp->lastrec; + for (i = 0; i < WIGGLE; i++) { + if (i != up->wp) + up->wigbot[i].l_ui++; + L_SUB(&pp->lastrec, &up->wigbot[i]); + if (L_ISNEG(&pp->lastrec)) + L_ADD(&pp->lastrec, + &up->wigbot[i]); + else + pp->lastrec = up->wigbot[i]; + } + up->wp++; + up->wp %= WIGGLE; + up->wuggle = pp->lastrec; + refclock_process(pp); +#else /* IRIG_SUCKS */ + pp->lastref = pp->lastrec; + up->wuggle = pp->lastrec; + refclock_process(pp); +#endif /* IRIG_SUCKS */ + } + up->errflg = 0; + } + up->bitcnt = (up->bitcnt + 1) % SUBFLD; + if (up->bitcnt == 0) { + + /* + * End of subfield. Encode two hexadecimal digits in + * little-endian timecode field. + */ + if (up->fieldcnt == 0) + up->bits <<= 1; + if (up->xptr < 2) + up->xptr = 2 * FIELD; + up->timecode[--up->xptr] = hexchar[(up->bits >> 5) & + 0xf]; + up->timecode[--up->xptr] = hexchar[up->bits & 0xf]; + up->fieldcnt = (up->fieldcnt + 1) % FIELD; + if (up->fieldcnt == 0) { + + /* + * End of field. Decode the timecode and wind + * the clock. Not all IRIG generators have the + * year; if so, it is nonzero after year 2000. + * Not all have the hardware status bit; if so, + * it is lit when the source is okay and dim + * when bad. We watch this only if the year is + * nonzero. Not all are configured for signature + * control. If so, all BCD digits are set to + * zero if the source is bad. In this case the + * refclock_process() will reject the timecode + * as invalid. + */ + up->xptr = 2 * FIELD; + if (sscanf((char *)up->timecode, + "%6s%2d%c%2s%3d%2d%2d%2d", sbs, &pp->year, + &syncchar, spare, &pp->day, &pp->hour, + &pp->minute, &pp->second) != 8) + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + up->second = (up->second + up->decim) % 60; + if (pp->year > 0) { + pp->year += 2000; + if (syncchar == '0') + up->errflg |= IRIG_ERR_CHECK; + } + if (pp->second != up->second) + up->errflg |= IRIG_ERR_CHECK; + up->second = pp->second; + sprintf(pp->a_lastcode, + "%02x %c %2d %3d %02d:%02d:%02d %4.0f %3d %6.3f %2d %6.1f %6.1f %s", + up->errflg, syncchar, pp->year, pp->day, + pp->hour, pp->minute, pp->second, + up->maxsignal, up->gain, up->modndx, + up->tc, up->exing * 1e6 / SECOND, up->freq * + 1e6 / SECOND, ulfptoa(&up->wuggle, 6)); + pp->lencode = strlen(pp->a_lastcode); + if (pp->sloppyclockflag & CLK_FLAG4) { + record_clock_stats(&peer->srcadr, + pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("irig: %s\n", + pp->a_lastcode); +#endif /* DEBUG */ + } + } + } + up->lastbit = bit; +} + + +/* + * irig_poll - called by the transmit procedure + * + * This routine sweeps up the timecode updates since the last poll. For + * IRIG-B there should be at least 60 updates; for IRIG-E there should + * be at least 6. If nothing is heard, a timeout event is declared and + * any orphaned timecode updates are sent to foster care. + */ +static void +irig_poll( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } else { + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("irig: %s\n", pp->a_lastcode); +#endif /* DEBUG */ + } + pp->polls++; + +} + + +/* + * irig_gain - adjust codec gain + * + * This routine is called once each second. If the signal envelope + * amplitude is too low, the codec gain is bumped up by four units; if + * too high, it is bumped down. The decoder is relatively insensitive to + * amplitude, so this crudity works just fine. The input port is set and + * the error flag is cleared, mostly to be ornery. + */ +static void +irig_gain( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct irigunit *up; + + pp = peer->procptr; + up = (struct irigunit *)pp->unitptr; + + /* + * Apparently, the codec uses only the high order bits of the + * gain control field. Thus, it may take awhile for changes to + * wiggle the hardware bits. + */ + if (up->clipcnt == 0) { + up->gain += 4; + if (up->gain > MAXGAIN) + up->gain = MAXGAIN; + } else if (up->clipcnt > MAXCLP) { + up->gain -= 4; + if (up->gain < 0) + up->gain = 0; + } + audio_gain(up->gain, up->mongain, up->port); + up->clipcnt = 0; +} + +#else +int refclock_irig_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_jjy.c b/ntpd/refclock_jjy.c new file mode 100644 index 0000000..2aa9d6a --- /dev/null +++ b/ntpd/refclock_jjy.c @@ -0,0 +1,710 @@ +/* + * refclock_jjy - clock driver for JJY receivers + */ + +/**********************************************************************/ +/* */ +/* Copyright (C) 2001, Takao Abe. All rights reserved. */ +/* */ +/* Permission to use, copy, modify, and distribute this software */ +/* and its documentation for any purpose is hereby granted */ +/* without fee, provided that the following conditions are met: */ +/* */ +/* One retains the entire copyright notice properly, and both the */ +/* copyright notice and this license. in the documentation and/or */ +/* other materials provided with the distribution. */ +/* */ +/* This software and the name of the author must not be used to */ +/* endorse or promote products derived from this software without */ +/* prior written permission. */ +/* */ +/* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESSED OR IMPLIED */ +/* WARRANTIES OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, THE */ +/* IMPLIED WARRANTIES OF MERCHANTABLILITY AND FITNESS FOR A */ +/* PARTICULAR PURPOSE. */ +/* IN NO EVENT SHALL THE AUTHOR TAKAO ABE BE LIABLE FOR ANY DIRECT, */ +/* INDIRECT, GENERAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA OR PROFITS; OR BUSINESS */ +/* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, */ +/* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ( INCLUDING */ +/* NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF */ +/* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* This driver is developed in my private time, and is opened as */ +/* voluntary contributions for the NTP. */ +/* The manufacturer of the JJY receiver has not participated in */ +/* a development of this driver. */ +/* The manufacturer does not warrant anything about this driver, */ +/* and is not liable for anything about this driver. */ +/* */ +/**********************************************************************/ +/* */ +/* Author Takao Abe */ +/* Email abetakao@bea.hi-ho.ne.jp */ +/* Homepage http://www.bea.hi-ho.ne.jp/abetakao/ */ +/* */ +/**********************************************************************/ +/* */ +/* History */ +/* */ +/* 2001/07/15 */ +/* [New] Support the Tristate Ltd. JJY receiver */ +/* */ +/* 2001/08/04 */ +/* [Change] Log to clockstats even if bad reply */ +/* [Fix] PRECISION = (-3) (about 100 ms) */ +/* [Add] Support the C-DEX Co.Ltd. JJY receiver */ +/* 2001/12/04 */ +/* [Fix] C-DEX JST2000 ( fukusima@goto.info.waseda.ac.jp ) */ +/* */ +/**********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_JJY) + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_tty.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +/**********************************************************************/ +/* */ +/* The Tristate Ltd. JJY receiver JJY01 */ +/* */ +/* Command Response Remarks */ +/* ------------ ---------------------- --------------------- */ +/* date<CR><LF> YYYY/MM/DD XXX<CR><LF> */ +/* time<CR><LF> HH:MM:SS<CR><LF> */ +/* stim<CR><LF> HH:MM:SS<CR><LF> Reply at just second */ +/* */ +/* During synchronization after a receiver is turned on, */ +/* It replies the past time from 2000/01/01 00:00:00. */ +/* The function "refclock_process" checks the time and tells */ +/* as an insanity time. */ +/* */ +/**********************************************************************/ +/* */ +/* The C-DEX Co. Ltd. JJY receiver JST2000 */ +/* */ +/* Command Response Remarks */ +/* ------------ ---------------------- --------------------- */ +/* <ENQ>1J<ETX> <STX>JYYMMDD HHMMSSS<ETX> */ +/* */ +/**********************************************************************/ + +/* + * Interface definitions + */ +#define DEVICE "/dev/jjy%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define REFID "JJY" /* reference ID */ +#define DESCRIPTION "JJY Receiver" +#define PRECISION (-3) /* precision assumed (about 100 ms) */ + +/* + * JJY unit control structure + */ +struct jjyunit { + char unittype ; /* UNITTYPE_XXXXXXXXXX */ + short version ; + short linediscipline ; /* LDISC_CLK or LDISC_RAW */ + int linecount ; + int lineerror ; + int year, month, day, hour, minute, second, msecond ; +/* LDISC_RAW only */ +#define MAX_LINECOUNT 8 +#define MAX_RAWBUF 64 + int lineexpect ; + int charexpect [ MAX_LINECOUNT ] ; + int charcount ; + char rawbuf [ MAX_RAWBUF ] ; +}; + +#define UNITTYPE_TRISTATE_JJY01 1 +#define UNITTYPE_CDEX_JST2000 2 + +/* + * Function prototypes + */ +static int jjy_start P((int, struct peer *)); +static void jjy_shutdown P((int, struct peer *)); +static void jjy_poll P((int, struct peer *)); +static void jjy_poll_tristate_jjy01 P((int, struct peer *)); +static void jjy_poll_cdex_jst2000 P((int, struct peer *)); +static void jjy_receive P((struct recvbuf *)); +static int jjy_receive_tristate_jjy01 P((struct recvbuf *)); +static int jjy_receive_cdex_jst2000 P((struct recvbuf *)); + +/* + * Transfer vector + */ +struct refclock refclock_jjy = { + jjy_start, /* start up driver */ + jjy_shutdown, /* shutdown driver */ + jjy_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* not used */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +/* + * Start up driver return code + */ +#define RC_START_SUCCESS 1 +#define RC_START_ERROR 0 + +/* + * Local constants definition + */ + +#define MAX_LOGTEXT 64 + + +/**************************************************************************************************/ +/* jjy_start - open the devices and initialize data for processing */ +/**************************************************************************************************/ +static int +jjy_start ( int unit, struct peer *peer ) +{ + + struct jjyunit *up ; + struct refclockproc *pp ; + int fd ; + char *pDeviceName ; + short iDiscipline ; + +#ifdef DEBUG + if ( debug ) { + printf ( "jjy_start (refclock_jjy.c) : %s mode=%d ", ntoa(&peer->srcadr), peer->ttl ) ; + printf ( DEVICE, unit ) ; + printf ( "\n" ) ; + } +#endif + /* + * Open serial port + */ + if ( ! ( pDeviceName = (char*) emalloc ( strlen(DEVICE) + 10 ) ) ) { + return RC_START_ERROR ; + } + sprintf ( pDeviceName, DEVICE, unit ) ; + + /* + * peer->ttl is a mode number specified by "127.127.40.X mode N" in the ntp.conf + */ + switch ( peer->ttl ) { + case 0 : + case 1 : iDiscipline = LDISC_CLK ; break ; + case 2 : iDiscipline = LDISC_RAW ; break ; + default : + msyslog ( LOG_ERR, "JJY receiver [ %s mode %d ] : Unsupported mode", + ntoa(&peer->srcadr), peer->ttl ) ; + free ( (void*) pDeviceName ) ; + return RC_START_ERROR ; + } + + if ( ! ( fd = refclock_open ( pDeviceName, SPEED232, iDiscipline ) ) ) { + free ( (void*) pDeviceName ) ; + return RC_START_ERROR ; + } + free ( (void*) pDeviceName ) ; + + /* + * Allocate and initialize unit structure + */ + if ( ! ( up = (struct jjyunit *) emalloc (sizeof(struct jjyunit)) ) ) { + close ( fd ) ; + return RC_START_ERROR ; + } + + memset ( (char*)up, 0, sizeof(struct jjyunit) ) ; + up->linediscipline = iDiscipline ; + + /* + * peer->ttl is a mode number specified by "127.127.40.X mode N" in the ntp.conf + */ + switch ( peer->ttl ) { + case 0 : + /* + * The mode 0 is a default clock type at this time. + * But this will be change to auto-detect mode in the future. + */ + case 1 : + up->unittype = UNITTYPE_TRISTATE_JJY01 ; + up->version = 100 ; + up->lineexpect = 2 ; + up->charexpect[0] = 14 ; /* YYYY/MM/DD WWW<CR><LF> */ + up->charexpect[1] = 8 ; /* HH:MM:SS<CR><LF> */ + break ; + case 2 : + up->unittype = UNITTYPE_CDEX_JST2000 ; + up->lineexpect = 1 ; + up->charexpect[0] = 15 ; /* <STX>JYYMMDD HHMMSSS<ETX> */ + break ; + default : + msyslog ( LOG_ERR, "JJY receiver [ %s mode %d ] : Unsupported mode", + ntoa(&peer->srcadr), peer->ttl ) ; + close ( fd ) ; + free ( (void*) up ) ; + return RC_START_ERROR ; + } + + pp = peer->procptr ; + pp->unitptr = (caddr_t) up ; + pp->io.clock_recv = jjy_receive ; + pp->io.srcclock = (caddr_t) peer ; + pp->io.datalen = 0 ; + pp->io.fd = fd ; + if ( ! io_addclock(&pp->io) ) { + close ( fd ) ; + free ( (void*) up ) ; + return RC_START_ERROR ; + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION ; + peer->burst = 1 ; + pp->clockdesc = DESCRIPTION ; + memcpy ( (char*)&pp->refid, REFID, strlen(REFID) ) ; + + return RC_START_SUCCESS ; + +} + + +/**************************************************************************************************/ +/* jjy_shutdown - shutdown the clock */ +/**************************************************************************************************/ +static void +jjy_shutdown ( int unit, struct peer *peer ) +{ + + struct jjyunit *up; + struct refclockproc *pp; + + pp = peer->procptr ; + up = (struct jjyunit *) pp->unitptr ; + io_closeclock ( &pp->io ) ; + free ( (void*) up ) ; + +} + + +/**************************************************************************************************/ +/* jjy_receive - receive data from the serial interface */ +/**************************************************************************************************/ +static void +jjy_receive ( struct recvbuf *rbufp ) +{ + + struct jjyunit *up ; + struct refclockproc *pp ; + struct peer *peer; + + l_fp tRecvTimestamp; /* arrival timestamp */ + int rc ; + char sLogText [ MAX_LOGTEXT ] ; + int i, bCntrlChar ; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *) rbufp->recv_srcclock ; + pp = peer->procptr ; + up = (struct jjyunit *) pp->unitptr ; + + /* + * Get next input line + */ + pp->lencode = refclock_gtlin ( rbufp, pp->a_lastcode, BMAX, &tRecvTimestamp ) ; + + if ( up->linediscipline == LDISC_RAW ) { + /* + * The reply with <STX> and <ETX> may give a blank line + */ + if ( pp->lencode == 0 && up->charcount == 0 ) return ; + /* + * Copy received charaters to temporary buffer + */ + for ( i = 0 ; i < pp->lencode && up->charcount < MAX_RAWBUF - 2 ; i ++ , up->charcount ++ ) { + up->rawbuf[up->charcount] = pp->a_lastcode[i] ; + } + while ( up->charcount > 0 && up->rawbuf[0] < ' ' ) { + for ( i = 0 ; i < up->charcount - 1 ; i ++ ) up->rawbuf[i] = up->rawbuf[i+1] ; + up->charcount -- ; + } + bCntrlChar = 0 ; + for ( i = 0 ; i < up->charcount ; i ++ ) { + if ( up->rawbuf[i] < ' ' ) { + bCntrlChar = 1 ; + break ; + } + } + if ( pp->lencode > 0 && up->linecount < up->lineexpect ) { + if ( bCntrlChar == 0 && up->charcount < up->charexpect[up->linecount] ) return ; + } + up->rawbuf[up->charcount] = 0 ; + } else { + /* + * The reply with <CR><LF> gives a blank line + */ + if ( pp->lencode == 0 ) return ; + } + /* + * We get down to business + */ + + pp->lastrec = tRecvTimestamp ; + + up->linecount ++ ; + + if ( up->lineerror != 0 ) return ; + + switch ( up->unittype ) { + + case UNITTYPE_TRISTATE_JJY01 : + rc = jjy_receive_tristate_jjy01 ( rbufp ) ; + break ; + + case UNITTYPE_CDEX_JST2000 : + rc = jjy_receive_cdex_jst2000 ( rbufp ) ; + break ; + + default : + rc = 0 ; + break ; + + } + + if ( up->linediscipline == LDISC_RAW ) { + if ( up->linecount <= up->lineexpect && up->charcount > up->charexpect[up->linecount-1] ) { + for ( i = 0 ; i < up->charcount - up->charexpect[up->linecount-1] ; i ++ ) { + up->rawbuf[i] = up->rawbuf[i+up->charexpect[up->linecount-1]] ; + } + up->charcount -= up->charexpect[up->linecount-1] ; + } else { + up->charcount = 0 ; + } + } + + if ( rc == 0 ) return ; + + if ( up->lineerror != 0 ) { + refclock_report ( peer, CEVNT_BADREPLY ) ; + strcpy ( sLogText, "BAD REPLY [" ) ; + if ( up->linediscipline == LDISC_RAW ) { + strncat ( sLogText, up->rawbuf, MAX_LOGTEXT - strlen ( sLogText ) - 1 ) ; + } else { + strncat ( sLogText, pp->a_lastcode, MAX_LOGTEXT - strlen ( sLogText ) - 1 ) ; + } + sLogText[MAX_LOGTEXT-1] = 0 ; + if ( strlen ( sLogText ) < MAX_LOGTEXT - 2 ) strcat ( sLogText, "]" ) ; + record_clock_stats ( &peer->srcadr, sLogText ) ; + return ; + } + + pp->year = up->year ; + pp->day = ymd2yd ( up->year, up->month, up->day ) ; + pp->hour = up->hour ; + pp->minute = up->minute ; + pp->second = up->second ; + pp->nsec = up->msecond * 1000000; + + /* + * JST to UTC + */ + pp->hour -= 9 ; + if ( pp->hour < 0 ) { + pp->hour += 24 ; + pp->day -- ; + if ( pp->day < 1 ) { + pp->year -- ; + pp->day = ymd2yd ( pp->year, 12, 31 ) ; + } + } +#ifdef DEBUG + if ( debug ) { + printf ( "jjy_receive (refclock_jjy.c) : %04d/%02d/%02d %02d:%02d:%02d JST ", + up->year, up->month, up->day, up->hour, up->minute, up->second ) ; + printf ( "( %04d/%03d %02d:%02d:%02d UTC )\n", + pp->year, pp->day, pp->hour, pp->minute, pp->second ) ; + } +#endif + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if ( ! refclock_process ( pp ) ) { + refclock_report(peer, CEVNT_BADTIME); + sprintf ( sLogText, "BAD TIME %04d/%02d/%02d %02d:%02d:%02d JST", + up->year, up->month, up->day, up->hour, up->minute, up->second ) ; + record_clock_stats ( &peer->srcadr, sLogText ) ; + return ; + } + + sprintf ( sLogText, "%04d/%02d/%02d %02d:%02d:%02d JST", + up->year, up->month, up->day, up->hour, up->minute, up->second ) ; + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats ( &peer->srcadr, sLogText ) ; +} + +/**************************************************************************************************/ + +static int +jjy_receive_tristate_jjy01 ( struct recvbuf *rbufp ) +{ + + struct jjyunit *up ; + struct refclockproc *pp ; + struct peer *peer; + + char *pBuf ; + int iLen ; + int rc ; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *) rbufp->recv_srcclock ; + pp = peer->procptr ; + up = (struct jjyunit *) pp->unitptr ; + + if ( up->linediscipline == LDISC_RAW ) { + pBuf = up->rawbuf ; + iLen = up->charcount ; + } else { + pBuf = pp->a_lastcode ; + iLen = pp->lencode ; + } + + switch ( up->linecount ) { + + case 1 : /* YYYY/MM/DD */ + + if ( iLen < 10 ) { + up->lineerror = 1 ; + break ; + } + rc = sscanf ( pBuf, "%4d/%2d/%2d", &up->year, &up->month, &up->day ) ; + if ( rc != 3 || up->year < 2000 || up->month < 1 || up->month > 12 || up->day < 1 || up->day > 31 ) { + up->lineerror = 1 ; + break ; + } + return 0 ; + + case 2 : /* HH:MM:SS */ + + if ( iLen < 8 ) { + up->lineerror = 1 ; + break ; + } + rc = sscanf ( pBuf, "%2d:%2d:%2d", &up->hour, &up->minute, &up->second ) ; + if ( rc != 3 || up->hour > 23 || up->minute > 59 || up->second > 60 ) { + up->lineerror = 1 ; + break ; + } + up->msecond = 0 ; + if ( up->hour == 0 && up->minute == 0 && up->second <= 2 ) { + /* + * The command "date" and "time" ( or "stim" ) were sent to the JJY receiver continuously. + * But the JJY receiver replies a date and time separately. + * Just after midnight transtions, we ignore this time. + */ + return 0 ; + } + break ; + + default : /* Unexpected reply */ + + up->lineerror = 1 ; + break ; + + } + + return 1 ; + +} + +/**************************************************************************************************/ + +static int +jjy_receive_cdex_jst2000 ( struct recvbuf *rbufp ) +{ + + struct jjyunit *up ; + struct refclockproc *pp ; + struct peer *peer; + + char *pBuf ; + int iLen ; + int rc ; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *) rbufp->recv_srcclock ; + pp = peer->procptr ; + up = (struct jjyunit *) pp->unitptr ; + + if ( up->linediscipline == LDISC_RAW ) { + pBuf = up->rawbuf ; + iLen = up->charcount ; + } else { + pBuf = pp->a_lastcode ; + iLen = pp->lencode ; + } + + switch ( up->linecount ) { + + case 1 : /* JYYMMDD HHMMSSS */ + + if ( iLen < 15 ) { + up->lineerror = 1 ; + break ; + } + rc = sscanf ( pBuf, "J%2d%2d%2d%*1d%2d%2d%2d%1d", + &up->year, &up->month, &up->day, &up->hour, &up->minute, &up->second, &up->msecond ) ; + if ( rc != 7 || up->month < 1 || up->month > 12 || up->day < 1 || up->day > 31 + || up->hour > 23 || up->minute > 59 || up->second > 60 ) { + up->lineerror = 1 ; + break ; + } + up->year += 2000 ; + up->msecond *= 100 ; + break ; + + default : /* Unexpected reply */ + + up->lineerror = 1 ; + break ; + + } + + return 1 ; + +} + +/**************************************************************************************************/ +/* jjy_poll - called by the transmit procedure */ +/**************************************************************************************************/ +static void +jjy_poll ( int unit, struct peer *peer ) +{ + + struct jjyunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct jjyunit *) pp->unitptr ; + + if ( pp->polls > 0 && up->linecount == 0 ) { + /* + * No reply for last command + */ + refclock_report ( peer, CEVNT_TIMEOUT ) ; + } + +#ifdef DEBUG + if ( debug ) { + printf ( "jjy_poll (refclock_jjy.c) : %ld\n", pp->polls ) ; + } +#endif + + pp->polls ++ ; + + up->linecount = 0 ; + up->lineerror = 0 ; + up->charcount = 0 ; + + switch ( up->unittype ) { + + case UNITTYPE_TRISTATE_JJY01 : + jjy_poll_tristate_jjy01 ( unit, peer ) ; + break ; + + case UNITTYPE_CDEX_JST2000 : + jjy_poll_cdex_jst2000 ( unit, peer ) ; + break ; + + default : + break ; + + } + +} + +/**************************************************************************************************/ + +static void +jjy_poll_tristate_jjy01 ( int unit, struct peer *peer ) +{ + + struct jjyunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct jjyunit *) pp->unitptr ; + + /* + * Send "date<CR><LF>" command + */ + + if ( write ( pp->io.fd, "date\r\n",6 ) != 6 ) { + refclock_report ( peer, CEVNT_FAULT ) ; + } + + /* + * Send "stim<CR><LF>" or "time<CR><LF>" command + */ + + if ( up->version >= 100 ) { + if ( write ( pp->io.fd, "stim\r\n",6 ) != 6 ) { + refclock_report ( peer, CEVNT_FAULT ) ; + } + } else { + if ( write ( pp->io.fd, "time\r\n",6 ) != 6 ) { + refclock_report ( peer, CEVNT_FAULT ) ; + } + } + +} + +/**************************************************************************************************/ + +static void +jjy_poll_cdex_jst2000 ( int unit, struct peer *peer ) +{ + + struct refclockproc *pp; + + pp = peer->procptr; + + /* + * Send "<ENQ>1J<ETX>" command + */ + + if ( write ( pp->io.fd, "\0051J\003", 4 ) != 4 ) { + refclock_report ( peer, CEVNT_FAULT ) ; + } + +} + +#else +int refclock_jjy_bs ; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_jupiter.c b/ntpd/refclock_jupiter.c new file mode 100644 index 0000000..eff088b --- /dev/null +++ b/ntpd/refclock_jupiter.c @@ -0,0 +1,1140 @@ +/* + * Copyright (c) 1997, 1998, 2003 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Lawrence Berkeley Laboratory. + * 4. The name of the University may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_JUPITER) && defined(HAVE_PPSAPI) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#include "jupiter.h" + +#ifdef HAVE_PPSAPI +# ifdef HAVE_TIMEPPS_H +# include <timepps.h> +# else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +# endif +#endif + +#ifdef XNTP_BIG_ENDIAN +#define getshort(s) ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) +#define putshort(s) ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff)) +#else +#define getshort(s) (s) +#define putshort(s) (s) +#endif + +/* XXX */ +#ifdef sun +char *strerror(int); +#endif + +/* + * This driver supports the Rockwell Jupiter GPS Receiver board + * adapted to precision timing applications. It requires the + * ppsclock line discipline or streams module described in the + * Line Disciplines and Streams Drivers page. It also requires a + * gadget box and 1-PPS level converter, such as described in the + * Pulse-per-second (PPS) Signal Interfacing page. + * + * It may work (with minor modifications) with other Rockwell GPS + * receivers such as the CityTracker. + */ + +/* + * GPS Definitions + */ +#define DEVICE "/dev/gps%d" /* device name and unit */ +#define SPEED232 B9600 /* baud */ + +/* + * Radio interface parameters + */ +#define PRECISION (-18) /* precision assumed (about 4 us) */ +#define REFID "GPS\0" /* reference id */ +#define DESCRIPTION "Rockwell Jupiter GPS Receiver" /* who we are */ +#define DEFFUDGETIME 0 /* default fudge time (ms) */ + +/* Unix timestamp for the GPS epoch: January 6, 1980 */ +#define GPS_EPOCH 315964800 + +/* Double short to unsigned int */ +#define DS2UI(p) ((getshort((p)[1]) << 16) | getshort((p)[0])) + +/* Double short to signed int */ +#define DS2I(p) ((getshort((p)[1]) << 16) | getshort((p)[0])) + +/* One week's worth of seconds */ +#define WEEKSECS (7 * 24 * 60 * 60) + +/* + * Jupiter unit control structure. + */ +struct instance { + struct peer *peer; /* peer */ + u_int pollcnt; /* poll message counter */ + u_int polled; /* Hand in a time sample? */ +#ifdef HAVE_PPSAPI + pps_params_t pps_params; /* pps parameters */ + pps_info_t pps_info; /* last pps data */ + pps_handle_t pps_handle; /* pps handle */ + u_int assert; /* pps edge to use */ + struct timespec ts; /* last timestamp */ +#endif + l_fp limit; + u_int gpos_gweek; /* Current GPOS GPS week number */ + u_int gpos_sweek; /* Current GPOS GPS seconds into week */ + u_int gweek; /* current GPS week number */ + u_int32 lastsweek; /* last seconds into GPS week */ + time_t timecode; /* current ntp timecode */ + u_int32 stime; /* used to detect firmware bug */ + int wantid; /* don't reconfig on channel id msg */ + u_int moving; /* mobile platform? */ + u_char sloppyclockflag; /* fudge flags */ + u_short sbuf[512]; /* local input buffer */ + int ssize; /* space used in sbuf */ +}; + +/* + * Function prototypes + */ +static void jupiter_canmsg P((struct instance *, u_int)); +static u_short jupiter_cksum P((u_short *, u_int)); +static int jupiter_config P((struct instance *)); +static void jupiter_debug P((struct peer *, char *, char *, ...)) + __attribute__ ((format (printf, 3, 4))); +static char * jupiter_parse_t P((struct instance *, u_short *)); +static char * jupiter_parse_gpos P((struct instance *, u_short *)); +static void jupiter_platform P((struct instance *, u_int)); +static void jupiter_poll P((int, struct peer *)); +static void jupiter_control P((int, struct refclockstat *, struct + refclockstat *, struct peer *)); +#ifdef HAVE_PPSAPI +static int jupiter_ppsapi P((struct instance *, int, int)); +static int jupiter_pps P((struct instance *)); +#endif /* HAVE_PPSAPI */ +static int jupiter_recv P((struct instance *)); +static void jupiter_receive P((struct recvbuf *rbufp)); +static void jupiter_reqmsg P((struct instance *, u_int, u_int)); +static void jupiter_reqonemsg P((struct instance *, u_int)); +static char * jupiter_send P((struct instance *, struct jheader *)); +static void jupiter_shutdown P((int, struct peer *)); +static int jupiter_start P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_jupiter = { + jupiter_start, /* start up driver */ + jupiter_shutdown, /* shut down driver */ + jupiter_poll, /* transmit poll message */ + jupiter_control, /* (clock control) */ + noentry, /* (clock init) */ + noentry, /* (clock buginfo) */ + NOFLAGS /* not used */ +}; + +/* + * jupiter_start - open the devices and initialize data for processing + */ +static int +jupiter_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + struct instance *instance; + int fd = -1; + char gpsdev[20]; + + /* + * Open serial port + */ + (void)sprintf(gpsdev, DEVICE, unit); + fd = refclock_open(gpsdev, SPEED232, LDISC_RAW); + if (fd == 0) { + jupiter_debug(peer, "jupiter_start", "open %s: %s", + gpsdev, strerror(errno)); + return (0); + } + + /* Allocate unit structure */ + if ((instance = (struct instance *) + emalloc(sizeof(struct instance))) == NULL) { + (void) close(fd); + return (0); + } + memset((char *)instance, 0, sizeof(struct instance)); + instance->peer = peer; + pp = peer->procptr; + pp->io.clock_recv = jupiter_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(instance); + return (0); + } + pp->unitptr = (caddr_t)instance; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + +#ifdef HAVE_PPSAPI + /* + * Start the PPSAPI interface if it is there. Default to use + * the assert edge and do not enable the kernel hardpps. + */ + if (time_pps_create(fd, &instance->pps_handle) < 0) { + instance->pps_handle = 0; + msyslog(LOG_ERR, + "refclock_jupiter: time_pps_create failed: %m"); + } + else if (!jupiter_ppsapi(instance, 0, 0)) + goto clean_up; +#endif /* HAVE_PPSAPI */ + + /* Ensure the receiver is properly configured */ + if (!jupiter_config(instance)) + goto clean_up; + + return (1); + +clean_up: + jupiter_shutdown(unit, peer); + pp->unitptr = 0; + return (0); +} + +/* + * jupiter_shutdown - shut down the clock + */ +static void +jupiter_shutdown(int unit, struct peer *peer) +{ + struct instance *instance; + struct refclockproc *pp; + + pp = peer->procptr; + instance = (struct instance *)pp->unitptr; + if(!instance) + return; + +#ifdef HAVE_PPSAPI + if (instance->pps_handle) { + time_pps_destroy(instance->pps_handle); + instance->pps_handle = 0; + } +#endif /* HAVE_PPSAPI */ + + io_closeclock(&pp->io); + free(instance); +} + +/* + * jupiter_config - Configure the receiver + */ +static int +jupiter_config(struct instance *instance) +{ + jupiter_debug(instance->peer, "jupiter_config", "init receiver"); + + /* + * Initialize the unit variables + */ + instance->sloppyclockflag = instance->peer->procptr->sloppyclockflag; + instance->moving = !!(instance->sloppyclockflag & CLK_FLAG2); + if (instance->moving) + jupiter_debug(instance->peer, "jupiter_config", + "mobile platform"); + + instance->pollcnt = 2; + instance->polled = 0; + instance->gpos_gweek = 0; + instance->gpos_sweek = 0; + instance->gweek = 0; + instance->lastsweek = 2 * WEEKSECS; + instance->timecode = 0; + instance->stime = 0; + instance->ssize = 0; + + /* Stop outputting all messages */ + jupiter_canmsg(instance, JUPITER_ALL); + + /* Request the receiver id so we can syslog the firmware version */ + jupiter_reqonemsg(instance, JUPITER_O_ID); + + /* Flag that this the id was requested (so we don't get called again) */ + instance->wantid = 1; + + /* Request perodic time mark pulse messages */ + jupiter_reqmsg(instance, JUPITER_O_PULSE, 1); + + /* Request perodic geodetic position status */ + jupiter_reqmsg(instance, JUPITER_O_GPOS, 1); + + /* Set application platform type */ + if (instance->moving) + jupiter_platform(instance, JUPITER_I_PLAT_MED); + else + jupiter_platform(instance, JUPITER_I_PLAT_LOW); + + return (1); +} + +#ifdef HAVE_PPSAPI +/* + * Initialize PPSAPI + */ +int +jupiter_ppsapi( + struct instance *instance, /* unit structure pointer */ + int enb_clear, /* clear enable */ + int enb_hardpps /* hardpps enable */ + ) +{ + int capability; + + if (time_pps_getcap(instance->pps_handle, &capability) < 0) { + msyslog(LOG_ERR, + "refclock_jupiter: time_pps_getcap failed: %m"); + return (0); + } + memset(&instance->pps_params, 0, sizeof(pps_params_t)); + if (enb_clear) + instance->pps_params.mode = capability & PPS_CAPTURECLEAR; + else + instance->pps_params.mode = capability & PPS_CAPTUREASSERT; + if (!(instance->pps_params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { + msyslog(LOG_ERR, + "refclock_jupiter: invalid capture edge %d", + !enb_clear); + return (0); + } + instance->pps_params.mode |= PPS_TSFMT_TSPEC; + if (time_pps_setparams(instance->pps_handle, &instance->pps_params) < 0) { + msyslog(LOG_ERR, + "refclock_jupiter: time_pps_setparams failed: %m"); + return (0); + } + if (enb_hardpps) { + if (time_pps_kcbind(instance->pps_handle, PPS_KC_HARDPPS, + instance->pps_params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR), + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, + "refclock_jupiter: time_pps_kcbind failed: %m"); + return (0); + } + pps_enable = 1; + } +/* instance->peer->precision = PPS_PRECISION; */ + +#if DEBUG + if (debug) { + time_pps_getparams(instance->pps_handle, &instance->pps_params); + jupiter_debug(instance->peer, "refclock_jupiter", + "pps capability 0x%x version %d mode 0x%x kern %d", + capability, instance->pps_params.api_version, + instance->pps_params.mode, enb_hardpps); + } +#endif + + return (1); +} + +/* + * Get PPSAPI timestamps. + * + * Return 0 on failure and 1 on success. + */ +static int +jupiter_pps(struct instance *instance) +{ + pps_info_t pps_info; + struct timespec timeout, ts; + double dtemp; + l_fp tstmp; + + /* + * Convert the timespec nanoseconds field to ntp l_fp units. + */ + if (instance->pps_handle == 0) + return 1; + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + memcpy(&pps_info, &instance->pps_info, sizeof(pps_info_t)); + if (time_pps_fetch(instance->pps_handle, PPS_TSFMT_TSPEC, &instance->pps_info, + &timeout) < 0) + return 1; + if (instance->pps_params.mode & PPS_CAPTUREASSERT) { + if (pps_info.assert_sequence == + instance->pps_info.assert_sequence) + return 1; + ts = instance->pps_info.assert_timestamp; + } else if (instance->pps_params.mode & PPS_CAPTURECLEAR) { + if (pps_info.clear_sequence == + instance->pps_info.clear_sequence) + return 1; + ts = instance->pps_info.clear_timestamp; + } else { + return 1; + } + if ((instance->ts.tv_sec == ts.tv_sec) && (instance->ts.tv_nsec == ts.tv_nsec)) + return 1; + instance->ts = ts; + + tstmp.l_ui = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec * FRAC / 1e9; + tstmp.l_uf = (u_int32)dtemp; + instance->peer->procptr->lastrec = tstmp; + return 0; +} +#endif /* HAVE_PPSAPI */ + +/* + * jupiter_poll - jupiter watchdog routine + */ +static void +jupiter_poll(int unit, struct peer *peer) +{ + struct instance *instance; + struct refclockproc *pp; + + pp = peer->procptr; + instance = (struct instance *)pp->unitptr; + + /* + * You don't need to poll this clock. It puts out timecodes + * once per second. If asked for a timestamp, take note. + * The next time a timecode comes in, it will be fed back. + */ + + /* + * If we haven't had a response in a while, reset the receiver. + */ + if (instance->pollcnt > 0) { + instance->pollcnt--; + } else { + refclock_report(peer, CEVNT_TIMEOUT); + + /* Request the receiver id to trigger a reconfig */ + jupiter_reqonemsg(instance, JUPITER_O_ID); + instance->wantid = 0; + } + + /* + * polled every 64 seconds. Ask jupiter_receive to hand in + * a timestamp. + */ + instance->polled = 1; + pp->polls++; +} + +/* + * jupiter_control - fudge control + */ +static void +jupiter_control( + int unit, /* unit (not used) */ + struct refclockstat *in, /* input parameters (not used) */ + struct refclockstat *out, /* output parameters (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct instance *instance; + u_char sloppyclockflag; + + pp = peer->procptr; + instance = (struct instance *)pp->unitptr; + + DTOLFP(pp->fudgetime2, &instance->limit); + /* Force positive value. */ + if (L_ISNEG(&instance->limit)) + L_NEG(&instance->limit); + +#ifdef HAVE_PPSAPI + instance->assert = !(pp->sloppyclockflag & CLK_FLAG3); + jupiter_ppsapi(instance, !instance->assert, 0); +#endif /* HAVE_PPSAPI */ + + sloppyclockflag = instance->sloppyclockflag; + instance->sloppyclockflag = pp->sloppyclockflag; + if ((instance->sloppyclockflag & CLK_FLAG2) != + (sloppyclockflag & CLK_FLAG2)) { + jupiter_debug(peer, + "jupiter_control", + "mode switch: reset receiver"); + jupiter_config(instance); + return; + } +} + +/* + * jupiter_receive - receive gps data + * Gag me! + */ +static void +jupiter_receive(struct recvbuf *rbufp) +{ + int bpcnt, cc, size, ppsret; + time_t last_timecode; + u_int32 laststime; + char *cp; + u_char *bp; + u_short *sp; + struct jid *ip; + struct jheader *hp; + struct peer *peer; + struct refclockproc *pp; + struct instance *instance; + l_fp tstamp; + + /* Initialize pointers and read the timecode and timestamp */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + instance = (struct instance *)pp->unitptr; + + bp = (u_char *)rbufp->recv_buffer; + bpcnt = rbufp->recv_length; + + /* This shouldn't happen */ + if (bpcnt > sizeof(instance->sbuf) - instance->ssize) + bpcnt = sizeof(instance->sbuf) - instance->ssize; + + /* Append to input buffer */ + memcpy((u_char *)instance->sbuf + instance->ssize, bp, bpcnt); + instance->ssize += bpcnt; + + /* While there's at least a header and we parse an intact message */ + while (instance->ssize > sizeof(*hp) && (cc = jupiter_recv(instance)) > 0) { + instance->pollcnt = 2; + + tstamp = rbufp->recv_time; + hp = (struct jheader *)instance->sbuf; + sp = (u_short *)(hp + 1); + size = cc - sizeof(*hp); + switch (getshort(hp->id)) { + + case JUPITER_O_PULSE: + if (size != sizeof(struct jpulse)) { + jupiter_debug(peer, + "jupiter_receive", "pulse: len %d != %u", + size, (int)sizeof(struct jpulse)); + refclock_report(peer, CEVNT_BADREPLY); + break; + } + + /* + * There appears to be a firmware bug related + * to the pulse message; in addition to the one + * per second messages, we get an extra pulse + * message once an hour (on the anniversary of + * the cold start). It seems to come 200 ms + * after the one requested. So if we've seen a + * pulse message in the last 210 ms, we skip + * this one. + */ + laststime = instance->stime; + instance->stime = DS2UI(((struct jpulse *)sp)->stime); + if (laststime != 0 && instance->stime - laststime <= 21) { + jupiter_debug(peer, "jupiter_receive", + "avoided firmware bug (stime %.2f, laststime %.2f)", + (double)instance->stime * 0.01, (double)laststime * 0.01); + break; + } + + /* Retrieve pps timestamp */ + ppsret = jupiter_pps(instance); + + /* + * Add one second if msg received early + * (i.e. before limit, a.k.a. fudgetime2) in + * the second. + */ + L_SUB(&tstamp, &pp->lastrec); + if (!L_ISGEQ(&tstamp, &instance->limit)) + ++pp->lastrec.l_ui; + + /* Parse timecode (even when there's no pps) */ + last_timecode = instance->timecode; + if ((cp = jupiter_parse_t(instance, sp)) != NULL) { + jupiter_debug(peer, + "jupiter_receive", "pulse: %s", cp); + break; + } + + /* Bail if we didn't get a pps timestamp */ + if (ppsret) + break; + + /* Bail if we don't have the last timecode yet */ + if (last_timecode == 0) + break; + + /* Add the new sample to a median filter */ + tstamp.l_ui = JAN_1970 + last_timecode; + tstamp.l_uf = 0; + + refclock_process_offset(pp, tstamp, pp->lastrec, pp->fudgetime1); + + /* + * The clock will blurt a timecode every second + * but we only want one when polled. If we + * havn't been polled, bail out. + */ + if (!instance->polled) + break; + instance->polled = 0; + + /* + * It's a live one! Remember this time. + */ + + pp->lastref = pp->lastrec; + refclock_receive(peer); + + /* + * If we get here - what we got from the clock is + * OK, so say so + */ + refclock_report(peer, CEVNT_NOMINAL); + + /* + * We have succeeded in answering the poll. + * Turn off the flag and return + */ + instance->polled = 0; + break; + + case JUPITER_O_GPOS: + if (size != sizeof(struct jgpos)) { + jupiter_debug(peer, + "jupiter_receive", "gpos: len %d != %u", + size, (int)sizeof(struct jgpos)); + refclock_report(peer, CEVNT_BADREPLY); + break; + } + + if ((cp = jupiter_parse_gpos(instance, sp)) != NULL) { + jupiter_debug(peer, + "jupiter_receive", "gpos: %s", cp); + break; + } + break; + + case JUPITER_O_ID: + if (size != sizeof(struct jid)) { + jupiter_debug(peer, + "jupiter_receive", "id: len %d != %u", + size, (int)sizeof(struct jid)); + refclock_report(peer, CEVNT_BADREPLY); + break; + } + /* + * If we got this message because the Jupiter + * just powered instance, it needs to be reconfigured. + */ + ip = (struct jid *)sp; + jupiter_debug(peer, + "jupiter_receive", "%s chan ver %s, %s (%s)", + ip->chans, ip->vers, ip->date, ip->opts); + msyslog(LOG_DEBUG, + "jupiter_receive: %s chan ver %s, %s (%s)\n", + ip->chans, ip->vers, ip->date, ip->opts); + if (instance->wantid) + instance->wantid = 0; + else { + jupiter_debug(peer, + "jupiter_receive", "reset receiver"); + jupiter_config(instance); + /* + * Restore since jupiter_config() just + * zeroed it + */ + instance->ssize = cc; + } + break; + + default: + jupiter_debug(peer, + "jupiter_receive", "unknown message id %d", + getshort(hp->id)); + break; + } + instance->ssize -= cc; + if (instance->ssize < 0) { + fprintf(stderr, "jupiter_recv: negative ssize!\n"); + abort(); + } else if (instance->ssize > 0) + memcpy(instance->sbuf, (u_char *)instance->sbuf + cc, instance->ssize); + } +} + +static char * +jupiter_parse_t(struct instance *instance, u_short *sp) +{ + struct tm *tm; + char *cp; + struct jpulse *jp; + u_int32 sweek; + time_t last_timecode; + u_short flags; + + jp = (struct jpulse *)sp; + + /* The timecode is presented as seconds into the current GPS week */ + sweek = DS2UI(jp->sweek) % WEEKSECS; + + /* + * If we don't know the current GPS week, calculate it from the + * current time. (It's too bad they didn't include this + * important value in the pulse message). We'd like to pick it + * up from one of the other messages like gpos or chan but they + * don't appear to be synchronous with time keeping and changes + * too soon (something like 10 seconds before the new GPS + * week). + * + * If we already know the current GPS week, increment it when + * we wrap into a new week. + */ + if (instance->gweek == 0) { + if (!instance->gpos_gweek) { + return ("jupiter_parse_t: Unknown gweek"); + } + + instance->gweek = instance->gpos_gweek; + + /* + * Fix warps. GPOS has GPS time and PULSE has UTC. + * Plus, GPOS need not be completely in synch with + * the PPS signal. + */ + if (instance->gpos_sweek >= sweek) { + if ((instance->gpos_sweek - sweek) > WEEKSECS / 2) + ++instance->gweek; + } + else { + if ((sweek - instance->gpos_sweek) > WEEKSECS / 2) + --instance->gweek; + } + } + else if (sweek == 0 && instance->lastsweek == WEEKSECS - 1) { + ++instance->gweek; + jupiter_debug(instance->peer, + "jupiter_parse_t", "NEW gps week %u", instance->gweek); + } + + /* + * See if the sweek stayed the same (this happens when there is + * no pps pulse). + * + * Otherwise, look for time warps: + * + * - we have stored at least one lastsweek and + * - the sweek didn't increase by one and + * - we didn't wrap to a new GPS week + * + * Then we warped. + */ + if (instance->lastsweek == sweek) + jupiter_debug(instance->peer, + "jupiter_parse_t", "gps sweek not incrementing (%d)", + sweek); + else if (instance->lastsweek != 2 * WEEKSECS && + instance->lastsweek + 1 != sweek && + !(sweek == 0 && instance->lastsweek == WEEKSECS - 1)) + jupiter_debug(instance->peer, + "jupiter_parse_t", "gps sweek jumped (was %d, now %d)", + instance->lastsweek, sweek); + instance->lastsweek = sweek; + + /* This timecode describes next pulse */ + last_timecode = instance->timecode; + instance->timecode = + GPS_EPOCH + (instance->gweek * WEEKSECS) + sweek; + + if (last_timecode == 0) + /* XXX debugging */ + jupiter_debug(instance->peer, + "jupiter_parse_t", "UTC <none> (gweek/sweek %u/%u)", + instance->gweek, sweek); + else { + /* XXX debugging */ + tm = gmtime(&last_timecode); + cp = asctime(tm); + + jupiter_debug(instance->peer, + "jupiter_parse_t", "UTC %.24s (gweek/sweek %u/%u)", + cp, instance->gweek, sweek); + + /* Billboard last_timecode (which is now the current time) */ + instance->peer->procptr->year = tm->tm_year + 1900; + instance->peer->procptr->day = tm->tm_yday + 1; + instance->peer->procptr->hour = tm->tm_hour; + instance->peer->procptr->minute = tm->tm_min; + instance->peer->procptr->second = tm->tm_sec; + } + + flags = getshort(jp->flags); + + /* Toss if not designated "valid" by the gps */ + if ((flags & JUPITER_O_PULSE_VALID) == 0) { + refclock_report(instance->peer, CEVNT_BADTIME); + return ("time mark not valid"); + } + + /* We better be sync'ed to UTC... */ + if ((flags & JUPITER_O_PULSE_UTC) == 0) { + refclock_report(instance->peer, CEVNT_BADTIME); + return ("time mark not sync'ed to UTC"); + } + + return (NULL); +} + +static char * +jupiter_parse_gpos(struct instance *instance, u_short *sp) +{ + struct jgpos *jg; + time_t t; + struct tm *tm; + char *cp; + + jg = (struct jgpos *)sp; + + if (jg->navval != 0) { + /* + * Solution not valid. Use caution and refuse + * to determine GPS week from this message. + */ + instance->gpos_gweek = 0; + instance->gpos_sweek = 0; + return ("Navigation solution not valid"); + } + + instance->gpos_gweek = jg->gweek; + instance->gpos_sweek = DS2UI(jg->sweek); + while(instance->gpos_sweek >= WEEKSECS) { + instance->gpos_sweek -= WEEKSECS; + ++instance->gpos_gweek; + } + instance->gweek = 0; + + t = GPS_EPOCH + (instance->gpos_gweek * WEEKSECS) + instance->gpos_sweek; + tm = gmtime(&t); + cp = asctime(tm); + + jupiter_debug(instance->peer, + "jupiter_parse_g", "GPS %.24s (gweek/sweek %u/%u)", + cp, instance->gpos_gweek, instance->gpos_sweek); + return (NULL); +} + +/* + * jupiter_debug - print debug messages + */ +#if defined(__STDC__) || defined(SYS_WINNT) +static void +jupiter_debug(struct peer *peer, char *function, char *fmt, ...) +#else +static void +jupiter_debug(peer, function, fmt, va_alist) + struct peer *peer; + char *function; + char *fmt; +#endif /* __STDC__ */ +{ + char buffer[200]; + va_list ap; + +#if defined(__STDC__) || defined(SYS_WINNT) + va_start(ap, fmt); +#else + va_start(ap); +#endif /* __STDC__ */ + /* + * Print debug message to stdout + * In the future, we may want to get get more creative... + */ + vsnprintf(buffer, sizeof(buffer), fmt, ap); + record_clock_stats(&(peer->srcadr), buffer); + if (debug) { + fprintf(stdout, "%s: ", function); + fprintf(stdout, buffer); + fprintf(stdout, "\n"); + fflush(stdout); + } + + va_end(ap); +} + +/* Checksum and transmit a message to the Jupiter */ +static char * +jupiter_send(struct instance *instance, struct jheader *hp) +{ + u_int len, size; + int cc; + u_short *sp; + static char errstr[132]; + + size = sizeof(*hp); + hp->hsum = putshort(jupiter_cksum((u_short *)hp, + (size / sizeof(u_short)) - 1)); + len = getshort(hp->len); + if (len > 0) { + sp = (u_short *)(hp + 1); + sp[len] = putshort(jupiter_cksum(sp, len)); + size += (len + 1) * sizeof(u_short); + } + + if ((cc = write(instance->peer->procptr->io.fd, (char *)hp, size)) < 0) { + (void)sprintf(errstr, "write: %s", strerror(errno)); + return (errstr); + } else if (cc != size) { + (void)sprintf(errstr, "short write (%d != %d)", cc, size); + return (errstr); + } + return (NULL); +} + +/* Request periodic message output */ +static struct { + struct jheader jheader; + struct jrequest jrequest; +} reqmsg = { + { putshort(JUPITER_SYNC), 0, + putshort((sizeof(struct jrequest) / sizeof(u_short)) - 1), + 0, (u_char)putshort(JUPITER_FLAG_REQUEST | JUPITER_FLAG_NAK | + JUPITER_FLAG_CONN | JUPITER_FLAG_LOG), 0 }, + { 0, 0, 0, 0 } +}; + +/* An interval of zero means to output on trigger */ +static void +jupiter_reqmsg(struct instance *instance, u_int id, + u_int interval) +{ + struct jheader *hp; + struct jrequest *rp; + char *cp; + + hp = &reqmsg.jheader; + hp->id = putshort(id); + rp = &reqmsg.jrequest; + rp->trigger = putshort(interval == 0); + rp->interval = putshort(interval); + if ((cp = jupiter_send(instance, hp)) != NULL) + jupiter_debug(instance->peer, "jupiter_reqmsg", "%u: %s", id, cp); +} + +/* Cancel periodic message output */ +static struct jheader canmsg = { + putshort(JUPITER_SYNC), 0, 0, 0, + (u_char)putshort(JUPITER_FLAG_REQUEST | JUPITER_FLAG_NAK | JUPITER_FLAG_DISC), + 0 +}; + +static void +jupiter_canmsg(struct instance *instance, u_int id) +{ + struct jheader *hp; + char *cp; + + hp = &canmsg; + hp->id = putshort(id); + if ((cp = jupiter_send(instance, hp)) != NULL) + jupiter_debug(instance->peer, "jupiter_canmsg", "%u: %s", id, cp); +} + +/* Request a single message output */ +static struct jheader reqonemsg = { + putshort(JUPITER_SYNC), 0, 0, 0, + (u_char)putshort(JUPITER_FLAG_REQUEST | JUPITER_FLAG_NAK | JUPITER_FLAG_QUERY), + 0 +}; + +static void +jupiter_reqonemsg(struct instance *instance, u_int id) +{ + struct jheader *hp; + char *cp; + + hp = &reqonemsg; + hp->id = putshort(id); + if ((cp = jupiter_send(instance, hp)) != NULL) + jupiter_debug(instance->peer, "jupiter_reqonemsg", "%u: %s", id, cp); +} + +/* Set the platform dynamics */ +static struct { + struct jheader jheader; + struct jplat jplat; +} platmsg = { + { putshort(JUPITER_SYNC), putshort(JUPITER_I_PLAT), + putshort((sizeof(struct jplat) / sizeof(u_short)) - 1), 0, + (u_char)putshort(JUPITER_FLAG_REQUEST | JUPITER_FLAG_NAK), 0 }, + { 0, 0, 0 } +}; + +static void +jupiter_platform(struct instance *instance, u_int platform) +{ + struct jheader *hp; + struct jplat *pp; + char *cp; + + hp = &platmsg.jheader; + pp = &platmsg.jplat; + pp->platform = putshort(platform); + if ((cp = jupiter_send(instance, hp)) != NULL) + jupiter_debug(instance->peer, "jupiter_platform", "%u: %s", platform, cp); +} + +/* Checksum "len" shorts */ +static u_short +jupiter_cksum(u_short *sp, u_int len) +{ + u_short sum, x; + + sum = 0; + while (len-- > 0) { + x = *sp++; + sum += getshort(x); + } + return (~sum + 1); +} + +/* Return the size of the next message (or zero if we don't have it all yet) */ +static int +jupiter_recv(struct instance *instance) +{ + int n, len, size, cc; + struct jheader *hp; + u_char *bp; + u_short *sp; + + /* Must have at least a header's worth */ + cc = sizeof(*hp); + size = instance->ssize; + if (size < cc) + return (0); + + /* Search for the sync short if missing */ + sp = instance->sbuf; + hp = (struct jheader *)sp; + if (getshort(hp->sync) != JUPITER_SYNC) { + /* Wasn't at the front, sync up */ + jupiter_debug(instance->peer, "jupiter_recv", "syncing"); + bp = (u_char *)sp; + n = size; + while (n >= 2) { + if (bp[0] != (JUPITER_SYNC & 0xff)) { + /* + jupiter_debug(instance->peer, "{0x%x}", bp[0]); + */ + ++bp; + --n; + continue; + } + if (bp[1] == ((JUPITER_SYNC >> 8) & 0xff)) + break; + /* + jupiter_debug(instance->peer, "{0x%x 0x%x}", bp[0], bp[1]); + */ + bp += 2; + n -= 2; + } + /* + jupiter_debug(instance->peer, "\n"); + */ + /* Shuffle data to front of input buffer */ + if (n > 0) + memcpy(sp, bp, n); + size = n; + instance->ssize = size; + if (size < cc || hp->sync != JUPITER_SYNC) + return (0); + } + + if (jupiter_cksum(sp, (cc / sizeof(u_short) - 1)) != + getshort(hp->hsum)) { + jupiter_debug(instance->peer, "jupiter_recv", "bad header checksum!"); + /* This is drastic but checksum errors should be rare */ + instance->ssize = 0; + return (0); + } + + /* Check for a payload */ + len = getshort(hp->len); + if (len > 0) { + n = (len + 1) * sizeof(u_short); + /* Not enough data yet */ + if (size < cc + n) + return (0); + + /* Check payload checksum */ + sp = (u_short *)(hp + 1); + if (jupiter_cksum(sp, len) != getshort(sp[len])) { + jupiter_debug(instance->peer, + "jupiter_recv", "bad payload checksum!"); + /* This is drastic but checksum errors should be rare */ + instance->ssize = 0; + return (0); + } + cc += n; + } + return (cc); +} + +#else /* not (REFCLOCK && CLOCK_JUPITER && HAVE_PPSAPI) */ +int refclock_jupiter_bs; +#endif /* not (REFCLOCK && CLOCK_JUPITER && HAVE_PPSAPI) */ diff --git a/ntpd/refclock_leitch.c b/ntpd/refclock_leitch.c new file mode 100644 index 0000000..d7cd9bb --- /dev/null +++ b/ntpd/refclock_leitch.c @@ -0,0 +1,620 @@ +/* + * refclock_leitch - clock driver for the Leitch CSD-5300 Master Clock + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_LEITCH) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef STREAM +#include <stropts.h> +#if defined(LEITCHCLK) +#include <sys/clkdefs.h> +#endif /* LEITCHCLK */ +#endif /* STREAM */ + +#include "ntp_stdlib.h" + + +/* + * Driver for Leitch CSD-5300 Master Clock System + * + * COMMANDS: + * DATE: D <CR> + * TIME: T <CR> + * STATUS: S <CR> + * LOOP: L <CR> + * + * FORMAT: + * DATE: YYMMDD<CR> + * TIME: <CR>/HHMMSS <CR>/HHMMSS <CR>/HHMMSS <CR>/ + * second bondaried on the stop bit of the <CR> + * second boundaries at '/' above. + * STATUS: G (good), D (diag fail), T (time not provided) or + * P (last phone update failed) + */ +#define MAXUNITS 1 /* max number of LEITCH units */ +#define LEITCHREFID "ATOM" /* reference id */ +#define LEITCH_DESCRIPTION "Leitch: CSD 5300 Master Clock System Driver" +#define LEITCH232 "/dev/leitch%d" /* name of radio device */ +#define SPEED232 B300 /* uart speed (300 baud) */ +#define leitch_send(A,M) \ +if (debug) fprintf(stderr,"write leitch %s\n",M); \ +if ((write(A->leitchio.fd,M,sizeof(M)) < 0)) {\ + if (debug) \ + fprintf(stderr, "leitch_send: unit %d send failed\n", A->unit); \ + else \ + msyslog(LOG_ERR, "leitch_send: unit %d send failed %m",A->unit);} + +#define STATE_IDLE 0 +#define STATE_DATE 1 +#define STATE_TIME1 2 +#define STATE_TIME2 3 +#define STATE_TIME3 4 + +/* + * LEITCH unit control structure + */ +struct leitchunit { + struct peer *peer; + struct refclockio leitchio; + u_char unit; + short year; + short yearday; + short month; + short day; + short hour; + short second; + short minute; + short state; + u_short fudge1; + l_fp reftime1; + l_fp reftime2; + l_fp reftime3; + l_fp codetime1; + l_fp codetime2; + l_fp codetime3; + u_long yearstart; +}; + +/* + * Function prototypes + */ +static void leitch_init P((void)); +static int leitch_start P((int, struct peer *)); +static void leitch_shutdown P((int, struct peer *)); +static void leitch_poll P((int, struct peer *)); +static void leitch_control P((int, struct refclockstat *, struct refclockstat *, struct peer *)); +#define leitch_buginfo noentry +static void leitch_receive P((struct recvbuf *)); +static void leitch_process P((struct leitchunit *)); +#if 0 +static void leitch_timeout P((struct peer *)); +#endif +static int leitch_get_date P((struct recvbuf *, struct leitchunit *)); +static int leitch_get_time P((struct recvbuf *, struct leitchunit *, int)); +static int days_per_year P((int)); + +static struct leitchunit leitchunits[MAXUNITS]; +static u_char unitinuse[MAXUNITS]; +static u_char stratumtouse[MAXUNITS]; +static u_int32 refid[MAXUNITS]; + +static char days_in_month [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +/* + * Transfer vector + */ +struct refclock refclock_leitch = { + leitch_start, leitch_shutdown, leitch_poll, + leitch_control, leitch_init, leitch_buginfo, NOFLAGS +}; + +/* + * leitch_init - initialize internal leitch driver data + */ +static void +leitch_init(void) +{ + int i; + + memset((char*)leitchunits, 0, sizeof(leitchunits)); + memset((char*)unitinuse, 0, sizeof(unitinuse)); + for (i = 0; i < MAXUNITS; i++) + memcpy((char *)&refid[i], LEITCHREFID, 4); +} + +/* + * leitch_shutdown - shut down a LEITCH clock + */ +static void +leitch_shutdown( + int unit, + struct peer *peer + ) +{ +#ifdef DEBUG + if (debug) + fprintf(stderr, "leitch_shutdown()\n"); +#endif +} + +/* + * leitch_poll - called by the transmit procedure + */ +static void +leitch_poll( + int unit, + struct peer *peer + ) +{ + struct leitchunit *leitch; + + /* start the state machine rolling */ + +#ifdef DEBUG + if (debug) + fprintf(stderr, "leitch_poll()\n"); +#endif + if (unit > MAXUNITS) { + /* XXXX syslog it */ + return; + } + + leitch = &leitchunits[unit]; + + if (leitch->state != STATE_IDLE) { + /* reset and wait for next poll */ + /* XXXX syslog it */ + leitch->state = STATE_IDLE; + } else { + leitch_send(leitch,"D\r"); + leitch->state = STATE_DATE; + } +} + +static void +leitch_control( + int unit, + struct refclockstat *in, + struct refclockstat *out, + struct peer *passed_peer + ) +{ + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, + "leitch_control: unit %d invalid", unit); + return; + } + + if (in) { + if (in->haveflags & CLK_HAVEVAL1) + stratumtouse[unit] = (u_char)(in->fudgeval1); + if (in->haveflags & CLK_HAVEVAL2) + refid[unit] = in->fudgeval2; + if (unitinuse[unit]) { + struct peer *peer; + + peer = (&leitchunits[unit])->peer; + peer->stratum = stratumtouse[unit]; + peer->refid = refid[unit]; + } + } + + if (out) { + memset((char *)out, 0, sizeof (struct refclockstat)); + out->type = REFCLK_ATOM_LEITCH; + out->haveflags = CLK_HAVEVAL1 | CLK_HAVEVAL2; + out->fudgeval1 = (int32)stratumtouse[unit]; + out->fudgeval2 = refid[unit]; + out->p_lastcode = ""; + out->clockdesc = LEITCH_DESCRIPTION; + } +} + +/* + * leitch_start - open the LEITCH devices and initialize data for processing + */ +static int +leitch_start( + int unit, + struct peer *peer + ) +{ + struct leitchunit *leitch; + int fd232; + char leitchdev[20]; + + /* + * Check configuration info. + */ + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "leitch_start: unit %d invalid", unit); + return (0); + } + + if (unitinuse[unit]) { + msyslog(LOG_ERR, "leitch_start: unit %d in use", unit); + return (0); + } + + /* + * Open serial port. + */ + (void) sprintf(leitchdev, LEITCH232, unit); + fd232 = open(leitchdev, O_RDWR, 0777); + if (fd232 == -1) { + msyslog(LOG_ERR, + "leitch_start: open of %s: %m", leitchdev); + return (0); + } + + leitch = &leitchunits[unit]; + memset((char*)leitch, 0, sizeof(*leitch)); + +#if defined(HAVE_SYSV_TTYS) + /* + * System V serial line parameters (termio interface) + * + */ + { struct termio ttyb; + if (ioctl(fd232, TCGETA, &ttyb) < 0) { + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, TCGETA): %m", leitchdev); + goto screwed; + } + ttyb.c_iflag = IGNBRK|IGNPAR|ICRNL; + ttyb.c_oflag = 0; + ttyb.c_cflag = SPEED232|CS8|CLOCAL|CREAD; + ttyb.c_lflag = ICANON; + ttyb.c_cc[VERASE] = ttyb.c_cc[VKILL] = '\0'; + if (ioctl(fd232, TCSETA, &ttyb) < 0) { + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, TCSETA): %m", leitchdev); + goto screwed; + } + } +#endif /* HAVE_SYSV_TTYS */ +#if defined(HAVE_TERMIOS) + /* + * POSIX serial line parameters (termios interface) + * + * The LEITCHCLK option provides timestamping at the driver level. + * It requires the tty_clk streams module. + */ + { struct termios ttyb, *ttyp; + + ttyp = &ttyb; + if (tcgetattr(fd232, ttyp) < 0) { + msyslog(LOG_ERR, + "leitch_start: tcgetattr(%s): %m", leitchdev); + goto screwed; + } + ttyp->c_iflag = IGNBRK|IGNPAR|ICRNL; + ttyp->c_oflag = 0; + ttyp->c_cflag = SPEED232|CS8|CLOCAL|CREAD; + ttyp->c_lflag = ICANON; + ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0'; + if (tcsetattr(fd232, TCSANOW, ttyp) < 0) { + msyslog(LOG_ERR, + "leitch_start: tcsetattr(%s): %m", leitchdev); + goto screwed; + } + if (tcflush(fd232, TCIOFLUSH) < 0) { + msyslog(LOG_ERR, + "leitch_start: tcflush(%s): %m", leitchdev); + goto screwed; + } + } +#endif /* HAVE_TERMIOS */ +#ifdef STREAM +#if defined(LEITCHCLK) + if (ioctl(fd232, I_PUSH, "clk") < 0) + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, I_PUSH, clk): %m", leitchdev); + if (ioctl(fd232, CLK_SETSTR, "\n") < 0) + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, CLK_SETSTR): %m", leitchdev); +#endif /* LEITCHCLK */ +#endif /* STREAM */ +#if defined(HAVE_BSD_TTYS) + /* + * 4.3bsd serial line parameters (sgttyb interface) + * + * The LEITCHCLK option provides timestamping at the driver level. + * It requires the tty_clk line discipline and 4.3bsd or later. + */ + { struct sgttyb ttyb; +#if defined(LEITCHCLK) + int ldisc = CLKLDISC; +#endif /* LEITCHCLK */ + + if (ioctl(fd232, TIOCGETP, &ttyb) < 0) { + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, TIOCGETP): %m", leitchdev); + goto screwed; + } + ttyb.sg_ispeed = ttyb.sg_ospeed = SPEED232; +#if defined(LEITCHCLK) + ttyb.sg_erase = ttyb.sg_kill = '\r'; + ttyb.sg_flags = RAW; +#else + ttyb.sg_erase = ttyb.sg_kill = '\0'; + ttyb.sg_flags = EVENP|ODDP|CRMOD; +#endif /* LEITCHCLK */ + if (ioctl(fd232, TIOCSETP, &ttyb) < 0) { + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, TIOCSETP): %m", leitchdev); + goto screwed; + } +#if defined(LEITCHCLK) + if (ioctl(fd232, TIOCSETD, &ldisc) < 0) { + msyslog(LOG_ERR, + "leitch_start: ioctl(%s, TIOCSETD): %m",leitchdev); + goto screwed; + } +#endif /* LEITCHCLK */ + } +#endif /* HAVE_BSD_TTYS */ + + /* + * Set up the structures + */ + leitch->peer = peer; + leitch->unit = unit; + leitch->state = STATE_IDLE; + leitch->fudge1 = 15; /* 15ms */ + + leitch->leitchio.clock_recv = leitch_receive; + leitch->leitchio.srcclock = (caddr_t) leitch; + leitch->leitchio.datalen = 0; + leitch->leitchio.fd = fd232; + if (!io_addclock(&leitch->leitchio)) { + goto screwed; + } + + /* + * All done. Initialize a few random peer variables, then + * return success. + */ + peer->precision = 0; + peer->stratum = stratumtouse[unit]; + peer->refid = refid[unit]; + unitinuse[unit] = 1; + return(1); + + /* + * Something broke; abandon ship. + */ + screwed: + close(fd232); + return(0); +} + +/* + * leitch_receive - receive data from the serial interface on a leitch + * clock + */ +static void +leitch_receive( + struct recvbuf *rbufp + ) +{ + struct leitchunit *leitch = (struct leitchunit *)rbufp->recv_srcclock; + +#ifdef DEBUG + if (debug) + fprintf(stderr, "leitch_recieve(%*.*s)\n", + rbufp->recv_length, rbufp->recv_length, + rbufp->recv_buffer); +#endif + if (rbufp->recv_length != 7) + return; /* The date is return with a trailing newline, + discard it. */ + + switch (leitch->state) { + case STATE_IDLE: /* unexpected, discard and resync */ + return; + case STATE_DATE: + if (!leitch_get_date(rbufp,leitch)) { + leitch->state = STATE_IDLE; + break; + } + leitch_send(leitch,"T\r"); +#ifdef DEBUG + if (debug) + fprintf(stderr, "%u\n",leitch->yearday); +#endif + leitch->state = STATE_TIME1; + break; + case STATE_TIME1: + if (!leitch_get_time(rbufp,leitch,1)) { + } + if (!clocktime(leitch->yearday,leitch->hour,leitch->minute, + leitch->second, 1, rbufp->recv_time.l_ui, + &leitch->yearstart, &leitch->reftime1.l_ui)) { + leitch->state = STATE_IDLE; + break; + } + leitch->reftime1.l_uf = 0; +#ifdef DEBUG + if (debug) + fprintf(stderr, "%lu\n", (u_long)leitch->reftime1.l_ui); +#endif + MSUTOTSF(leitch->fudge1, leitch->reftime1.l_uf); + leitch->codetime1 = rbufp->recv_time; + leitch->state = STATE_TIME2; + break; + case STATE_TIME2: + if (!leitch_get_time(rbufp,leitch,2)) { + } + if (!clocktime(leitch->yearday,leitch->hour,leitch->minute, + leitch->second, 1, rbufp->recv_time.l_ui, + &leitch->yearstart, &leitch->reftime2.l_ui)) { + leitch->state = STATE_IDLE; + break; + } +#ifdef DEBUG + if (debug) + fprintf(stderr, "%lu\n", (u_long)leitch->reftime2.l_ui); +#endif + MSUTOTSF(leitch->fudge1, leitch->reftime2.l_uf); + leitch->codetime2 = rbufp->recv_time; + leitch->state = STATE_TIME3; + break; + case STATE_TIME3: + if (!leitch_get_time(rbufp,leitch,3)) { + } + if (!clocktime(leitch->yearday,leitch->hour,leitch->minute, + leitch->second, GMT, rbufp->recv_time.l_ui, + &leitch->yearstart, &leitch->reftime3.l_ui)) { + leitch->state = STATE_IDLE; + break; + } +#ifdef DEBUG + if (debug) + fprintf(stderr, "%lu\n", (u_long)leitch->reftime3.l_ui); +#endif + MSUTOTSF(leitch->fudge1, leitch->reftime3.l_uf); + leitch->codetime3 = rbufp->recv_time; + leitch_process(leitch); + leitch->state = STATE_IDLE; + break; + default: + msyslog(LOG_ERR, + "leitech_receive: invalid state %d unit %d", + leitch->state, leitch->unit); + } +} + +/* + * leitch_process - process a pile of samples from the clock + * + * This routine uses a three-stage median filter to calculate offset and + * dispersion. reduce jitter. The dispersion is calculated as the span + * of the filter (max - min), unless the quality character (format 2) is + * non-blank, in which case the dispersion is calculated on the basis of + * the inherent tolerance of the internal radio oscillator, which is + * +-2e-5 according to the radio specifications. + */ +static void +leitch_process( + struct leitchunit *leitch + ) +{ + l_fp off; + l_fp tmp_fp; + /*double doffset;*/ + + off = leitch->reftime1; + L_SUB(&off,&leitch->codetime1); + tmp_fp = leitch->reftime2; + L_SUB(&tmp_fp,&leitch->codetime2); + if (L_ISGEQ(&off,&tmp_fp)) + off = tmp_fp; + tmp_fp = leitch->reftime3; + L_SUB(&tmp_fp,&leitch->codetime3); + + if (L_ISGEQ(&off,&tmp_fp)) + off = tmp_fp; + /*LFPTOD(&off, doffset);*/ + refclock_receive(leitch->peer); +} + +/* + * days_per_year + */ +static int +days_per_year( + int year + ) +{ + if (year%4) { /* not a potential leap year */ + return (365); + } else { + if (year % 100) { /* is a leap year */ + return (366); + } else { + if (year % 400) { + return (365); + } else { + return (366); + } + } + } +} + +static int +leitch_get_date( + struct recvbuf *rbufp, + struct leitchunit *leitch + ) +{ + int i; + + if (rbufp->recv_length < 6) + return(0); +#undef BAD /* confict: defined as (-1) in AIX sys/param.h */ +#define BAD(A) (rbufp->recv_buffer[A] < '0') || (rbufp->recv_buffer[A] > '9') + if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5)) + return(0); +#define ATOB(A) ((rbufp->recv_buffer[A])-'0') + leitch->year = ATOB(0)*10 + ATOB(1); + leitch->month = ATOB(2)*10 + ATOB(3); + leitch->day = ATOB(4)*10 + ATOB(5); + + /* sanity checks */ + if (leitch->month > 12) + return(0); + if (leitch->day > days_in_month[leitch->month-1]) + return(0); + + /* calculate yearday */ + i = 0; + leitch->yearday = leitch->day; + + while ( i < (leitch->month-1) ) + leitch->yearday += days_in_month[i++]; + + if ((days_per_year((leitch->year>90?1900:2000)+leitch->year)==365) && + leitch->month > 2) + leitch->yearday--; + + return(1); +} + +/* + * leitch_get_time + */ +static int +leitch_get_time( + struct recvbuf *rbufp, + struct leitchunit *leitch, + int which + ) +{ + if (BAD(0)||BAD(1)||BAD(2)||BAD(3)||BAD(4)||BAD(5)) + return(0); + leitch->hour = ATOB(0)*10 +ATOB(1); + leitch->minute = ATOB(2)*10 +ATOB(3); + leitch->second = ATOB(4)*10 +ATOB(5); + + if ((leitch->hour > 23) || (leitch->minute > 60) || + (leitch->second > 60)) + return(0); + return(1); +} + +#else +int refclock_leitch_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_local.c b/ntpd/refclock_local.c new file mode 100644 index 0000000..3478f43 --- /dev/null +++ b/ntpd/refclock_local.c @@ -0,0 +1,264 @@ + +/* + * refclock_local - local pseudo-clock driver + * + * wjm 17-aug-1995: add a hook for special treatment of VMS_LOCALUNIT + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef REFCLOCK + +#include "ntpd.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef KERNEL_PLL +#include "ntp_syscall.h" +#endif + +/* + * This is a hack to allow a machine to use its own system clock as a + * reference clock, i.e., to free-run using no outside clock discipline + * source. This is useful if you want to use NTP in an isolated + * environment with no radio clock or NIST modem available. Pick a + * machine that you figure has a good clock oscillator and configure it + * with this driver. Set the clock using the best means available, like + * eyeball-and-wristwatch. Then, point all the other machines at this + * one or use broadcast (not multicast) mode to distribute time. + * + * Another application for this driver is if you want to use a + * particular server's clock as the clock of last resort when all other + * normal synchronization sources have gone away. This is especially + * useful if that server has an ovenized oscillator. For this you would + * configure this driver at a higher stratum (say 5) to prevent the + * server's stratum from falling below that. + * + * A third application for this driver is when an external discipline + * source is available, such as the NIST "lockclock" program, which + * synchronizes the local clock via a telephone modem and the NIST + * Automated Computer Time Service (ACTS), or the Digital Time + * Synchronization Service (DTSS), which runs on DCE machines. In this + * case the stratum should be set at zero, indicating a bona fide + * stratum-1 source. Exercise some caution with this, since there is no + * easy way to telegraph via NTP that something might be wrong in the + * discipline source itself. In the case of DTSS, the local clock can + * have a rather large jitter, depending on the interval between + * corrections and the intrinsic frequency error of the clock + * oscillator. In extreme cases, this can cause clients to exceed the + * 128-ms slew window and drop off the NTP subnet. + * + * THis driver includes provisions to telegraph synchronization state + * and related variables by means of kernel variables with specially + * modified kernels. This is done using the ntp_adjtime() syscall. + * In the cases where another protocol or device synchronizes the local + * host, the data given to the kernel can be slurped up by this driver + * and distributed to clients by ordinary NTP messaging. + * + * In the default mode the behavior of the clock selection algorithm is + * modified when this driver is in use. The algorithm is designed so + * that this driver will never be selected unless no other discipline + * source is available. This can be overriden with the prefer keyword of + * the server configuration command, in which case only this driver will + * be selected for synchronization and all other discipline sources will + * be ignored. This behavior is intended for use when an external + * discipline source controls the system clock. + * + * Fudge Factors + * + * The stratum for this driver set at 5 by default, but it can be + * changed by the fudge command and/or the ntpdc utility. The reference + * ID is "LCL" by default, but can be changed using the same mechanism. + * *NEVER* configure this driver to operate at a stratum which might + * possibly disrupt a client with access to a bona fide primary server, + * unless the local clock oscillator is reliably disciplined by another + * source. *NEVER NEVER* configure a server which might devolve to an + * undisciplined local clock to use multicast mode. Always remember that + * an improperly configured local clock driver let loose in the Internet + * can cause very serious disruption. This is why most of us who care + * about good time use cryptographic authentication. + * + * This driver provides a mechanism to trim the local clock in both time + * and frequency, as well as a way to manipulate the leap bits. The + * fudge time1 parameter adjusts the time, in seconds, and the fudge + * time2 parameter adjusts the frequency, in ppm. The fudge time1 + * parameter is additive; that is, it adds an increment to the current + * time. The fudge time2 parameter directly sets the frequency. + */ +/* + * Local interface definitions + */ +#define PRECISION (-7) /* about 10 ms precision */ +#if defined(VMS) && defined(VMS_LOCALUNIT) +#define REFID "LCLv" /* reference ID */ +#else /* VMS VMS_LOCALUNIT */ +#define REFID "LCL\0" /* reference ID */ +#endif /* VMS VMS_LOCALUNIT */ +#define DESCRIPTION "Undisciplined local clock" /* WRU */ + +#define STRATUM 5 /* default stratum */ +#define DISPERSION .01 /* default dispersion (10 ms) */ + +/* + * Imported from the timer module + */ +extern u_long current_time; + +/* + * Imported from ntp_proto + */ +extern s_char sys_precision; + +#ifdef KERNEL_PLL +/* + * Imported from ntp_loopfilter + */ +extern int pll_control; /* kernel pll control */ +extern int kern_enable; /* kernel pll enabled */ +extern int ext_enable; /* external clock enable */ +#endif /* KERNEL_PLL */ + +/* + * Function prototypes + */ +static int local_start P((int, struct peer *)); +static void local_poll P((int, struct peer *)); + +/* + * Local variables + */ +static u_long poll_time; /* last time polled */ + +/* + * Transfer vector + */ +struct refclock refclock_local = { + local_start, /* start up driver */ + noentry, /* shut down driver (not used) */ + local_poll, /* transmit poll message */ + noentry, /* not used (old lcl_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old lcl_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * local_start - start up the clock + */ +static int +local_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + + /* + * Initialize miscellaneous variables + */ + peer->precision = sys_precision; + pp->leap = LEAP_NOTINSYNC; + peer->stratum = STRATUM; + pp->stratum = STRATUM; + pp->clockdesc = DESCRIPTION; + memcpy(&pp->refid, "INIT", 4); + poll_time = current_time; + return (1); +} + + +/* + * local_poll - called by the transmit procedure + * + * LOCKCLOCK: If the kernel supports the nanokernel or microkernel + * system calls, the leap bits are extracted from the kernel. If there + * is a kernel error or the kernel leap bits are set to 11, the NTP leap + * bits are set to 11 and the stratum is set to infinity. Otherwise, the + * NTP leap bits are set to the kernel leap bits and the stratum is set + * as fudged. This behavior does not faithfully follow the + * specification, but is probably more appropriate in a multiple-server + * national laboratory network. + */ +static void +local_poll( + int unit, + struct peer *peer + ) +{ +#if defined(KERNEL_PLL) && defined(LOCKCLOCK) + struct timex ntv; +#endif /* KERNEL_PLL LOCKCLOCK */ + struct refclockproc *pp; + +#if defined(VMS) && defined(VMS_LOCALUNIT) + if (unit == VMS_LOCALUNIT) { + extern void vms_local_poll(struct peer *); + + vms_local_poll(peer); + return; + } +#endif /* VMS && VMS_LOCALUNIT */ + pp = peer->procptr; + pp->polls++; + + /* + * Ramble through the usual filtering and grooming code, which + * is essentially a no-op and included mostly for pretty + * billboards. We allow a one-time time adjustment using fudge + * time1 (s) and a continuous frequency adjustment using fudge + * time 2 (ppm). + */ + get_systime(&pp->lastrec); + pp->fudgetime1 += pp->fudgetime2 * 1e-6 * (current_time - + poll_time); + poll_time = current_time; + refclock_process_offset(pp, pp->lastrec, pp->lastrec, + pp->fudgetime1); + + /* + * If another process is disciplining the system clock, we set + * the leap bits and quality indicators from the kernel. + */ +#if defined(KERNEL_PLL) && defined(LOCKCLOCK) + memset(&ntv, 0, sizeof ntv); + switch (ntp_adjtime(&ntv)) { + case TIME_OK: + pp->leap = LEAP_NOWARNING; + peer->stratum = pp->stratum; + break; + + case TIME_INS: + pp->leap = LEAP_ADDSECOND; + peer->stratum = pp->stratum; + break; + + case TIME_DEL: + pp->leap = LEAP_DELSECOND; + peer->stratum = pp->stratum; + break; + + default: + pp->leap = LEAP_NOTINSYNC; + peer->stratum = STRATUM_UNSPEC; + } + pp->disp = 0; + pp->jitter = 0; +#else /* KERNEL_PLL LOCKCLOCK */ + pp->leap = LEAP_NOWARNING; + pp->disp = DISPERSION; + pp->jitter = 0; +#endif /* KERNEL_PLL LOCKCLOCK */ + pp->lastref = pp->lastrec; + refclock_receive(peer); + pp->fudgetime1 = 0; +} +#else +int refclock_local_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_msfees.c b/ntpd/refclock_msfees.c new file mode 100644 index 0000000..ebfb983 --- /dev/null +++ b/ntpd/refclock_msfees.c @@ -0,0 +1,1455 @@ +/* refclock_ees - clock driver for the EES M201 receiver */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_MSFEES) && defined(PPS) + +/* Currently REQUIRES STREAM and PPSCD. CLK and CBREAK modes + * were removed as the code was overly hairy, they weren't in use + * (hence probably didn't work). Still in RCS file at cl.cam.ac.uk + */ + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_calendar.h" + +#include <ctype.h> +#if defined(HAVE_BSD_TTYS) +#include <sgtty.h> +#endif /* HAVE_BSD_TTYS */ +#if defined(HAVE_SYSV_TTYS) +#include <termio.h> +#endif /* HAVE_SYSV_TTYS */ +#if defined(HAVE_TERMIOS) +#include <termios.h> +#endif +#if defined(STREAM) +#include <stropts.h> +#endif + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +#include "ntp_stdlib.h" + +/* + fudgefactor = fudgetime1; + os_delay = fudgetime2; + offset_fudge = os_delay + fudgefactor + inherent_delay; + stratumtouse = fudgeval1 & 0xf + debug = fudgeval2; + sloppyclockflag = flags & CLK_FLAG1; + 1 log smoothing summary when processing sample + 4 dump the buffer from the clock + 8 EIOGETKD the last n uS time stamps + if (flags & CLK_FLAG2 && unitinuse) ees->leaphold = 0; + ees->dump_vals = flags & CLK_FLAG3; + ees->usealldata = flags & CLK_FLAG4; + + + bug->values[0] = (ees->lasttime) ? current_time - ees->lasttime : 0; + bug->values[1] = (ees->clocklastgood)?current_time-ees->clocklastgood:0; + bug->values[2] = (u_long)ees->status; + bug->values[3] = (u_long)ees->lastevent; + bug->values[4] = (u_long)ees->reason; + bug->values[5] = (u_long)ees->nsamples; + bug->values[6] = (u_long)ees->codestate; + bug->values[7] = (u_long)ees->day; + bug->values[8] = (u_long)ees->hour; + bug->values[9] = (u_long)ees->minute; + bug->values[10] = (u_long)ees->second; + bug->values[11] = (u_long)ees->tz; + bug->values[12] = ees->yearstart; + bug->values[13] = (ees->leaphold > current_time) ? + ees->leaphold - current_time : 0; + bug->values[14] = inherent_delay[unit].l_uf; + bug->values[15] = offset_fudge[unit].l_uf; + + bug->times[0] = ees->reftime; + bug->times[1] = ees->arrvtime; + bug->times[2] = ees->lastsampletime; + bug->times[3] = ees->offset; + bug->times[4] = ees->lowoffset; + bug->times[5] = ees->highoffset; + bug->times[6] = inherent_delay[unit]; + bug->times[8] = os_delay[unit]; + bug->times[7] = fudgefactor[unit]; + bug->times[9] = offset_fudge[unit]; + bug->times[10]= ees->yearstart, 0; + */ + +/* This should support the use of an EES M201 receiver with RS232 + * output (modified to transmit time once per second). + * + * For the format of the message sent by the clock, see the EESM_ + * definitions below. + * + * It appears to run free for an integral number of minutes, until the error + * reaches 4mS, at which point it steps at second = 01. + * It appears that sometimes it steps 4mS (say at 7 min interval), + * then the next minute it decides that it was an error, so steps back. + * On the next minute it steps forward again :-( + * This is typically 16.5uS/S then 3975uS at the 4min re-sync, + * or 9.5uS/S then 3990.5uS at a 7min re-sync, + * at which point it may lose the "00" second time stamp. + * I assume that the most accurate time is just AFTER the re-sync. + * Hence remember the last cycle interval, + * + * Can run in any one of: + * + * PPSCD PPS signal sets CD which interupts, and grabs the current TOD + * (sun) *in the interupt code*, so as to avoid problems with + * the STREAMS scheduling. + * + * It appears that it goes 16.5 uS slow each second, then every 4 mins it + * generates no "00" second tick, and gains 3975 uS. Ho Hum ! (93/2/7) + */ + +/* Definitions */ +#ifndef MAXUNITS +#define MAXUNITS 4 /* maximum number of EES units permitted */ +#endif + +#ifndef EES232 +#define EES232 "/dev/ees%d" /* Device to open to read the data */ +#endif + +/* Other constant stuff */ +#ifndef EESPRECISION +#define EESPRECISION (-10) /* what the heck - 2**-10 = 1ms */ +#endif +#ifndef EESREFID +#define EESREFID "MSF\0" /* String to identify the clock */ +#endif +#ifndef EESHSREFID +#define EESHSREFID (0x7f7f0000 | ((REFCLK_MSF_EES) << 8)) /* Numeric refid */ +#endif + +/* Description of clock */ +#define EESDESCRIPTION "EES M201 MSF Receiver" + +/* Speed we run the clock port at. If this is changed the UARTDELAY + * value should be recomputed to suit. + */ +#ifndef SPEED232 +#define SPEED232 B9600 /* 9600 baud */ +#endif + +/* What is the inherent delay for this mode of working, i.e. when is the + * data time stamped. + */ +#define SAFETY_SHIFT 10 /* Split the shift to avoid overflow */ +#define BITS_TO_L_FP(bits, baud) \ +(((((bits)*2 +1) << (FRACTION_PREC-SAFETY_SHIFT)) / (2*baud)) << SAFETY_SHIFT) +#define INH_DELAY_CBREAK BITS_TO_L_FP(119, 9600) +#define INH_DELAY_PPS BITS_TO_L_FP( 0, 9600) + +#ifndef STREAM_PP1 +#define STREAM_PP1 "ppsclocd\0<-- patch space for module name1 -->" +#endif +#ifndef STREAM_PP2 +#define STREAM_PP2 "ppsclock\0<-- patch space for module name2 -->" +#endif + + /* Offsets of the bytes of the serial line code. The clock gives + * local time with a GMT/BST indication. The EESM_ definitions + * give offsets into ees->lastcode. + */ +#define EESM_CSEC 0 /* centiseconds - always zero in our clock */ +#define EESM_SEC 1 /* seconds in BCD */ +#define EESM_MIN 2 /* minutes in BCD */ +#define EESM_HOUR 3 /* hours in BCD */ +#define EESM_DAYWK 4 /* day of week (Sun = 0 etc) */ +#define EESM_DAY 5 /* day of month in BCD */ +#define EESM_MON 6 /* month in BCD */ +#define EESM_YEAR 7 /* year MOD 100 in BCD */ +#define EESM_LEAP 8 /* 0x0f if leap year, otherwise zero */ +#define EESM_BST 9 /* 0x03 if BST, 0x00 if GMT */ +#define EESM_MSFOK 10 /* 0x3f if radio good, otherwise zero */ + /* followed by a frame alignment byte (0xff) / + / which is not put into the lastcode buffer*/ + +/* Length of the serial time code, in characters. The first length + * is less the frame alignment byte. + */ +#define LENEESPRT (EESM_MSFOK+1) +#define LENEESCODE (LENEESPRT+1) + + /* Code state. */ +#define EESCS_WAIT 0 /* waiting for start of timecode */ +#define EESCS_GOTSOME 1 /* have an incomplete time code buffered */ + + /* Default fudge factor and character to receive */ +#define DEFFUDGETIME 0 /* Default user supplied fudge factor */ +#ifndef DEFOSTIME +#define DEFOSTIME 0 /* Default OS delay -- passed by Make ? */ +#endif +#define DEFINHTIME INH_DELAY_PPS /* inherent delay due to sample point*/ + + /* Limits on things. Reduce the number of samples to SAMPLEREDUCE by median + * elimination. If we're running with an accurate clock, chose the BESTSAMPLE + * as the estimated offset, otherwise average the remainder. + */ +#define FULLSHIFT 6 /* NCODES root 2 */ +#define NCODES (1<< FULLSHIFT) /* 64 */ +#define REDUCESHIFT (FULLSHIFT -1) /* SAMPLEREDUCE root 2 */ + + /* Towards the high ( Why ?) end of half */ +#define BESTSAMPLE ((samplereduce * 3) /4) /* 24 */ + + /* Leap hold time. After a leap second the clock will no longer be + * reliable until it resynchronizes. Hope 40 minutes is enough. */ +#define EESLEAPHOLD (40 * 60) + +#define EES_STEP_F (1 << 24) /* the receiver steps in units of about 4ms */ +#define EES_STEP_F_GRACE (EES_STEP_F/8) /*Allow for slop of 1/8 which is .5ms*/ +#define EES_STEP_NOTE (1 << 21)/* Log any unexpected jumps, say .5 ms .... */ +#define EES_STEP_NOTES 50 /* Only do a limited number */ +#define MAX_STEP 16 /* Max number of steps to remember */ + + /* debug is a bit mask of debugging that is wanted */ +#define DB_SYSLOG_SMPLI 0x0001 +#define DB_SYSLOG_SMPLE 0x0002 +#define DB_SYSLOG_SMTHI 0x0004 +#define DB_SYSLOG_NSMTHE 0x0008 +#define DB_SYSLOG_NSMTHI 0x0010 +#define DB_SYSLOG_SMTHE 0x0020 +#define DB_PRINT_EV 0x0040 +#define DB_PRINT_CDT 0x0080 +#define DB_PRINT_CDTC 0x0100 +#define DB_SYSLOG_KEEPD 0x0800 +#define DB_SYSLOG_KEEPE 0x1000 +#define DB_LOG_DELTAS 0x2000 +#define DB_PRINT_DELTAS 0x4000 +#define DB_LOG_AWAITMORE 0x8000 +#define DB_LOG_SAMPLES 0x10000 +#define DB_NO_PPS 0x20000 +#define DB_INC_PPS 0x40000 +#define DB_DUMP_DELTAS 0x80000 + + struct eesunit { /* EES unit control structure. */ + struct peer *peer; /* associated peer structure */ + struct refclockio io; /* given to the I/O handler */ + l_fp reftime; /* reference time */ + l_fp lastsampletime; /* time as in txt from last EES msg */ + l_fp arrvtime; /* Time at which pkt arrived */ + l_fp codeoffsets[NCODES]; /* the time of arrival of 232 codes */ + l_fp offset; /* chosen offset (for clkbug) */ + l_fp lowoffset; /* lowest sample offset (for clkbug) */ + l_fp highoffset; /* highest " " (for clkbug) */ + char lastcode[LENEESCODE+6]; /* last time code we received */ + u_long lasttime; /* last time clock heard from */ + u_long clocklastgood; /* last time good radio seen */ + u_char lencode; /* length of code in buffer */ + u_char nsamples; /* number of samples we've collected */ + u_char codestate; /* state of 232 code reception */ + u_char unit; /* unit number for this guy */ + u_char status; /* clock status */ + u_char lastevent; /* last clock event */ + u_char reason; /* reason for last abort */ + u_char hour; /* hour of day */ + u_char minute; /* minute of hour */ + u_char second; /* seconds of minute */ + char tz; /* timezone from clock */ + u_char ttytype; /* method used */ + u_char dump_vals; /* Should clock values be dumped */ + u_char usealldata; /* Use ALL samples */ + u_short day; /* day of year from last code */ + u_long yearstart; /* start of current year */ + u_long leaphold; /* time of leap hold expiry */ + u_long badformat; /* number of bad format codes */ + u_long baddata; /* number of invalid time codes */ + u_long timestarted; /* time we started this */ + long last_pps_no; /* The serial # of the last PPS */ + char fix_pending; /* Is a "sync to time" pending ? */ + /* Fine tuning - compensate for 4 mS ramping .... */ + l_fp last_l; /* last time stamp */ + u_char last_steps[MAX_STEP]; /* Most recent n steps */ + int best_av_step; /* Best guess at average step */ + char best_av_step_count; /* # of steps over used above */ + char this_step; /* Current pos in buffer */ + int last_step_late; /* How late the last step was (0-59) */ + long jump_fsecs; /* # of fractions of a sec last jump */ + u_long last_step; /* time of last step */ + int last_step_secs; /* Number of seconds in last step */ + int using_ramp; /* 1 -> noemal, -1 -> over stepped */ + }; +#define last_sec last_l.l_ui +#define last_sfsec last_l.l_f +#define this_uisec ((ees->arrvtime).l_ui) +#define this_sfsec ((ees->arrvtime).l_f) +#define msec(x) ((x) / (1<<22)) +#define LAST_STEPS (sizeof ees->last_steps / sizeof ees->last_steps[0]) +#define subms(x) ((((((x < 0) ? (-(x)) : (x)) % (1<<22))/2) * 625) / (1<<(22 -5))) + +/* Bitmask for what methods to try to use -- currently only PPS enabled */ +#define T_CBREAK 1 +#define T_PPS 8 +/* macros to test above */ +#define is_cbreak(x) ((x)->ttytype & T_CBREAK) +#define is_pps(x) ((x)->ttytype & T_PPS) +#define is_any(x) ((x)->ttytype) + +#define CODEREASON 20 /* reason codes */ + +/* Data space for the unit structures. Note that we allocate these on + * the fly, but never give them back. */ +static struct eesunit *eesunits[MAXUNITS]; +static u_char unitinuse[MAXUNITS]; + +/* Keep the fudge factors separately so they can be set even + * when no clock is configured. */ +static l_fp inherent_delay[MAXUNITS]; /* when time stamp is taken */ +static l_fp fudgefactor[MAXUNITS]; /* fudgetime1 */ +static l_fp os_delay[MAXUNITS]; /* fudgetime2 */ +static l_fp offset_fudge[MAXUNITS]; /* Sum of above */ +static u_char stratumtouse[MAXUNITS]; +static u_char sloppyclockflag[MAXUNITS]; + +static int deltas[60]; + +static l_fp acceptable_slop; /* = { 0, 1 << (FRACTION_PREC -2) }; */ +static l_fp onesec; /* = { 1, 0 }; */ + +#ifndef DUMP_BUF_SIZE /* Size of buffer to be used by dump_buf */ +#define DUMP_BUF_SIZE 10112 +#endif + +/* ees_reset - reset the count back to zero */ +#define ees_reset(ees) (ees)->nsamples = 0; \ +(ees)->codestate = EESCS_WAIT + +/* ees_event - record and report an event */ +#define ees_event(ees, evcode) if ((ees)->status != (u_char)(evcode)) \ +ees_report_event((ees), (evcode)) + + /* Find the precision of the system clock by reading it */ +#define USECS 1000000 +#define MINSTEP 5 /* some systems increment uS on each call */ +#define MAXLOOPS (USECS/9) + +/* + * Function prototypes + */ + +static int msfees_start P((int unit, struct peer *peer)); +static void msfees_shutdown P((int unit, struct peer *peer)); +static void msfees_poll P((int unit, struct peer *peer)); +static void msfees_init P((void)); +static void dump_buf P((l_fp *coffs, int from, int to, char *text)); +static void ees_report_event P((struct eesunit *ees, int code)); +static void ees_receive P((struct recvbuf *rbufp)); +static void ees_process P((struct eesunit *ees)); +#ifdef QSORT_USES_VOID_P +static int offcompare P((const void *va, const void *vb)); +#else +static int offcompare P((const l_fp *a, const l_fp *b)); +#endif /* QSORT_USES_VOID_P */ + + +/* + * Transfer vector + */ +struct refclock refclock_msfees = { + msfees_start, /* start up driver */ + msfees_shutdown, /* shut down driver */ + msfees_poll, /* transmit poll message */ + noentry, /* not used */ + msfees_init, /* initialize driver */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + + +static void +dump_buf( + l_fp *coffs, + int from, + int to, + char *text + ) +{ + char buff[DUMP_BUF_SIZE + 80]; + int i; + register char *ptr = buff; + + sprintf(ptr, text); + for (i=from; i<to; i++) + { while (*ptr) ptr++; + if ((ptr-buff) > DUMP_BUF_SIZE) msyslog(LOG_DEBUG, "D: %s", ptr=buff); + sprintf(ptr, " %06d", ((int)coffs[i].l_f) / 4295); + } + msyslog(LOG_DEBUG, "D: %s", buff); +} + +/* msfees_init - initialize internal ees driver data */ +static void +msfees_init(void) +{ + register int i; + /* Just zero the data arrays */ + memset((char *)eesunits, 0, sizeof eesunits); + memset((char *)unitinuse, 0, sizeof unitinuse); + + acceptable_slop.l_ui = 0; + acceptable_slop.l_uf = 1 << (FRACTION_PREC -2); + + onesec.l_ui = 1; + onesec.l_uf = 0; + + /* Initialize fudge factors to default. */ + for (i = 0; i < MAXUNITS; i++) { + fudgefactor[i].l_ui = 0; + fudgefactor[i].l_uf = DEFFUDGETIME; + os_delay[i].l_ui = 0; + os_delay[i].l_uf = DEFOSTIME; + inherent_delay[i].l_ui = 0; + inherent_delay[i].l_uf = DEFINHTIME; + offset_fudge[i] = os_delay[i]; + L_ADD(&offset_fudge[i], &fudgefactor[i]); + L_ADD(&offset_fudge[i], &inherent_delay[i]); + stratumtouse[i] = 0; + sloppyclockflag[i] = 0; + } +} + + +/* msfees_start - open the EES devices and initialize data for processing */ +static int +msfees_start( + int unit, + struct peer *peer + ) +{ + register struct eesunit *ees; + register int i; + int fd232 = -1; + char eesdev[20]; + struct termios ttyb, *ttyp; + struct refclockproc *pp; + pp = peer->procptr; + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "ees clock: unit number %d invalid (max %d)", + unit, MAXUNITS-1); + return 0; + } + if (unitinuse[unit]) { + msyslog(LOG_ERR, "ees clock: unit number %d in use", unit); + return 0; + } + + /* Unit okay, attempt to open the devices. We do them both at + * once to make sure we can */ + (void) sprintf(eesdev, EES232, unit); + + fd232 = open(eesdev, O_RDWR, 0777); + if (fd232 == -1) { + msyslog(LOG_ERR, "ees clock: open of %s failed: %m", eesdev); + return 0; + } + +#ifdef TIOCEXCL + /* Set for exclusive use */ + if (ioctl(fd232, TIOCEXCL, (char *)0) < 0) { + msyslog(LOG_ERR, "ees clock: ioctl(%s, TIOCEXCL): %m", eesdev); + goto screwed; + } +#endif + + /* STRIPPED DOWN VERSION: Only PPS CD is supported at the moment */ + + /* Set port characteristics. If we don't have a STREAMS module or + * a clock line discipline, cooked mode is just usable, even though it + * strips the top bit. The only EES byte which uses the top + * bit is the year, and we don't use that anyway. If we do + * have the line discipline, we choose raw mode, and the + * line discipline code will block up the messages. + */ + + /* STIPPED DOWN VERSION: Only PPS CD is supported at the moment */ + + ttyp = &ttyb; + if (tcgetattr(fd232, ttyp) < 0) { + msyslog(LOG_ERR, "msfees_start: tcgetattr(%s): %m", eesdev); + goto screwed; + } + + ttyp->c_iflag = IGNBRK|IGNPAR|ICRNL; + ttyp->c_cflag = SPEED232|CS8|CLOCAL|CREAD; + ttyp->c_oflag = 0; + ttyp->c_lflag = ICANON; + ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0'; + if (tcsetattr(fd232, TCSANOW, ttyp) < 0) { + msyslog(LOG_ERR, "msfees_start: tcsetattr(%s): %m", eesdev); + goto screwed; + } + + if (tcflush(fd232, TCIOFLUSH) < 0) { + msyslog(LOG_ERR, "msfees_start: tcflush(%s): %m", eesdev); + goto screwed; + } + + inherent_delay[unit].l_uf = INH_DELAY_PPS; + + /* offset fudge (how *late* the timestamp is) = fudge + os delays */ + offset_fudge[unit] = os_delay[unit]; + L_ADD(&offset_fudge[unit], &fudgefactor[unit]); + L_ADD(&offset_fudge[unit], &inherent_delay[unit]); + + /* Looks like this might succeed. Find memory for the structure. + * Look to see if there are any unused ones, if not we malloc() one. + */ + if (eesunits[unit] != 0) /* The one we want is okay */ + ees = eesunits[unit]; + else { + /* Look for an unused, but allocated struct */ + for (i = 0; i < MAXUNITS; i++) { + if (!unitinuse[i] && eesunits[i] != 0) + break; + } + + if (i < MAXUNITS) { /* Reclaim this one */ + ees = eesunits[i]; + eesunits[i] = 0; + } /* no spare -- make a new one */ + else ees = (struct eesunit *) emalloc(sizeof(struct eesunit)); + } + memset((char *)ees, 0, sizeof(struct eesunit)); + eesunits[unit] = ees; + + /* Set up the structures */ + ees->peer = peer; + ees->unit = (u_char)unit; + ees->timestarted= current_time; + ees->ttytype = 0; + ees->io.clock_recv= ees_receive; + ees->io.srcclock= (caddr_t)ees; + ees->io.datalen = 0; + ees->io.fd = fd232; + + /* Okay. Push one of the two (linked into the kernel, or dynamically + * loaded) STREAMS module, and give it to the I/O code to start + * receiving stuff. + */ + +#ifdef STREAM + { + int rc1; + /* Pop any existing onews first ... */ + while (ioctl(fd232, I_POP, 0 ) >= 0) ; + + /* Now try pushing either of the possible modules */ + if ((rc1=ioctl(fd232, I_PUSH, STREAM_PP1)) < 0 && + ioctl(fd232, I_PUSH, STREAM_PP2) < 0) { + msyslog(LOG_ERR, + "ees clock: Push of `%s' and `%s' to %s failed %m", + STREAM_PP1, STREAM_PP2, eesdev); + goto screwed; + } + else { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "I: ees clock: PUSHed %s on %s", + (rc1 >= 0) ? STREAM_PP1 : STREAM_PP2, eesdev); + ees->ttytype |= T_PPS; + } + } +#endif /* STREAM */ + + /* Add the clock */ + if (!io_addclock(&ees->io)) { + /* Oh shit. Just close and return. */ + msyslog(LOG_ERR, "ees clock: io_addclock(%s): %m", eesdev); + goto screwed; + } + + + /* All done. Initialize a few random peer variables, then + * return success. */ + peer->precision = sys_precision; + peer->stratum = stratumtouse[unit]; + if (stratumtouse[unit] <= 1) { + memcpy((char *)&pp->refid, EESREFID, 4); + if (unit > 0 && unit < 10) + ((char *)&pp->refid)[3] = '0' + unit; + } else { + peer->refid = htonl(EESHSREFID); + } + unitinuse[unit] = 1; + pp->unitptr = (caddr_t) &eesunits[unit]; + pp->clockdesc = EESDESCRIPTION; + msyslog(LOG_ERR, "ees clock: %s OK on %d", eesdev, unit); + return (1); + + screwed: + if (fd232 != -1) + (void) close(fd232); + return (0); +} + + +/* msfees_shutdown - shut down a EES clock */ +static void +msfees_shutdown( + int unit, + struct peer *peer + ) +{ + register struct eesunit *ees; + + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, + "ees clock: INTERNAL ERROR, unit number %d invalid (max %d)", + unit, MAXUNITS); + return; + } + if (!unitinuse[unit]) { + msyslog(LOG_ERR, + "ees clock: INTERNAL ERROR, unit number %d not in use", unit); + return; + } + + /* Tell the I/O module to turn us off. We're history. */ + ees = eesunits[unit]; + io_closeclock(&ees->io); + unitinuse[unit] = 0; +} + + +/* ees_report_event - note the occurance of an event */ +static void +ees_report_event( + struct eesunit *ees, + int code + ) +{ + if (ees->status != (u_char)code) { + ees->status = (u_char)code; + if (code != CEVNT_NOMINAL) + ees->lastevent = (u_char)code; + /* Should report event to trap handler in here. + * Soon... + */ + } +} + + +/* ees_receive - receive data from the serial interface on an EES clock */ +static void +ees_receive( + struct recvbuf *rbufp + ) +{ + register int n_sample; + register int day; + register struct eesunit *ees; + register u_char *dpt; /* Data PoinTeR: move along ... */ + register u_char *dpend; /* Points just *after* last data char */ + register char *cp; + l_fp tmp; + int call_pps_sample = 0; + l_fp pps_arrvstamp; + int sincelast; + int pps_step = 0; + int suspect_4ms_step = 0; + struct ppsclockev ppsclockev; + long *ptr = (long *) &ppsclockev; + int rc; + int request; +#ifdef HAVE_CIOGETEV + request = CIOGETEV; +#endif +#ifdef HAVE_TIOCGPPSEV + request = TIOCGPPSEV; +#endif + + /* Get the clock this applies to and a pointer to the data */ + ees = (struct eesunit *)rbufp->recv_srcclock; + dpt = (u_char *)&rbufp->recv_space; + dpend = dpt + rbufp->recv_length; + if ((debug & DB_LOG_AWAITMORE) && (rbufp->recv_length != LENEESCODE)) + printf("[%d] ", rbufp->recv_length); + + /* Check out our state and process appropriately */ + switch (ees->codestate) { + case EESCS_WAIT: + /* Set an initial guess at the timestamp as the recv time. + * If just running in CBREAK mode, we can't improve this. + * If we have the CLOCK Line Discipline, PPSCD, or sime such, + * then we will do better later .... + */ + ees->arrvtime = rbufp->recv_time; + ees->codestate = EESCS_GOTSOME; + ees->lencode = 0; + /*FALLSTHROUGH*/ + + case EESCS_GOTSOME: + cp = &(ees->lastcode[ees->lencode]); + + /* Gobble the bytes until the final (possibly stripped) 0xff */ + while (dpt < dpend && (*dpt & 0x7f) != 0x7f) { + *cp++ = (char)*dpt++; + ees->lencode++; + /* Oh dear -- too many bytes .. */ + if (ees->lencode > LENEESPRT) { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, + "I: ees clock: %d + %d > %d [%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x]", + ees->lencode, dpend - dpt, LENEESPRT, +#define D(x) (ees->lastcode[x]) + D(0), D(1), D(2), D(3), D(4), D(5), D(6), + D(7), D(8), D(9), D(10), D(11), D(12)); +#undef D + ees->badformat++; + ees->reason = CODEREASON + 1; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + } + /* Gave up because it was end of the buffer, rather than ff */ + if (dpt == dpend) { + /* Incomplete. Wait for more. */ + if (debug & DB_LOG_AWAITMORE) + msyslog(LOG_INFO, + "I: ees clock %d: %p == %p: await more", + ees->unit, dpt, dpend); + return; + } + + /* This shouldn't happen ... ! */ + if ((*dpt & 0x7f) != 0x7f) { + msyslog(LOG_INFO, "I: ees clock: %0x & 0x7f != 0x7f", *dpt); + ees->badformat++; + ees->reason = CODEREASON + 2; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + /* Skip the 0xff */ + dpt++; + + /* Finally, got a complete buffer. Mainline code will + * continue on. */ + cp = ees->lastcode; + break; + + default: + msyslog(LOG_ERR, "ees clock: INTERNAL ERROR: %d state %d", + ees->unit, ees->codestate); + ees->reason = CODEREASON + 5; + ees_event(ees, CEVNT_FAULT); + ees_reset(ees); + return; + } + + /* Boy! After all that crap, the lastcode buffer now contains + * something we hope will be a valid time code. Do length + * checks and sanity checks on constant data. + */ + ees->codestate = EESCS_WAIT; + ees->lasttime = current_time; + if (ees->lencode != LENEESPRT) { + ees->badformat++; + ees->reason = CODEREASON + 6; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + cp = ees->lastcode; + + /* Check that centisecond is zero */ + if (cp[EESM_CSEC] != 0) { + ees->baddata++; + ees->reason = CODEREASON + 7; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + /* Check flag formats */ + if (cp[EESM_LEAP] != 0 && cp[EESM_LEAP] != 0x0f) { + ees->badformat++; + ees->reason = CODEREASON + 8; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + if (cp[EESM_BST] != 0 && cp[EESM_BST] != 0x03) { + ees->badformat++; + ees->reason = CODEREASON + 9; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + if (cp[EESM_MSFOK] != 0 && cp[EESM_MSFOK] != 0x3f) { + ees->badformat++; + ees->reason = CODEREASON + 10; + ees_event(ees, CEVNT_BADREPLY); + ees_reset(ees); + return; + } + + /* So far, so good. Compute day, hours, minutes, seconds, + * time zone. Do range checks on these. + */ + +#define bcdunpack(val) ( (((val)>>4) & 0x0f) * 10 + ((val) & 0x0f) ) +#define istrue(x) ((x)?1:0) + + ees->second = bcdunpack(cp[EESM_SEC]); /* second */ + ees->minute = bcdunpack(cp[EESM_MIN]); /* minute */ + ees->hour = bcdunpack(cp[EESM_HOUR]); /* hour */ + + day = bcdunpack(cp[EESM_DAY]); /* day of month */ + + switch (bcdunpack(cp[EESM_MON])) { /* month */ + + /* Add in lengths of all previous months. Add one more + if it is a leap year and after February. + */ + case 12: day += NOV; /*FALLSTHROUGH*/ + case 11: day += OCT; /*FALLSTHROUGH*/ + case 10: day += SEP; /*FALLSTHROUGH*/ + case 9: day += AUG; /*FALLSTHROUGH*/ + case 8: day += JUL; /*FALLSTHROUGH*/ + case 7: day += JUN; /*FALLSTHROUGH*/ + case 6: day += MAY; /*FALLSTHROUGH*/ + case 5: day += APR; /*FALLSTHROUGH*/ + case 4: day += MAR; /*FALLSTHROUGH*/ + case 3: day += FEB; + if (istrue(cp[EESM_LEAP])) day++; /*FALLSTHROUGH*/ + case 2: day += JAN; /*FALLSTHROUGH*/ + case 1: break; + default: ees->baddata++; + ees->reason = CODEREASON + 11; + ees_event(ees, CEVNT_BADDATE); + ees_reset(ees); + return; + } + + ees->day = day; + + /* Get timezone. The clocktime routine wants the number + * of hours to add to the delivered time to get UT. + * Currently -1 if BST flag set, 0 otherwise. This + * is the place to tweak things if double summer time + * ever happens. + */ + ees->tz = istrue(cp[EESM_BST]) ? -1 : 0; + + if (ees->day > 366 || ees->day < 1 || + ees->hour > 23 || ees->minute > 59 || ees->second > 59) { + ees->baddata++; + ees->reason = CODEREASON + 12; + ees_event(ees, CEVNT_BADDATE); + ees_reset(ees); + return; + } + + n_sample = ees->nsamples; + + /* Now, compute the reference time value: text -> tmp.l_ui */ + if (!clocktime(ees->day, ees->hour, ees->minute, ees->second, + ees->tz, rbufp->recv_time.l_ui, &ees->yearstart, + &tmp.l_ui)) { + ees->baddata++; + ees->reason = CODEREASON + 13; + ees_event(ees, CEVNT_BADDATE); + ees_reset(ees); + return; + } + tmp.l_uf = 0; + + /* DON'T use ees->arrvtime -- it may be < reftime */ + ees->lastsampletime = tmp; + + /* If we are synchronised to the radio, update the reference time. + * Also keep a note of when clock was last good. + */ + if (istrue(cp[EESM_MSFOK])) { + ees->reftime = tmp; + ees->clocklastgood = current_time; + } + + + /* Compute the offset. For the fractional part of the + * offset we use the expected delay for the message. + */ + ees->codeoffsets[n_sample].l_ui = tmp.l_ui; + ees->codeoffsets[n_sample].l_uf = 0; + + /* Number of seconds since the last step */ + sincelast = this_uisec - ees->last_step; + + memset((char *) &ppsclockev, 0, sizeof ppsclockev); + + rc = ioctl(ees->io.fd, request, (char *) &ppsclockev); + if (debug & DB_PRINT_EV) fprintf(stderr, + "[%x] CIOGETEV u%d %d (%x %d) gave %d (%d): %08lx %08lx %ld\n", + DB_PRINT_EV, ees->unit, ees->io.fd, request, is_pps(ees), + rc, errno, ptr[0], ptr[1], ptr[2]); + + /* If we managed to get the time of arrival, process the info */ + if (rc >= 0) { + int conv = -1; + pps_step = ppsclockev.serial - ees->last_pps_no; + + /* Possible that PPS triggered, but text message didn't */ + if (pps_step == 2) msyslog(LOG_ERR, "pps step = 2 @ %02d", ees->second); + if (pps_step == 2 && ees->second == 1) suspect_4ms_step |= 1; + if (pps_step == 2 && ees->second == 2) suspect_4ms_step |= 4; + + /* allow for single loss of PPS only */ + if (pps_step != 1 && pps_step != 2) + fprintf(stderr, "PPS step: %d too far off %ld (%d)\n", + ppsclockev.serial, ees->last_pps_no, pps_step); + else if (!buftvtots((char *) &(ppsclockev.tv), &pps_arrvstamp)) + fprintf(stderr, "buftvtots failed\n"); + else { /* if ((ABS(time difference) - 0.25) < 0) + * then believe it ... + */ + l_fp diff; + diff = pps_arrvstamp; + conv = 0; + L_SUB(&diff, &ees->arrvtime); + if (debug & DB_PRINT_CDT) + printf("[%x] Have %lx.%08lx and %lx.%08lx -> %lx.%08lx @ %s", + DB_PRINT_CDT, (long)ees->arrvtime.l_ui, (long)ees->arrvtime.l_uf, + (long)pps_arrvstamp.l_ui, (long)pps_arrvstamp.l_uf, + (long)diff.l_ui, (long)diff.l_uf, + ctime(&(ppsclockev.tv.tv_sec))); + if (L_ISNEG(&diff)) M_NEG(diff.l_ui, diff.l_uf); + L_SUB(&diff, &acceptable_slop); + if (L_ISNEG(&diff)) { /* AOK -- pps_sample */ + ees->arrvtime = pps_arrvstamp; + conv++; + call_pps_sample++; + } + /* Some loss of some signals around sec = 1 */ + else if (ees->second == 1) { + diff = pps_arrvstamp; + L_ADD(&diff, &onesec); + L_SUB(&diff, &ees->arrvtime); + if (L_ISNEG(&diff)) M_NEG(diff.l_ui, diff.l_uf); + L_SUB(&diff, &acceptable_slop); + msyslog(LOG_ERR, "Have sec==1 slip %ds a=%08x-p=%08x -> %x.%08x (u=%d) %s", + pps_arrvstamp.l_ui - ees->arrvtime.l_ui, + pps_arrvstamp.l_uf, + ees->arrvtime.l_uf, + diff.l_ui, diff.l_uf, + (int)ppsclockev.tv.tv_usec, + ctime(&(ppsclockev.tv.tv_sec))); + if (L_ISNEG(&diff)) { /* AOK -- pps_sample */ + suspect_4ms_step |= 2; + ees->arrvtime = pps_arrvstamp; + L_ADD(&ees->arrvtime, &onesec); + conv++; + call_pps_sample++; + } + } + } + ees->last_pps_no = ppsclockev.serial; + if (debug & DB_PRINT_CDTC) + printf( + "[%x] %08lx %08lx %d u%d (%d %d)\n", + DB_PRINT_CDTC, (long)pps_arrvstamp.l_ui, + (long)pps_arrvstamp.l_uf, conv, ees->unit, + call_pps_sample, pps_step); + } + + /* See if there has been a 4ms jump at a minute boundry */ + { l_fp delta; +#define delta_isec delta.l_ui +#define delta_ssec delta.l_i +#define delta_sfsec delta.l_f + long delta_f_abs; + + delta.l_i = ees->arrvtime.l_i; + delta.l_f = ees->arrvtime.l_f; + + L_SUB(&delta, &ees->last_l); + delta_f_abs = delta_sfsec; + if (delta_f_abs < 0) delta_f_abs = -delta_f_abs; + + /* Dump the deltas each minute */ + if (debug & DB_DUMP_DELTAS) + { if (/*0 <= ees->second && */ + ees->second < ((sizeof deltas) / (sizeof deltas[0]))) deltas[ees->second] = delta_sfsec; + /* Dump on second 1, as second 0 sometimes missed */ + if (ees->second == 1) { + char text[16 * ((sizeof deltas) / (sizeof deltas[0]))]; + char *cptr=text; + int i; + for (i=0; i<((sizeof deltas) / (sizeof deltas[0])); i++) { + sprintf(cptr, " %d.%04d", + msec(deltas[i]), subms(deltas[i])); + while (*cptr) cptr++; + } + msyslog(LOG_ERR, "Deltas: %d.%04d<->%d.%04d: %s", + msec(EES_STEP_F - EES_STEP_F_GRACE), subms(EES_STEP_F - EES_STEP_F_GRACE), + msec(EES_STEP_F + EES_STEP_F_GRACE), subms(EES_STEP_F + EES_STEP_F_GRACE), + text+1); + for (i=0; i<((sizeof deltas) / (sizeof deltas[0])); i++) deltas[i] = 0; + } + } + + /* Lets see if we have a 4 mS step at a minute boundaary */ + if ( ((EES_STEP_F - EES_STEP_F_GRACE) < delta_f_abs) && + (delta_f_abs < (EES_STEP_F + EES_STEP_F_GRACE)) && + (ees->second == 0 || ees->second == 1 || ees->second == 2) && + (sincelast < 0 || sincelast > 122) + ) { /* 4ms jump at min boundry */ + int old_sincelast; + int count=0; + int sum = 0; + /* Yes -- so compute the ramp time */ + if (ees->last_step == 0) sincelast = 0; + old_sincelast = sincelast; + + /* First time in, just set "ees->last_step" */ + if(ees->last_step) { + int other_step = 0; + int third_step = 0; + int this_step = (sincelast + (60 /2)) / 60; + int p_step = ees->this_step; + int p; + ees->last_steps[p_step] = this_step; + p= p_step; + p_step++; + if (p_step >= LAST_STEPS) p_step = 0; + ees->this_step = p_step; + /* Find the "average" interval */ + while (p != p_step) { + int this = ees->last_steps[p]; + if (this == 0) break; + if (this != this_step) { + if (other_step == 0 && ( + this== (this_step +2) || + this== (this_step -2) || + this== (this_step +1) || + this== (this_step -1))) + other_step = this; + if (other_step != this) { + int idelta = (this_step - other_step); + if (idelta < 0) idelta = - idelta; + if (third_step == 0 && ( + (idelta == 1) ? ( + this == (other_step +1) || + this == (other_step -1) || + this == (this_step +1) || + this == (this_step -1)) + : + ( + this == (this_step + other_step)/2 + ) + )) third_step = this; + if (third_step != this) break; + } + } + sum += this; + p--; + if (p < 0) p += LAST_STEPS; + count++; + } + msyslog(LOG_ERR, "MSF%d: %d: This=%d (%d), other=%d/%d, sum=%d, count=%d, pps_step=%d, suspect=%x", ees->unit, p, ees->last_steps[p], this_step, other_step, third_step, sum, count, pps_step, suspect_4ms_step); + if (count != 0) sum = ((sum * 60) + (count /2)) / count; +#define SV(x) (ees->last_steps[(x + p_step) % LAST_STEPS]) + msyslog(LOG_ERR, "MSF%d: %x steps %d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", + ees->unit, suspect_4ms_step, p_step, SV(0), SV(1), SV(2), SV(3), SV(4), SV(5), SV(6), + SV(7), SV(8), SV(9), SV(10), SV(11), SV(12), SV(13), SV(14), SV(15)); + printf("MSF%d: steps %d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + ees->unit, p_step, SV(0), SV(1), SV(2), SV(3), SV(4), SV(5), SV(6), + SV(7), SV(8), SV(9), SV(10), SV(11), SV(12), SV(13), SV(14), SV(15)); +#undef SV + ees->jump_fsecs = delta_sfsec; + ees->using_ramp = 1; + if (sincelast > 170) + ees->last_step_late += sincelast - ((sum) ? sum : ees->last_step_secs); + else ees->last_step_late = 30; + if (ees->last_step_late < -60 || ees->last_step_late > 120) ees->last_step_late = 30; + if (ees->last_step_late < 0) ees->last_step_late = 0; + if (ees->last_step_late >= 60) ees->last_step_late = 59; + sincelast = 0; + } + else { /* First time in -- just save info */ + ees->last_step_late = 30; + ees->jump_fsecs = delta_sfsec; + ees->using_ramp = 1; + sum = 4 * 60; + } + ees->last_step = this_uisec; + printf("MSF%d: d=%3ld.%04ld@%d :%d:%d:$%d:%d:%d\n", + ees->unit, (long)msec(delta_sfsec), (long)subms(delta_sfsec), + ees->second, old_sincelast, ees->last_step_late, count, sum, + ees->last_step_secs); + msyslog(LOG_ERR, "MSF%d: d=%3d.%04d@%d :%d:%d:%d:%d:%d", + ees->unit, msec(delta_sfsec), subms(delta_sfsec), ees->second, + old_sincelast, ees->last_step_late, count, sum, ees->last_step_secs); + if (sum) ees->last_step_secs = sum; + } + /* OK, so not a 4ms step at a minute boundry */ + else { + if (suspect_4ms_step) msyslog(LOG_ERR, + "MSF%d: suspect = %x, but delta of %d.%04d [%d.%04d<%d.%04d<%d.%04d: %d %d]", + ees->unit, suspect_4ms_step, msec(delta_sfsec), subms(delta_sfsec), + msec(EES_STEP_F - EES_STEP_F_GRACE), + subms(EES_STEP_F - EES_STEP_F_GRACE), + (int)msec(delta_f_abs), + (int)subms(delta_f_abs), + msec(EES_STEP_F + EES_STEP_F_GRACE), + subms(EES_STEP_F + EES_STEP_F_GRACE), + ees->second, + sincelast); + if ((delta_f_abs > EES_STEP_NOTE) && ees->last_l.l_i) { + static int ees_step_notes = EES_STEP_NOTES; + if (ees_step_notes > 0) { + ees_step_notes--; + printf("MSF%d: D=%3ld.%04ld@%02d :%d%s\n", + ees->unit, (long)msec(delta_sfsec), (long)subms(delta_sfsec), + ees->second, sincelast, ees_step_notes ? "" : " -- NO MORE !"); + msyslog(LOG_ERR, "MSF%d: D=%3d.%04d@%02d :%d%s", + ees->unit, msec(delta_sfsec), subms(delta_sfsec), ees->second, (ees->last_step) ? sincelast : -1, ees_step_notes ? "" : " -- NO MORE !"); + } + } + } + } + ees->last_l = ees->arrvtime; + + /* IF we have found that it's ramping + * && it's within twice the expected ramp period + * && there is a non zero step size (avoid /0 !) + * THEN we twiddle things + */ + if (ees->using_ramp && + sincelast < (ees->last_step_secs)*2 && + ees->last_step_secs) + { long sec_of_ramp = sincelast + ees->last_step_late; + long fsecs; + l_fp inc; + + /* Ramp time may vary, so may ramp for longer than last time */ + if (sec_of_ramp > (ees->last_step_secs + 120)) + sec_of_ramp = ees->last_step_secs; + + /* sec_of_ramp * ees->jump_fsecs may overflow 2**32 */ + fsecs = sec_of_ramp * (ees->jump_fsecs / ees->last_step_secs); + + if (debug & DB_LOG_DELTAS) msyslog(LOG_ERR, + "[%x] MSF%d: %3ld/%03d -> d=%11ld (%d|%ld)", + DB_LOG_DELTAS, + ees->unit, sec_of_ramp, ees->last_step_secs, fsecs, + pps_arrvstamp.l_f, pps_arrvstamp.l_f + fsecs); + if (debug & DB_PRINT_DELTAS) printf( + "MSF%d: %3ld/%03d -> d=%11ld (%ld|%ld)\n", + ees->unit, sec_of_ramp, ees->last_step_secs, fsecs, + (long)pps_arrvstamp.l_f, pps_arrvstamp.l_f + fsecs); + + /* Must sign extend the result */ + inc.l_i = (fsecs < 0) ? -1 : 0; + inc.l_f = fsecs; + if (debug & DB_INC_PPS) + { L_SUB(&pps_arrvstamp, &inc); + L_SUB(&ees->arrvtime, &inc); + } + else + { L_ADD(&pps_arrvstamp, &inc); + L_ADD(&ees->arrvtime, &inc); + } + } + else { + if (debug & DB_LOG_DELTAS) msyslog(LOG_ERR, + "[%x] MSF%d: ees->using_ramp=%d, sincelast=%x / %x, ees->last_step_secs=%x", + DB_LOG_DELTAS, + ees->unit, ees->using_ramp, + sincelast, + (ees->last_step_secs)*2, + ees->last_step_secs); + if (debug & DB_PRINT_DELTAS) printf( + "[%x] MSF%d: ees->using_ramp=%d, sincelast=%x / %x, ees->last_step_secs=%x\n", + DB_LOG_DELTAS, + ees->unit, ees->using_ramp, + sincelast, + (ees->last_step_secs)*2, + ees->last_step_secs); + } + + L_SUB(&ees->arrvtime, &offset_fudge[ees->unit]); + L_SUB(&pps_arrvstamp, &offset_fudge[ees->unit]); + + if (call_pps_sample && !(debug & DB_NO_PPS)) { + /* Sigh -- it expects its args negated */ + L_NEG(&pps_arrvstamp); + /* + * I had to disable this here, since it appears there is no pointer to the + * peer structure. + * + (void) pps_sample(peer, &pps_arrvstamp); + */ + } + + /* Subtract off the local clock time stamp */ + L_SUB(&ees->codeoffsets[n_sample], &ees->arrvtime); + if (debug & DB_LOG_SAMPLES) msyslog(LOG_ERR, + "MSF%d: [%x] %d (ees: %d %d) (pps: %d %d)%s", + ees->unit, DB_LOG_DELTAS, n_sample, + ees->codeoffsets[n_sample].l_f, + ees->codeoffsets[n_sample].l_f / 4295, + pps_arrvstamp.l_f, + pps_arrvstamp.l_f /4295, + (debug & DB_NO_PPS) ? " [no PPS]" : ""); + + if (ees->nsamples++ == NCODES-1) ees_process(ees); + + /* Done! */ +} + + +/* offcompare - auxiliary comparison routine for offset sort */ + +#ifdef QSORT_USES_VOID_P +static int +offcompare( + const void *va, + const void *vb + ) +{ + const l_fp *a = (const l_fp *)va; + const l_fp *b = (const l_fp *)vb; + return(L_ISGEQ(a, b) ? (L_ISEQU(a, b) ? 0 : 1) : -1); +} +#else +static int +offcompare( + const l_fp *a, + const l_fp *b + ) +{ + return(L_ISGEQ(a, b) ? (L_ISEQU(a, b) ? 0 : 1) : -1); +} +#endif /* QSORT_USES_VOID_P */ + + +/* ees_process - process a pile of samples from the clock */ +static void +ees_process( + struct eesunit *ees + ) +{ + static int last_samples = -1; + register int i, j; + register int noff; + register l_fp *coffs = ees->codeoffsets; + l_fp offset, tmp; + double dispersion; /* ++++ */ + int lostsync, isinsync; + int samples = ees->nsamples; + int samplelog = 0; /* keep "gcc -Wall" happy ! */ + int samplereduce = (samples + 1) / 2; + double doffset; + + /* Reset things to zero so we don't have to worry later */ + ees_reset(ees); + + if (sloppyclockflag[ees->unit]) { + samplelog = (samples < 2) ? 0 : + (samples < 5) ? 1 : + (samples < 9) ? 2 : + (samples < 17) ? 3 : + (samples < 33) ? 4 : 5; + samplereduce = (1 << samplelog); + } + + if (samples != last_samples && + ((samples != (last_samples-1)) || samples < 3)) { + msyslog(LOG_ERR, "Samples=%d (%d), samplereduce=%d ....", + samples, last_samples, samplereduce); + last_samples = samples; + } + if (samples < 1) return; + + /* If requested, dump the raw data we have in the buffer */ + if (ees->dump_vals) dump_buf(coffs, 0, samples, "Raw data is:"); + + /* Sort the offsets, trim off the extremes, then choose one. */ + qsort((char *) coffs, (size_t)samples, sizeof(l_fp), offcompare); + + noff = samples; + i = 0; + while ((noff - i) > samplereduce) { + /* Trim off the sample which is further away + * from the median. We work this out by doubling + * the median, subtracting off the end samples, and + * looking at the sign of the answer, using the + * identity (c-b)-(b-a) == 2*b-a-c + */ + tmp = coffs[(noff + i)/2]; + L_ADD(&tmp, &tmp); + L_SUB(&tmp, &coffs[i]); + L_SUB(&tmp, &coffs[noff-1]); + if (L_ISNEG(&tmp)) noff--; else i++; + } + + /* If requested, dump the reduce data we have in the buffer */ + if (ees->dump_vals) dump_buf(coffs, i, noff, "Reduced to:"); + + /* What we do next depends on the setting of the sloppy clock flag. + * If it is on, average the remainder to derive our estimate. + * Otherwise, just pick a representative value from the remaining stuff + */ + if (sloppyclockflag[ees->unit]) { + offset.l_ui = offset.l_uf = 0; + for (j = i; j < noff; j++) + L_ADD(&offset, &coffs[j]); + for (j = samplelog; j > 0; j--) + L_RSHIFTU(&offset); + } + else offset = coffs[i+BESTSAMPLE]; + + /* Compute the dispersion as the difference between the + * lowest and highest offsets that remain in the + * consideration list. + * + * It looks like MOST clocks have MOD (max error), so halve it ! + */ + tmp = coffs[noff-1]; + L_SUB(&tmp, &coffs[i]); +#define FRACT_SEC(n) ((1 << 30) / (n/2)) + dispersion = LFPTOFP(&tmp) / 2; /* ++++ */ + if (debug & (DB_SYSLOG_SMPLI | DB_SYSLOG_SMPLE)) msyslog( + (debug & DB_SYSLOG_SMPLE) ? LOG_ERR : LOG_INFO, + "I: [%x] Offset=%06d (%d), disp=%f%s [%d], %d %d=%d %d:%d %d=%d %d", + debug & (DB_SYSLOG_SMPLI | DB_SYSLOG_SMPLE), + offset.l_f / 4295, offset.l_f, + (dispersion * 1526) / 100, + (sloppyclockflag[ees->unit]) ? " by averaging" : "", + FRACT_SEC(10) / 4295, + (coffs[0].l_f) / 4295, + i, + (coffs[i].l_f) / 4295, + (coffs[samples/2].l_f) / 4295, + (coffs[i+BESTSAMPLE].l_f) / 4295, + noff-1, + (coffs[noff-1].l_f) / 4295, + (coffs[samples-1].l_f) / 4295); + + /* Are we playing silly wotsits ? + * If we are using all data, see if there is a "small" delta, + * and if so, blurr this with 3/4 of the delta from the last value + */ + if (ees->usealldata && ees->offset.l_uf) { + long diff = (long) (ees->offset.l_uf - offset.l_uf); + + /* is the delta small enough ? */ + if ((- FRACT_SEC(100)) < diff && diff < FRACT_SEC(100)) { + int samd = (64 * 4) / samples; + long new; + if (samd < 2) samd = 2; + new = offset.l_uf + ((diff * (samd -1)) / samd); + + /* Sign change -> need to fix up int part */ + if ((new & 0x80000000) != + (((long) offset.l_uf) & 0x80000000)) + { NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "I: %lx != %lx (%lx %lx), so add %d", + new & 0x80000000, + ((long) offset.l_uf) & 0x80000000, + new, (long) offset.l_uf, + (new < 0) ? -1 : 1); + offset.l_ui += (new < 0) ? -1 : 1; + } + dispersion /= 4; + if (debug & (DB_SYSLOG_SMTHI | DB_SYSLOG_SMTHE)) msyslog( + (debug & DB_SYSLOG_SMTHE) ? LOG_ERR : LOG_INFO, + "I: [%x] Smooth data: %ld -> %ld, dispersion now %f", + debug & (DB_SYSLOG_SMTHI | DB_SYSLOG_SMTHE), + ((long) offset.l_uf) / 4295, new / 4295, + (dispersion * 1526) / 100); + offset.l_uf = (unsigned long) new; + } + else if (debug & (DB_SYSLOG_NSMTHI | DB_SYSLOG_NSMTHE)) msyslog( + (debug & DB_SYSLOG_NSMTHE) ? LOG_ERR : LOG_INFO, + "[%x] No smooth as delta not %d < %ld < %d", + debug & (DB_SYSLOG_NSMTHI | DB_SYSLOG_NSMTHE), + - FRACT_SEC(100), diff, FRACT_SEC(100)); + } + else if (debug & (DB_SYSLOG_NSMTHI | DB_SYSLOG_NSMTHE)) msyslog( + (debug & DB_SYSLOG_NSMTHE) ? LOG_ERR : LOG_INFO, + "I: [%x] No smooth as flag=%x and old=%x=%d (%d:%d)", + debug & (DB_SYSLOG_NSMTHI | DB_SYSLOG_NSMTHE), + ees->usealldata, ees->offset.l_f, ees->offset.l_uf, + offset.l_f, ees->offset.l_f - offset.l_f); + + /* Collect offset info for debugging info */ + ees->offset = offset; + ees->lowoffset = coffs[i]; + ees->highoffset = coffs[noff-1]; + + /* Determine synchronization status. Can be unsync'd either + * by a report from the clock or by a leap hold. + * + * Loss of the radio signal for a short time does not cause + * us to go unsynchronised, since the receiver keeps quite + * good time on its own. The spec says 20ms in 4 hours; the + * observed drift in our clock (Cambridge) is about a second + * a day, but even that keeps us within the inherent tolerance + * of the clock for about 15 minutes. Observation shows that + * the typical "short" outage is 3 minutes, so to allow us + * to ride out those, we will give it 5 minutes. + */ + lostsync = current_time - ees->clocklastgood > 300 ? 1 : 0; + isinsync = (lostsync || ees->leaphold > current_time) ? 0 : 1; + + /* Done. Use time of last good, synchronised code as the + * reference time, and lastsampletime as the receive time. + */ + if (ees->fix_pending) { + msyslog(LOG_ERR, "MSF%d: fix_pending=%d -> jump %x.%08x\n", + ees->fix_pending, ees->unit, offset.l_i, offset.l_f); + ees->fix_pending = 0; + } + LFPTOD(&offset, doffset); + refclock_receive(ees->peer); + ees_event(ees, lostsync ? CEVNT_PROP : CEVNT_NOMINAL); +} + +/* msfees_poll - called by the transmit procedure */ +static void +msfees_poll( + int unit, + struct peer *peer + ) +{ + if (unit >= MAXUNITS) { + msyslog(LOG_ERR, "ees clock poll: INTERNAL: unit %d invalid", + unit); + return; + } + if (!unitinuse[unit]) { + msyslog(LOG_ERR, "ees clock poll: INTERNAL: unit %d unused", + unit); + return; + } + + ees_process(eesunits[unit]); + + if ((current_time - eesunits[unit]->lasttime) > 150) + ees_event(eesunits[unit], CEVNT_FAULT); +} + + +#else +int refclock_msfees_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_mx4200.c b/ntpd/refclock_mx4200.c new file mode 100644 index 0000000..bc694ad --- /dev/null +++ b/ntpd/refclock_mx4200.c @@ -0,0 +1,1654 @@ +/* + * This software was developed by the Computer Systems Engineering group + * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66. + * + * Copyright (c) 1992 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Lawrence Berkeley Laboratory. + * 4. The name of the University may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Modified: Marc Brett <marc.brett@westgeo.com> Sept, 1999. + * + * 1. Added support for alternate PPS schemes, with code mostly + * copied from the Oncore driver (Thanks, Poul-Henning Kamp). + * This code runs on SunOS 4.1.3 with ppsclock-1.6a1 and Solaris 7. + */ + + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_MX4200) && defined(HAVE_PPSAPI) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#include "mx4200.h" + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +#include "ntp_sprintf.h" + +#ifndef HAVE_STRUCT_PPSCLOCKEV +struct ppsclockev { +# ifdef HAVE_STRUCT_TIMESPEC + struct timespec tv; +# else + struct timeval tv; +# endif + u_int serial; +}; +#endif /* ! HAVE_STRUCT_PPSCLOCKEV */ + +#ifdef HAVE_TIMEPPS_H +# include <timepps.h> +#else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +#endif + +/* + * This driver supports the Magnavox Model MX 4200 GPS Receiver + * adapted to precision timing applications. It requires the + * ppsclock line discipline or streams module described in the + * Line Disciplines and Streams Drivers page. It also requires a + * gadget box and 1-PPS level converter, such as described in the + * Pulse-per-second (PPS) Signal Interfacing page. + * + * It's likely that other compatible Magnavox receivers such as the + * MX 4200D, MX 9212, MX 9012R, MX 9112 will be supported by this code. + */ + +/* + * Check this every time you edit the code! + */ +#define YEAR_LAST_MODIFIED 2000 + +/* + * GPS Definitions + */ +#define DEVICE "/dev/gps%d" /* device name and unit */ +#define SPEED232 B4800 /* baud */ + +/* + * Radio interface parameters + */ +#define PRECISION (-18) /* precision assumed (about 4 us) */ +#define REFID "GPS\0" /* reference id */ +#define DESCRIPTION "Magnavox MX4200 GPS Receiver" /* who we are */ +#define DEFFUDGETIME 0 /* default fudge time (ms) */ + +#define SLEEPTIME 32 /* seconds to wait for reconfig to complete */ + +/* + * Position Averaging. + */ +#define INTERVAL 1 /* Interval between position measurements (s) */ +#define AVGING_TIME 24 /* Number of hours to average */ +#define NOT_INITIALIZED -9999. /* initial pivot longitude */ + +/* + * MX4200 unit control structure. + */ +struct mx4200unit { + u_int pollcnt; /* poll message counter */ + u_int polled; /* Hand in a time sample? */ + u_int lastserial; /* last pps serial number */ + struct ppsclockev ppsev; /* PPS control structure */ + double avg_lat; /* average latitude */ + double avg_lon; /* average longitude */ + double avg_alt; /* average height */ + double central_meridian; /* central meridian */ + double N_fixes; /* Number of position measurements */ + int last_leap; /* leap second warning */ + u_int moving; /* mobile platform? */ + u_long sloppyclockflag; /* fudge flags */ + u_int known; /* position known yet? */ + u_long clamp_time; /* when to stop postion averaging */ + u_long log_time; /* when to print receiver status */ + pps_handle_t pps_h; + pps_params_t pps_p; + pps_info_t pps_i; +}; + +static char pmvxg[] = "PMVXG"; + +/* XXX should be somewhere else */ +#ifdef __GNUC__ +#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) +#ifndef __attribute__ +#define __attribute__(args) +#endif /* __attribute__ */ +#endif /* __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) */ +#else +#ifndef __attribute__ +#define __attribute__(args) +#endif /* __attribute__ */ +#endif /* __GNUC__ */ +/* XXX end */ + +/* + * Function prototypes + */ +static int mx4200_start P((int, struct peer *)); +static void mx4200_shutdown P((int, struct peer *)); +static void mx4200_receive P((struct recvbuf *)); +static void mx4200_poll P((int, struct peer *)); + +static char * mx4200_parse_t P((struct peer *)); +static char * mx4200_parse_p P((struct peer *)); +static char * mx4200_parse_s P((struct peer *)); +#ifdef QSORT_USES_VOID_P +int mx4200_cmpl_fp P((const void *, const void *)); +#else +int mx4200_cmpl_fp P((const l_fp *, const l_fp *)); +#endif /* not QSORT_USES_VOID_P */ +static int mx4200_config P((struct peer *)); +static void mx4200_ref P((struct peer *)); +static void mx4200_send P((struct peer *, char *, ...)) + __attribute__ ((format (printf, 2, 3))); +static u_char mx4200_cksum P((char *, int)); +static int mx4200_jday P((int, int, int)); +static void mx4200_debug P((struct peer *, char *, ...)) + __attribute__ ((format (printf, 2, 3))); +static int mx4200_pps P((struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_mx4200 = { + mx4200_start, /* start up driver */ + mx4200_shutdown, /* shut down driver */ + mx4200_poll, /* transmit poll message */ + noentry, /* not used (old mx4200_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old mx4200_buginfo) */ + NOFLAGS /* not used */ +}; + + + +/* + * mx4200_start - open the devices and initialize data for processing + */ +static int +mx4200_start( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + int fd; + char gpsdev[20]; + + /* + * Open serial port + */ + (void)sprintf(gpsdev, DEVICE, unit); + if (!(fd = refclock_open(gpsdev, SPEED232, LDISC_PPS))) { + return (0); + } + + /* + * Allocate unit structure + */ + if (!(up = (struct mx4200unit *) emalloc(sizeof(struct mx4200unit)))) { + perror("emalloc"); + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct mx4200unit)); + pp = peer->procptr; + pp->io.clock_recv = mx4200_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + + /* Ensure the receiver is properly configured */ + return mx4200_config(peer); +} + + +/* + * mx4200_shutdown - shut down the clock + */ +static void +mx4200_shutdown( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * mx4200_config - Configure the receiver + */ +static int +mx4200_config( + struct peer *peer + ) +{ + char tr_mode; + int add_mode; + register struct mx4200unit *up; + struct refclockproc *pp; + int mode; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * Initialize the unit variables + * + * STRANGE BEHAVIOUR WARNING: The fudge flags are not available + * at the time mx4200_start is called. These are set later, + * and so the code must be prepared to handle changing flags. + */ + up->sloppyclockflag = pp->sloppyclockflag; + if (pp->sloppyclockflag & CLK_FLAG2) { + up->moving = 1; /* Receiver on mobile platform */ + msyslog(LOG_DEBUG, "mx4200_config: mobile platform"); + } else { + up->moving = 0; /* Static Installation */ + } + up->pollcnt = 2; + up->polled = 0; + up->known = 0; + up->avg_lat = 0.0; + up->avg_lon = 0.0; + up->avg_alt = 0.0; + up->central_meridian = NOT_INITIALIZED; + up->N_fixes = 0.0; + up->last_leap = 0; /* LEAP_NOWARNING */ + up->clamp_time = current_time + (AVGING_TIME * 60 * 60); + up->log_time = current_time + SLEEPTIME; + + if (time_pps_create(pp->io.fd, &up->pps_h) < 0) { + perror("time_pps_create"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_create failed: %m"); + return (0); + } + if (time_pps_getcap(up->pps_h, &mode) < 0) { + msyslog(LOG_ERR, + "mx4200_config: time_pps_getcap failed: %m"); + return (0); + } + + if (time_pps_getparams(up->pps_h, &up->pps_p) < 0) { + msyslog(LOG_ERR, + "mx4200_config: time_pps_getparams failed: %m"); + return (0); + } + + /* nb. only turn things on, if someone else has turned something + * on before we get here, leave it alone! + */ + + up->pps_p.mode = PPS_CAPTUREASSERT | PPS_TSFMT_TSPEC; + up->pps_p.mode &= mode; /* only set what is legal */ + + if (time_pps_setparams(up->pps_h, &up->pps_p) < 0) { + perror("time_pps_setparams"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_setparams failed: %m"); + exit(1); + } + + if (time_pps_kcbind(up->pps_h, PPS_KC_HARDPPS, PPS_CAPTUREASSERT, + PPS_TSFMT_TSPEC) < 0) { + perror("time_pps_kcbind"); + msyslog(LOG_ERR, + "mx4200_config: time_pps_kcbind failed: %m"); + exit(1); + } + + + /* + * "007" Control Port Configuration + * Zero the output list (do it twice to flush possible junk) + */ + mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, + PMVXG_S_PORTCONF, + /* control port output block Label */ + 1); /* clear current output control list (1=yes) */ + /* add/delete sentences from list */ + /* must be null */ + /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + mx4200_send(peer, "%s,%03d,,%d,,,,,,", pmvxg, + PMVXG_S_PORTCONF, 1); + + /* + * Request software configuration so we can syslog the firmware version + */ + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_SOFTCONF); + + /* + * "001" Initialization/Mode Control, Part A + * Where ARE we? + */ + mx4200_send(peer, "%s,%03d,,,,,,,,,,", pmvxg, + PMVXG_S_INITMODEA); + /* day of month */ + /* month of year */ + /* year */ + /* gmt */ + /* latitude DDMM.MMMM */ + /* north/south */ + /* longitude DDDMM.MMMM */ + /* east/west */ + /* height */ + /* Altitude Reference 1=MSL */ + + /* + * "001" Initialization/Mode Control, Part B + * Start off in 2d/3d coast mode, holding altitude to last known + * value if only 3 satellites available. + */ + mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", + pmvxg, PMVXG_S_INITMODEB, + 3, /* 2d/3d coast */ + /* reserved */ + 0.1, /* hor accel fact as per Steve (m/s**2) */ + 0.1, /* ver accel fact as per Steve (m/s**2) */ + 10, /* vdop */ + 10, /* hdop limit as per Steve */ + 5, /* elevation limit as per Steve (deg) */ + 'U', /* time output mode (UTC) */ + 0); /* local time offset from gmt (HHHMM) */ + + /* + * "023" Time Recovery Configuration + * Get UTC time from a stationary receiver. + * (Set field 1 'D' == dynamic if we are on a moving platform). + * (Set field 1 'S' == static if we are not moving). + * (Set field 1 'K' == known position if we can initialize lat/lon/alt). + */ + + if (pp->sloppyclockflag & CLK_FLAG2) + up->moving = 1; /* Receiver on mobile platform */ + else + up->moving = 0; /* Static Installation */ + + up->pollcnt = 2; + if (up->moving) { + /* dynamic: solve for pos, alt, time, while moving */ + tr_mode = 'D'; + } else { + /* static: solve for pos, alt, time, while stationary */ + tr_mode = 'S'; + } + mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, + PMVXG_S_TRECOVCONF, + tr_mode, /* time recovery mode (see above ) */ + 'U', /* synchronize to UTC */ + 'A', /* always output a time pulse */ + 500, /* max time error in ns */ + 0, /* user bias in ns */ + 1); /* output "830" sentences to control port */ + /* Multi-satellite mode */ + + /* + * Output position information (to calculate fixed installation + * location) only if we are not moving + */ + if (up->moving) { + add_mode = 2; /* delete from list */ + } else { + add_mode = 1; /* add to list */ + } + + + /* + * "007" Control Port Configuration + * Output "021" position, height, velocity reports + */ + mx4200_send(peer, "%s,%03d,%03d,%d,%d,,%d,,,", pmvxg, + PMVXG_S_PORTCONF, + PMVXG_D_PHV, /* control port output block Label */ + 0, /* clear current output control list (0=no) */ + add_mode, /* add/delete sentences from list (1=add, 2=del) */ + /* must be null */ + INTERVAL); /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + + return (1); +} + +/* + * mx4200_ref - Reconfigure unit as a reference station at a known position. + */ +static void +mx4200_ref( + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + double minute, lat, lon, alt; + char lats[16], lons[16]; + char nsc, ewc; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* Should never happen! */ + if (up->moving) return; + + /* + * Set up to output status information in the near future + */ + up->log_time = current_time + SLEEPTIME; + + /* + * "007" Control Port Configuration + * Stop outputting "021" position, height, velocity reports + */ + mx4200_send(peer, "%s,%03d,%03d,%d,%d,,,,,", pmvxg, + PMVXG_S_PORTCONF, + PMVXG_D_PHV, /* control port output block Label */ + 0, /* clear current output control list (0=no) */ + 2); /* add/delete sentences from list (2=delete) */ + /* must be null */ + /* sentence output rate (sec) */ + /* precision for position output */ + /* nmea version for cga & gll output */ + /* pass-through control */ + + /* + * "001" Initialization/Mode Control, Part B + * Put receiver in fully-constrained 2d nav mode + */ + mx4200_send(peer, "%s,%03d,%d,,%.1f,%.1f,%d,%d,%d,%c,%d", + pmvxg, PMVXG_S_INITMODEB, + 2, /* 2d nav */ + /* reserved */ + 0.1, /* hor accel fact as per Steve (m/s**2) */ + 0.1, /* ver accel fact as per Steve (m/s**2) */ + 10, /* vdop */ + 10, /* hdop limit as per Steve */ + 5, /* elevation limit as per Steve (deg) */ + 'U', /* time output mode (UTC) */ + 0); /* local time offset from gmt (HHHMM) */ + + /* + * "023" Time Recovery Configuration + * Get UTC time from a stationary receiver. Solve for time only. + * This should improve the time resolution dramatically. + */ + mx4200_send(peer, "%s,%03d,%c,%c,%c,%d,%d,%d,", pmvxg, + PMVXG_S_TRECOVCONF, + 'K', /* known position: solve for time only */ + 'U', /* synchronize to UTC */ + 'A', /* always output a time pulse */ + 500, /* max time error in ns */ + 0, /* user bias in ns */ + 1); /* output "830" sentences to control port */ + /* Multi-satellite mode */ + + /* + * "000" Initialization/Mode Control - Part A + * Fix to our averaged position. + */ + if (up->central_meridian != NOT_INITIALIZED) { + up->avg_lon += up->central_meridian; + if (up->avg_lon < -180.0) up->avg_lon += 360.0; + if (up->avg_lon > 180.0) up->avg_lon -= 360.0; + } + + if (up->avg_lat >= 0.0) { + lat = up->avg_lat; + nsc = 'N'; + } else { + lat = up->avg_lat * (-1.0); + nsc = 'S'; + } + if (up->avg_lon >= 0.0) { + lon = up->avg_lon; + ewc = 'E'; + } else { + lon = up->avg_lon * (-1.0); + ewc = 'W'; + } + alt = up->avg_alt; + minute = (lat - (double)(int)lat) * 60.0; + sprintf(lats,"%02d%02.4f", (int)lat, minute); + minute = (lon - (double)(int)lon) * 60.0; + sprintf(lons,"%03d%02.4f", (int)lon, minute); + + mx4200_send(peer, "%s,%03d,,,,,%s,%c,%s,%c,%.2f,%d", pmvxg, + PMVXG_S_INITMODEA, + /* day of month */ + /* month of year */ + /* year */ + /* gmt */ + lats, /* latitude DDMM.MMMM */ + nsc, /* north/south */ + lons, /* longitude DDDMM.MMMM */ + ewc, /* east/west */ + alt, /* Altitude */ + 1); /* Altitude Reference (0=WGS84 ellipsoid, 1=MSL geoid)*/ + + msyslog(LOG_DEBUG, + "mx4200: reconfig to fixed location: %s %c, %s %c, %.2f m", + lats, nsc, lons, ewc, alt ); + +} + +/* + * mx4200_poll - mx4200 watchdog routine + */ +static void +mx4200_poll( + int unit, + struct peer *peer + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * You don't need to poll this clock. It puts out timecodes + * once per second. If asked for a timestamp, take note. + * The next time a timecode comes in, it will be fed back. + */ + + /* + * If we haven't had a response in a while, reset the receiver. + */ + if (up->pollcnt > 0) { + up->pollcnt--; + } else { + refclock_report(peer, CEVNT_TIMEOUT); + + /* + * Request a "000" status message which should trigger a + * reconfig + */ + mx4200_send(peer, "%s,%03d", + "CDGPQ", /* query from CDU to GPS */ + PMVXG_D_STATUS); /* label of desired sentence */ + } + + /* + * polled every 64 seconds. Ask mx4200_receive to hand in + * a timestamp. + */ + up->polled = 1; + pp->polls++; + + /* + * Output receiver status information. + */ + if ((up->log_time > 0) && (current_time > up->log_time)) { + up->log_time = 0; + /* + * Output the following messages once, for debugging. + * "004" Mode Data + * "523" Time Recovery Parameters + */ + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_MODEDATA); + mx4200_send(peer, "%s,%03d", "CDGPQ", PMVXG_D_TRECOVUSEAGE); + } +} + +static char char2hex[] = "0123456789ABCDEF"; + +/* + * mx4200_receive - receive gps data + */ +static void +mx4200_receive( + struct recvbuf *rbufp + ) +{ + register struct mx4200unit *up; + struct refclockproc *pp; + struct peer *peer; + char *cp; + int sentence_type; + u_char ck; + + /* + * Initialize pointers and read the timecode and timestamp. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * If operating mode has been changed, then reinitialize the receiver + * before doing anything else. + */ + if ((pp->sloppyclockflag & CLK_FLAG2) != + (up->sloppyclockflag & CLK_FLAG2)) { + up->sloppyclockflag = pp->sloppyclockflag; + mx4200_debug(peer, + "mx4200_receive: mode switch: reset receiver\n"); + mx4200_config(peer); + return; + } + up->sloppyclockflag = pp->sloppyclockflag; + + /* + * Read clock output. Automatically handles STREAMS, CLKLDISC. + */ + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); + + /* + * There is a case where <cr><lf> generates 2 timestamps. + */ + if (pp->lencode == 0) + return; + + up->pollcnt = 2; + pp->a_lastcode[pp->lencode] = '\0'; + record_clock_stats(&peer->srcadr, pp->a_lastcode); + mx4200_debug(peer, "mx4200_receive: %d %s\n", + pp->lencode, pp->a_lastcode); + + /* + * The structure of the control port sentences is based on the + * NMEA-0183 Standard for interfacing Marine Electronics + * Navigation Devices (Version 1.5) + * + * $PMVXG,XXX, ....................*CK<cr><lf> + * + * $ Sentence Start Identifier (reserved char) + * (Start-of-Sentence Identifier) + * P Special ID (Proprietary) + * MVX Originator ID (Magnavox) + * G Interface ID (GPS) + * , Field Delimiters (reserved char) + * XXX Sentence Type + * ...... Data + * * Checksum Field Delimiter (reserved char) + * CK Checksum + * <cr><lf> Carriage-Return/Line Feed (reserved chars) + * (End-of-Sentence Identifier) + * + * Reject if any important landmarks are missing. + */ + cp = pp->a_lastcode + pp->lencode - 3; + if (cp < pp->a_lastcode || *pp->a_lastcode != '$' || cp[0] != '*' ) { + mx4200_debug(peer, "mx4200_receive: bad format\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Check and discard the checksum + */ + ck = mx4200_cksum(&pp->a_lastcode[1], pp->lencode - 4); + if (char2hex[ck >> 4] != cp[1] || char2hex[ck & 0xf] != cp[2]) { + mx4200_debug(peer, "mx4200_receive: bad checksum\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + *cp = '\0'; + + /* + * Get the sentence type. + */ + sentence_type = 0; + if ((cp = strchr(pp->a_lastcode, ',')) == NULL) { + mx4200_debug(peer, "mx4200_receive: no sentence\n"); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + cp++; + sentence_type = strtol(cp, &cp, 10); + + /* + * Process the sentence according to its type. + */ + switch (sentence_type) { + + /* + * "000" Status message + */ + case PMVXG_D_STATUS: + /* + * XXX + * Since we configure the receiver to not give us status + * messages and since the receiver outputs status messages by + * default after being reset to factory defaults when sent the + * "$PMVXG,018,C\r\n" message, any status message we get + * indicates the reciever needs to be initialized; thus, it is + * not necessary to decode the status message. + */ + if ((cp = mx4200_parse_s(peer)) != NULL) { + mx4200_debug(peer, + "mx4200_receive: status: %s\n", cp); + } + mx4200_debug(peer, "mx4200_receive: reset receiver\n"); + mx4200_config(peer); + break; + + /* + * "021" Position, Height, Velocity message, + * if we are still averaging our position + */ + case PMVXG_D_PHV: + if (!up->known) { + /* + * Parse the message, calculating our averaged position. + */ + if ((cp = mx4200_parse_p(peer)) != NULL) { + mx4200_debug(peer, "mx4200_receive: pos: %s\n", cp); + return; + } + mx4200_debug(peer, + "mx4200_receive: position avg %f %.9f %.9f %.4f\n", + up->N_fixes, up->avg_lat, up->avg_lon, up->avg_alt); + /* + * Reinitialize as a reference station + * if position is well known. + */ + if (current_time > up->clamp_time) { + up->known++; + mx4200_debug(peer, "mx4200_receive: reconfiguring!\n"); + mx4200_ref(peer); + } + } + break; + + /* + * Print to the syslog: + * "004" Mode Data + * "030" Software Configuration + * "523" Time Recovery Parameters Currently in Use + */ + case PMVXG_D_MODEDATA: + case PMVXG_D_SOFTCONF: + case PMVXG_D_TRECOVUSEAGE: + + if ((cp = mx4200_parse_s(peer)) != NULL) { + mx4200_debug(peer, + "mx4200_receive: multi-record: %s\n", cp); + } + break; + + /* + * "830" Time Recovery Results message + */ + case PMVXG_D_TRECOVOUT: + + /* + * Capture the last PPS signal. + * Precision timestamp is returned in pp->lastrec + */ + if (mx4200_pps(peer) != NULL) { + mx4200_debug(peer, "mx4200_receive: pps failure\n"); + refclock_report(peer, CEVNT_FAULT); + return; + } + + + /* + * Parse the time recovery message, and keep the info + * to print the pretty billboards. + */ + if ((cp = mx4200_parse_t(peer)) != NULL) { + mx4200_debug(peer, "mx4200_receive: time: %s\n", cp); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Add the new sample to a median filter. + */ + if (!refclock_process(pp)) { + mx4200_debug(peer,"mx4200_receive: offset: %.6f\n", + pp->offset); + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * The clock will blurt a timecode every second but we only + * want one when polled. If we havn't been polled, bail out. + */ + if (!up->polled) + return; + + /* + * Return offset and dispersion to control module. We use + * lastrec as both the reference time and receive time in + * order to avoid being cute, like setting the reference time + * later than the receive time, which may cause a paranoid + * protocol module to chuck out the data. + */ + mx4200_debug(peer, "mx4200_receive: process time: "); + mx4200_debug(peer, "%4d-%03d %02d:%02d:%02d at %s, %.6f\n", + pp->year, pp->day, pp->hour, pp->minute, pp->second, + prettydate(&pp->lastrec), pp->offset); + pp->lastref = pp->lastrec; + refclock_receive(peer); + + /* + * We have succeeded in answering the poll. + * Turn off the flag and return + */ + up->polled = 0; + break; + + /* + * Ignore all other sentence types + */ + default: + break; + + } /* switch (sentence_type) */ + + return; +} + + +/* + * Parse a mx4200 time recovery message. Returns a string if error. + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,830,T,YYYY,MM,DD,HH:MM:SS,U,S,FFFFFF,PPPPP,BBBBBB,LL + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 830=Time Recovery Results + * This sentence is output approximately 1 second + * preceding the 1PPS output. It indicates the + * exact time of the next pulse, whether or not the + * time mark will be valid (based on operator-specified + * error tolerance), the time to which the pulse is + * synchronized, the receiver operating mode, + * and the time error of the *last* 1PPS output. + * 1 char Time Mark Valid: T=Valid, F=Not Valid + * 2 int Year: 1993- + * 3 int Month of Year: 1-12 + * 4 int Day of Month: 1-31 + * 5 int Time of Day: HH:MM:SS + * 6 char Time Synchronization: U=UTC, G=GPS + * 7 char Time Recovery Mode: D=Dynamic, S=Static, + * K=Known Position, N=No Time Recovery + * 8 int Oscillator Offset: The filter's estimate of the oscillator + * frequency error, in parts per billion (ppb). + * 9 int Time Mark Error: The computed error of the *last* pulse + * output, in nanoseconds. + * 10 int User Time Bias: Operator specified bias, in nanoseconds + * 11 int Leap Second Flag: Indicates that a leap second will + * occur. This value is usually zero, except during + * the week prior to the leap second occurrence, when + * this value will be set to +1 or -1. A value of + * +1 indicates that GPS time will be 1 second + * further ahead of UTC time. + * + */ +static char * +mx4200_parse_t( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + char time_mark_valid, time_sync, op_mode; + int sentence_type, valid; + int year, day_of_year, month, day_of_month; + int hour, minute, second, leapsec; + int oscillator_offset, time_mark_error, time_bias; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + leapsec = 0; /* Not all receivers output leap second warnings (!) */ + sscanf(pp->a_lastcode, + "$PMVXG,%d,%c,%d,%d,%d,%d:%d:%d,%c,%c,%d,%d,%d,%d", + &sentence_type, &time_mark_valid, &year, &month, &day_of_month, + &hour, &minute, &second, &time_sync, &op_mode, + &oscillator_offset, &time_mark_error, &time_bias, &leapsec); + + if (sentence_type != PMVXG_D_TRECOVOUT) + return ("wrong rec-type"); + + switch (time_mark_valid) { + case 'T': + valid = 1; + break; + case 'F': + valid = 0; + break; + default: + return ("bad pulse-valid"); + } + + switch (time_sync) { + case 'G': + return ("synchronized to GPS; should be UTC"); + case 'U': + break; /* UTC -> ok */ + default: + return ("not synchronized to UTC"); + } + + /* + * Check for insane time (allow for possible leap seconds) + */ + if (second > 60 || minute > 59 || hour > 23 || + second < 0 || minute < 0 || hour < 0) { + mx4200_debug(peer, + "mx4200_parse_t: bad time %02d:%02d:%02d", + hour, minute, second); + if (leapsec != 0) + mx4200_debug(peer, " (leap %+d\n)", leapsec); + mx4200_debug(peer, "\n"); + refclock_report(peer, CEVNT_BADTIME); + return ("bad time"); + } + if ( second == 60 ) { + msyslog(LOG_DEBUG, + "mx4200: leap second! %02d:%02d:%02d", + hour, minute, second); + } + + /* + * Check for insane date + * (Certainly can't be any year before this code was last altered!) + */ + if (day_of_month > 31 || month > 12 || + day_of_month < 1 || month < 1 || year < YEAR_LAST_MODIFIED) { + mx4200_debug(peer, + "mx4200_parse_t: bad date (%4d-%02d-%02d)\n", + year, month, day_of_month); + refclock_report(peer, CEVNT_BADDATE); + return ("bad date"); + } + + /* + * Silly Hack for MX4200: + * ASCII message is for *next* 1PPS signal, but we have the + * timestamp for the *last* 1PPS signal. So we have to subtract + * a second. Discard if we are on a month boundary to avoid + * possible leap seconds and leap days. + */ + second--; + if (second < 0) { + second = 59; + minute--; + if (minute < 0) { + minute = 59; + hour--; + if (hour < 0) { + hour = 23; + day_of_month--; + if (day_of_month < 1) { + return ("sorry, month boundary"); + } + } + } + } + + /* + * Calculate Julian date + */ + if (!(day_of_year = mx4200_jday(year, month, day_of_month))) { + mx4200_debug(peer, + "mx4200_parse_t: bad julian date %d (%4d-%02d-%02d)\n", + day_of_year, year, month, day_of_month); + refclock_report(peer, CEVNT_BADDATE); + return("invalid julian date"); + } + + /* + * Setup leap second indicator + */ + switch (leapsec) { + case 0: + pp->leap = LEAP_NOWARNING; + break; + case 1: + pp->leap = LEAP_ADDSECOND; + break; + case -1: + pp->leap = LEAP_DELSECOND; + break; + default: + pp->leap = LEAP_NOTINSYNC; + } + + /* + * Any change to the leap second warning status? + */ + if (leapsec != up->last_leap ) { + msyslog(LOG_DEBUG, + "mx4200: leap second warning: %d to %d (%d)", + up->last_leap, leapsec, pp->leap); + } + up->last_leap = leapsec; + + /* + * Copy time data for billboard monitoring. + */ + + pp->year = year; + pp->day = day_of_year; + pp->hour = hour; + pp->minute = minute; + pp->second = second; + + /* + * Toss if sentence is marked invalid + */ + if (!valid || pp->leap == LEAP_NOTINSYNC) { + mx4200_debug(peer, "mx4200_parse_t: time mark not valid\n"); + refclock_report(peer, CEVNT_BADTIME); + return ("pulse invalid"); + } + + return (NULL); +} + +/* + * Calculate the checksum + */ +static u_char +mx4200_cksum( + register char *cp, + register int n + ) +{ + register u_char ck; + + for (ck = 0; n-- > 0; cp++) + ck ^= *cp; + return (ck); +} + +/* + * Tables to compute the day of year. Viva la leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Calculate the the Julian Day + */ +static int +mx4200_jday( + int year, + int month, + int day_of_month + ) +{ + register int day, i; + int leap_year; + + /* + * Is this a leap year ? + */ + if (year % 4) { + leap_year = 0; /* FALSE */ + } else { + if (year % 100) { + leap_year = 1; /* TRUE */ + } else { + if (year % 400) { + leap_year = 0; /* FALSE */ + } else { + leap_year = 1; /* TRUE */ + } + } + } + + /* + * Calculate the Julian Date + */ + day = day_of_month; + + if (leap_year) { + /* a leap year */ + if (day > day2tab[month - 1]) { + return (0); + } + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } else { + /* not a leap year */ + if (day > day1tab[month - 1]) { + return (0); + } + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } + return (day); +} + +/* + * Parse a mx4200 position/height/velocity sentence. + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,021,SSSSSS.SS,DDMM.MMMM,N,DDDMM.MMMM,E,HHHHH.H,GGGG.G,EEEE.E,WWWW.W,MM + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 021=Position, Height Velocity Data + * This sentence gives the receiver position, height, + * navigation mode, and velocity north/east. + * *This sentence is intended for post-analysis + * applications.* + * 1 float UTC measurement time (seconds into week) + * 2 float WGS-84 Lattitude (degrees, minutes) + * 3 char N=North, S=South + * 4 float WGS-84 Longitude (degrees, minutes) + * 5 char E=East, W=West + * 6 float Altitude (meters above mean sea level) + * 7 float Geoidal height (meters) + * 8 float East velocity (m/sec) + * 9 float West Velocity (m/sec) + * 10 int Navigation Mode + * Mode if navigating: + * 1 = Position from remote device + * 2 = 2-D position + * 3 = 3-D position + * 4 = 2-D differential position + * 5 = 3-D differential position + * 6 = Static + * 8 = Position known -- reference station + * 9 = Position known -- Navigator + * Mode if not navigating: + * 51 = Too few satellites + * 52 = DOPs too large + * 53 = Position STD too large + * 54 = Velocity STD too large + * 55 = Too many iterations for velocity + * 56 = Too many iterations for position + * 57 = 3 sat startup failed + * 58 = Command abort + */ +static char * +mx4200_parse_p( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + int sentence_type, mode; + double mtime, lat, lon, alt, geoid, vele, veln; + char north_south, east_west; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* Should never happen! */ + if (up->moving) return ("mobile platform - no pos!"); + + sscanf ( pp->a_lastcode, + "$PMVXG,%d,%lf,%lf,%c,%lf,%c,%lf,%lf,%lf,%lf,%d", + &sentence_type, &mtime, &lat, &north_south, &lon, &east_west, + &alt, &geoid, &vele, &veln, &mode); + + /* Sentence type */ + if (sentence_type != PMVXG_D_PHV) + return ("wrong rec-type"); + + /* + * return if not navigating + */ + if (mode > 10) + return ("not navigating"); + if (mode != 3 && mode != 5) + return ("not navigating in 3D"); + + /* Latitude (always +ve) and convert DDMM.MMMM to decimal */ + if (lat < 0.0) return ("negative latitude"); + if (lat > 9000.0) lat = 9000.0; + lat *= 0.01; + lat = ((int)lat) + (((lat - (int)lat)) * 1.6666666666666666); + + /* North/South */ + switch (north_south) { + case 'N': + break; + case 'S': + lat *= -1.0; + break; + default: + return ("invalid north/south indicator"); + } + + /* Longitude (always +ve) and convert DDDMM.MMMM to decimal */ + if (lon < 0.0) return ("negative longitude"); + if (lon > 180.0) lon = 180.0; + lon *= 0.01; + lon = ((int)lon) + (((lon - (int)lon)) * 1.6666666666666666); + + /* East/West */ + switch (east_west) { + case 'E': + break; + case 'W': + lon *= -1.0; + break; + default: + return ("invalid east/west indicator"); + } + + /* + * Normalize longitude to near 0 degrees. + * Assume all data are clustered around first reading. + */ + if (up->central_meridian == NOT_INITIALIZED) { + up->central_meridian = lon; + mx4200_debug(peer, + "mx4200_receive: central meridian = %.9f \n", + up->central_meridian); + } + lon -= up->central_meridian; + if (lon < -180.0) lon += 360.0; + if (lon > 180.0) lon -= 360.0; + + /* + * Calculate running averages + */ + + up->avg_lon = (up->N_fixes * up->avg_lon) + lon; + up->avg_lat = (up->N_fixes * up->avg_lat) + lat; + up->avg_alt = (up->N_fixes * up->avg_alt) + alt; + + up->N_fixes += 1.0; + + up->avg_lon /= up->N_fixes; + up->avg_lat /= up->N_fixes; + up->avg_alt /= up->N_fixes; + + mx4200_debug(peer, + "mx4200_receive: position rdg %.0f: %.9f %.9f %.4f (CM=%.9f)\n", + up->N_fixes, lat, lon, alt, up->central_meridian); + + return (NULL); +} + +/* + * Parse a mx4200 Status sentence + * Parse a mx4200 Mode Data sentence + * Parse a mx4200 Software Configuration sentence + * Parse a mx4200 Time Recovery Parameters Currently in Use sentence + * (used only for logging raw strings) + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,000,XXX,XX,X,HHMM,X + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 000=Status. + * Returns status of the receiver to the controller. + * 1 Current Receiver Status: + * ACQ = Satellite re-acquisition + * ALT = Constellation selection + * COR = Providing corrections (for reference stations only) + * IAC = Initial acquisition + * IDL = Idle, no satellites + * NAV = Navigation + * STS = Search the Sky (no almanac available) + * TRK = Tracking + * 2 Number of satellites that should be visible + * 3 Number of satellites being tracked + * 4 Time since last navigation status if not currently navigating + * (hours, minutes) + * 5 Initialization status: + * 0 = Waiting for initialization parameters + * 1 = Initialization completed + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,004,C,R,D,H.HH,V.VV,TT,HHHH,VVVV,T + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 004=Software Configuration. + * Defines the navigation mode and criteria for + * acceptable navigation for the receiver. + * 1 Constrain Altitude Mode: + * 0 = Auto. Constrain altitude (2-D solution) and use + * manual altitude input when 3 sats avalable. Do + * not constrain altitude (3-D solution) when 4 sats + * available. + * 1 = Always constrain altitude (2-D solution). + * 2 = Never constrain altitude (3-D solution). + * 3 = Coast. Constrain altitude (2-D solution) and use + * last GPS altitude calculation when 3 sats avalable. + * Do not constrain altitude (3-D solution) when 4 sats + * available. + * 2 Altitude Reference: (always 0 for MX4200) + * 0 = Ellipsoid + * 1 = Geoid (MSL) + * 3 Differential Navigation Control: + * 0 = Disabled + * 1 = Enabled + * 4 Horizontal Acceleration Constant (m/sec**2) + * 5 Vertical Acceleration Constant (m/sec**2) (0 for MX4200) + * 6 Tracking Elevation Limit (degrees) + * 7 HDOP Limit + * 8 VDOP Limit + * 9 Time Output Mode: + * U = UTC + * L = Local time + * 10 Local Time Offset (minutes) (absent on MX4200) + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,030,NNNN,FFF + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 030=Software Configuration. + * This sentence contains the navigation processor + * and baseband firmware version numbers. + * 1 Nav Processor Version Number + * 2 Baseband Firmware Version Number + * + * A typical message looks like this. Checksum has already been stripped. + * + * $PMVXG,523,M,S,M,EEEE,BBBBBB,C,R + * + * Field Field Contents + * ----- -------------- + * Block Label: $PMVXG + * Sentence Type: 523=Time Recovery Parameters Currently in Use. + * This sentence contains the configuration of the + * time recovery feature of the receiver. + * 1 Time Recovery Mode: + * D = Dynamic; solve for position and time while moving + * S = Static; solve for position and time while stationary + * K = Known position input, solve for time only + * N = No time recovery + * 2 Time Synchronization: + * U = UTC time + * G = GPS time + * 3 Time Mark Mode: + * A = Always output a time pulse + * V = Only output time pulse if time is valid (as determined + * by Maximum Time Error) + * 4 Maximum Time Error - the maximum error (in nanoseconds) for + * which a time mark will be considered valid. + * 5 User Time Bias - external bias in nanoseconds + * 6 Time Message Control: + * 0 = Do not output the time recovery message + * 1 = Output the time recovery message (record 830) to + * Control port + * 2 = Output the time recovery message (record 830) to + * Equipment port + * 7 Reserved + * 8 Position Known PRN (absent on MX 4200) + * + */ +static char * +mx4200_parse_s( + struct peer *peer + ) +{ + struct refclockproc *pp; + struct mx4200unit *up; + int sentence_type; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + sscanf ( pp->a_lastcode, "$PMVXG,%d", &sentence_type); + + /* Sentence type */ + switch (sentence_type) { + + case PMVXG_D_STATUS: + msyslog(LOG_DEBUG, + "mx4200: status: %s", pp->a_lastcode); + break; + case PMVXG_D_MODEDATA: + msyslog(LOG_DEBUG, + "mx4200: mode data: %s", pp->a_lastcode); + break; + case PMVXG_D_SOFTCONF: + msyslog(LOG_DEBUG, + "mx4200: firmware configuration: %s", pp->a_lastcode); + break; + case PMVXG_D_TRECOVUSEAGE: + msyslog(LOG_DEBUG, + "mx4200: time recovery parms: %s", pp->a_lastcode); + break; + default: + return ("wrong rec-type"); + } + + return (NULL); +} + +/* + * Process a PPS signal, placing a timestamp in pp->lastrec. + */ +static int +mx4200_pps( + struct peer *peer + ) +{ + int temp_serial; + struct refclockproc *pp; + struct mx4200unit *up; + + struct timespec timeout; + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + /* + * Grab the timestamp of the PPS signal. + */ + temp_serial = up->pps_i.assert_sequence; + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + if (time_pps_fetch(up->pps_h, PPS_TSFMT_TSPEC, &(up->pps_i), + &timeout) < 0) { + mx4200_debug(peer, + "mx4200_pps: time_pps_fetch: serial=%d, %s\n", + up->pps_i.assert_sequence, strerror(errno)); + refclock_report(peer, CEVNT_FAULT); + return(1); + } + if (temp_serial == up->pps_i.assert_sequence) { + mx4200_debug(peer, + "mx4200_pps: assert_sequence serial not incrementing: %d\n", + up->pps_i.assert_sequence); + refclock_report(peer, CEVNT_FAULT); + return(1); + } + /* + * Check pps serial number against last one + */ + if (up->lastserial + 1 != up->pps_i.assert_sequence && + up->lastserial != 0) { + if (up->pps_i.assert_sequence == up->lastserial) { + mx4200_debug(peer, "mx4200_pps: no new pps event\n"); + } else { + mx4200_debug(peer, "mx4200_pps: missed %d pps events\n", + up->pps_i.assert_sequence - up->lastserial - 1); + } + refclock_report(peer, CEVNT_FAULT); + } + up->lastserial = up->pps_i.assert_sequence; + + /* + * Return the timestamp in pp->lastrec + */ + + pp->lastrec.l_ui = up->pps_i.assert_timestamp.tv_sec + + (u_int32) JAN_1970; + pp->lastrec.l_uf = ((double)(up->pps_i.assert_timestamp.tv_nsec) * + 4.2949672960) + 0.5; + + return(0); +} + +/* + * mx4200_debug - print debug messages + */ +#if defined(__STDC__) +static void +mx4200_debug(struct peer *peer, char *fmt, ...) +#else +static void +mx4200_debug(peer, fmt, va_alist) + struct peer *peer; + char *fmt; +#endif /* __STDC__ */ +{ + va_list ap; + struct refclockproc *pp; + struct mx4200unit *up; + + if (debug) { + +#if defined(__STDC__) + va_start(ap, fmt); +#else + va_start(ap); +#endif /* __STDC__ */ + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + + /* + * Print debug message to stdout + * In the future, we may want to get get more creative... + */ + vprintf(fmt, ap); + + va_end(ap); + } +} + +/* + * Send a character string to the receiver. Checksum is appended here. + */ +#if defined(__STDC__) +static void +mx4200_send(struct peer *peer, char *fmt, ...) +#else +static void +mx4200_send(peer, fmt, va_alist) + struct peer *peer; + char *fmt; + va_dcl +#endif /* __STDC__ */ +{ + struct refclockproc *pp; + struct mx4200unit *up; + + register char *cp; + register int n, m; + va_list ap; + char buf[1024]; + u_char ck; + +#if defined(__STDC__) + va_start(ap, fmt); +#else + va_start(ap); +#endif /* __STDC__ */ + + pp = peer->procptr; + up = (struct mx4200unit *)pp->unitptr; + + cp = buf; + *cp++ = '$'; + n = VSNPRINTF((cp, sizeof(buf) - 1, fmt, ap)); + ck = mx4200_cksum(cp, n); + cp += n; + ++n; + n += SNPRINTF((cp, sizeof(buf) - n - 5, "*%02X\r\n", ck)); + + m = write(pp->io.fd, buf, (unsigned)n); + if (m < 0) + msyslog(LOG_ERR, "mx4200_send: write: %m (%s)", buf); + mx4200_debug(peer, "mx4200_send: %d %s\n", m, buf); + va_end(ap); +} + +#else +int refclock_mx4200_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_neoclock4x.c b/ntpd/refclock_neoclock4x.c new file mode 100644 index 0000000..082b1cf --- /dev/null +++ b/ntpd/refclock_neoclock4x.c @@ -0,0 +1,1068 @@ +/* + * + * Refclock_neoclock4x.c + * - NeoClock4X driver for DCF77 or FIA Timecode + * + * Date: 2003-07-07 v1.13 + * + * see http://www.linum.com/redir/jump/id=neoclock4x&action=redir + * for details about the NeoClock4X device + * + * Copyright (C) 2002-2003 by Linum Software GmbH <neoclock4x@linum.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(REFCLOCK) && (defined(CLOCK_NEOCLOCK4X)) + +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <ctype.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_control.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#if defined HAVE_SYS_MODEM_H +# include <sys/modem.h> +# define TIOCMSET MCSETA +# define TIOCMGET MCGETA +# define TIOCM_RTS MRTS +#endif + +#ifdef HAVE_TERMIOS_H +# ifdef TERMIOS_NEEDS__SVID3 +# define _SVID3 +# endif +# include <termios.h> +# ifdef TERMIOS_NEEDS__SVID3 +# undef _SVID3 +# endif +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +/* + * If you want the driver for whatever reason to not use + * the TX line to send anything to your NeoClock4X + * device you must tell the NTP refclock driver which + * firmware you NeoClock4X device uses. + * + * If you want to enable this feature change the "#if 0" + * line to "#if 1" and make sure that the defined firmware + * matches the firmware off your NeoClock4X receiver! + * + */ + +#if 0 +#define NEOCLOCK4X_FIRMWARE NEOCLOCK4X_FIRMWARE_VERSION_A +#endif + +#define NEOCLOCK4X_FIRMWARE_VERSION_A 'A' + +#define NEOCLOCK4X_TIMECODELEN 37 + +#define NEOCLOCK4X_OFFSET_SERIAL 3 +#define NEOCLOCK4X_OFFSET_RADIOSIGNAL 9 +#define NEOCLOCK4X_OFFSET_DAY 12 +#define NEOCLOCK4X_OFFSET_MONTH 14 +#define NEOCLOCK4X_OFFSET_YEAR 16 +#define NEOCLOCK4X_OFFSET_HOUR 18 +#define NEOCLOCK4X_OFFSET_MINUTE 20 +#define NEOCLOCK4X_OFFSET_SECOND 22 +#define NEOCLOCK4X_OFFSET_HSEC 24 +#define NEOCLOCK4X_OFFSET_DOW 26 +#define NEOCLOCK4X_OFFSET_TIMESOURCE 28 +#define NEOCLOCK4X_OFFSET_DSTSTATUS 29 +#define NEOCLOCK4X_OFFSET_QUARZSTATUS 30 +#define NEOCLOCK4X_OFFSET_ANTENNA1 31 +#define NEOCLOCK4X_OFFSET_ANTENNA2 33 +#define NEOCLOCK4X_OFFSET_CRC 35 + +#define NEOCLOCK4X_DRIVER_VERSION "1.12 (2003-01-10)" + +struct neoclock4x_unit { + l_fp laststamp; /* last receive timestamp */ + short unit; /* NTP refclock unit number */ + u_long polled; /* flag to detect noreplies */ + char leap_status; /* leap second flag */ + int recvnow; + + char firmware[80]; + char firmwaretag; + char serial[7]; + char radiosignal[4]; + char timesource; + char dststatus; + char quarzstatus; + int antenna1; + int antenna2; + int utc_year; + int utc_month; + int utc_day; + int utc_hour; + int utc_minute; + int utc_second; + int utc_msec; +}; + +static int neoclock4x_start P((int, struct peer *)); +static void neoclock4x_shutdown P((int, struct peer *)); +static void neoclock4x_receive P((struct recvbuf *)); +static void neoclock4x_poll P((int, struct peer *)); +static void neoclock4x_control P((int, struct refclockstat *, struct refclockstat *, struct peer *)); + +static int neol_atoi_len P((const char str[], int *, int)); +static int neol_hexatoi_len P((const char str[], int *, int)); +static void neol_jdn_to_ymd P((unsigned long, int *, int *, int *)); +static void neol_localtime P((unsigned long, int* , int*, int*, int*, int*, int*)); +static unsigned long neol_mktime P((int, int, int, int, int, int)); +#if 0 +static void neol_mdelay P((int)); +#endif +#if !defined(NEOCLOCK4X_FIRMWARE) +static int neol_query_firmware P((int, int, char *, int)); +static int neol_check_firmware P((int, const char*, char *)); +#endif + +struct refclock refclock_neoclock4x = { + neoclock4x_start, /* start up driver */ + neoclock4x_shutdown, /* shut down driver */ + neoclock4x_poll, /* transmit poll message */ + neoclock4x_control, + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +static int +neoclock4x_start(int unit, + struct peer *peer) +{ + struct neoclock4x_unit *up; + struct refclockproc *pp; + int fd; + char dev[20]; + int sl232; +#if defined(HAVE_TERMIOS) + struct termios termsettings; +#endif +#if !defined(NEOCLOCK4X_FIRMWARE) + int tries; +#endif + + (void) snprintf(dev, sizeof(dev)-1, "/dev/neoclock4x-%d", unit); + + /* LDISC_STD, LDISC_RAW + * Open serial port. Use CLK line discipline, if available. + */ + fd = refclock_open(dev, B2400, LDISC_CLK); + if(fd <= 0) + { + return (0); + } + +#if defined(HAVE_TERMIOS) + if(tcgetattr(fd, &termsettings) < 0) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): (tcgetattr) can't query serial port settings: %m", unit); + (void) close(fd); + return (0); + } + + /* 2400 Baud 8N2 */ + termsettings.c_cflag &= ~PARENB; + termsettings.c_cflag |= CSTOPB; + termsettings.c_cflag &= ~CSIZE; + termsettings.c_cflag |= CS8; + + if(tcsetattr(fd, TCSANOW, &termsettings) < 0) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): (tcsetattr) can't set serial port 2400 8N2: %m", unit); + (void) close(fd); + return (0); + } +#elif defined(HAVE_SYSV_TTYS) + if(ioctl(fd, TCGETA, &termsettings) < 0) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): (TCGETA) can't query serial port settings: %m", unit); + (void) close(fd); + return (0); + } + + /* 2400 Baud 8N2 */ + termsettings.c_cflag &= ~PARENB; + termsettings.c_cflag |= CSTOPB; + termsettings.c_cflag &= ~CSIZE; + termsettings.c_cflag |= CS8; + + if(ioctl(fd, TCSETA, &termsettings) < 0) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): (TSGETA) can't set serial port 2400 8N2: %m", unit); + (void) close(fd); + return (0); + } +#else + msyslog(LOG_EMERG, "NeoClock4X(%d): don't know how to set port to 2400 8N2 with this OS!", unit); + (void) close(fd); + return (0); +#endif + +#if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS)) + /* turn on RTS, and DTR for power supply */ + /* NeoClock4x is powered from serial line */ + if(ioctl(fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): can't query RTS/DTR state: %m", unit); + (void) close(fd); + return (0); + } +#ifdef TIOCM_RTS + sl232 = sl232 | TIOCM_DTR | TIOCM_RTS; /* turn on RTS, and DTR for power supply */ +#else + sl232 = sl232 | CIOCM_DTR | CIOCM_RTS; /* turn on RTS, and DTR for power supply */ +#endif + if(ioctl(fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): can't set RTS/DTR to power neoclock4x: %m", unit); + (void) close(fd); + return (0); + } +#else + msyslog(LOG_EMERG, "NeoClock4X(%d): don't know how to set DTR/RTS to power NeoClock4X with this OS!", + unit); + (void) close(fd); + return (0); +#endif + + up = (struct neoclock4x_unit *) emalloc(sizeof(struct neoclock4x_unit)); + if(!(up)) + { + msyslog(LOG_ERR, "NeoClock4X(%d): can't allocate memory for: %m",unit); + (void) close(fd); + return (0); + } + + memset((char *)up, 0, sizeof(struct neoclock4x_unit)); + pp = peer->procptr; + pp->clockdesc = "NeoClock4X"; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = neoclock4x_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + /* + * no fudge time is given by user! + * use 169.583333 ms to compensate the serial line delay + * formula is: + * 2400 Baud / 11 bit = 218.18 charaters per second + * (NeoClock4X timecode len) + */ + pp->fudgetime1 = (NEOCLOCK4X_TIMECODELEN * 11) / 2400.0; + + /* + * Initialize miscellaneous variables + */ + peer->precision = -10; + peer->burst = NSTAGE; + memcpy((char *)&pp->refid, "neol", 4); + + up->leap_status = 0; + up->unit = unit; + strcpy(up->firmware, "?"); + up->firmwaretag = '?'; + strcpy(up->serial, "?"); + strcpy(up->radiosignal, "?"); + up->timesource = '?'; + up->dststatus = '?'; + up->quarzstatus = '?'; + up->antenna1 = -1; + up->antenna2 = -1; + up->utc_year = 0; + up->utc_month = 0; + up->utc_day = 0; + up->utc_hour = 0; + up->utc_minute = 0; + up->utc_second = 0; + up->utc_msec = 0; + +#if defined(NEOCLOCK4X_FIRMWARE) +#if NEOCLOCK4X_FIRMWARE == NEOCLOCK4X_FIRMWARE_VERSION_A + strcpy(up->firmware, "(c) 2002 NEOL S.A. FRANCE / L0.01 NDF:A:* (compile time)"); + up->firmwaretag = 'A'; +#else + msyslog(LOG_EMERG, "NeoClock4X(%d): Unkown firmware defined at compile time for NeoClock4X", + unit); + (void) close(fd); + pp->io.fd = -1; + free(pp->unitptr); + pp->unitptr = NULL; + return (0); +#endif +#else + for(tries=0; tries < 5; tries++) + { + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, "NeoClock4X(%d): checking NeoClock4X firmware version (%d/5)", unit, tries); + /* wait 3 seconds for receiver to power up */ + sleep(3); + if(neol_query_firmware(pp->io.fd, up->unit, up->firmware, sizeof(up->firmware))) + { + break; + } + } + + /* can I handle this firmware version? */ + if(!neol_check_firmware(up->unit, up->firmware, &up->firmwaretag)) + { + (void) close(fd); + pp->io.fd = -1; + free(pp->unitptr); + pp->unitptr = NULL; + return (0); + } +#endif + + if(!io_addclock(&pp->io)) + { + msyslog(LOG_ERR, "NeoClock4X(%d): error add peer to ntpd: %m", unit); + (void) close(fd); + pp->io.fd = -1; + free(pp->unitptr); + pp->unitptr = NULL; + return (0); + } + + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, "NeoClock4X(%d): receiver setup successful done", unit); + + return (1); +} + +static void +neoclock4x_shutdown(int unit, + struct peer *peer) +{ + struct neoclock4x_unit *up; + struct refclockproc *pp; + int sl232; + + if(NULL != peer) + { + pp = peer->procptr; + if(pp != NULL) + { + up = (struct neoclock4x_unit *)pp->unitptr; + if(up != NULL) + { + if(-1 != pp->io.fd) + { +#if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS)) + /* turn on RTS, and DTR for power supply */ + /* NeoClock4x is powered from serial line */ + if(ioctl(pp->io.fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): can't query RTS/DTR state: %m", + unit); + } +#ifdef TIOCM_RTS + /* turn on RTS, and DTR for power supply */ + sl232 &= ~(TIOCM_DTR | TIOCM_RTS); +#else + /* turn on RTS, and DTR for power supply */ + sl232 &= ~(CIOCM_DTR | CIOCM_RTS); +#endif + if(ioctl(pp->io.fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): can't set RTS/DTR to power neoclock4x: %m", + unit); + } +#endif + io_closeclock(&pp->io); + } + free(up); + pp->unitptr = NULL; + } + } + } + + msyslog(LOG_ERR, "NeoClock4X(%d): shutdown", unit); + + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, "NeoClock4X(%d): receiver shutdown done", unit); +} + +static void +neoclock4x_receive(struct recvbuf *rbufp) +{ + struct neoclock4x_unit *up; + struct refclockproc *pp; + struct peer *peer; + unsigned long calc_utc; + int day; + int month; /* ddd conversion */ + int c; + int dsec; + unsigned char calc_chksum; + int recv_chksum; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct neoclock4x_unit *)pp->unitptr; + + /* wait till poll interval is reached */ + if(0 == up->recvnow) + return; + + /* reset poll interval flag */ + up->recvnow = 0; + + /* read last received timecode */ + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); + pp->leap = LEAP_NOWARNING; + + if(NEOCLOCK4X_TIMECODELEN != pp->lencode) + { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_WARNING, "NeoClock4X(%d): received data has invalid length, expected %d bytes, received %d bytes: %s", + up->unit, NEOCLOCK4X_TIMECODELEN, pp->lencode, pp->a_lastcode); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_CRC], &recv_chksum, 2); + + /* calculate checksum */ + calc_chksum = 0; + for(c=0; c < NEOCLOCK4X_OFFSET_CRC; c++) + { + calc_chksum += pp->a_lastcode[c]; + } + if(recv_chksum != calc_chksum) + { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_WARNING, "NeoClock4X(%d): received data has invalid chksum: %s", + up->unit, pp->a_lastcode); + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* Allow synchronization even is quartz clock is + * never initialized. + * WARNING: This is dangerous! + */ + up->quarzstatus = pp->a_lastcode[NEOCLOCK4X_OFFSET_QUARZSTATUS]; + if(0==(pp->sloppyclockflag & CLK_FLAG2)) + { + if('I' != up->quarzstatus) + { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_NOTICE, "NeoClock4X(%d): quartz clock is not initialized: %s", + up->unit, pp->a_lastcode); + pp->leap = LEAP_NOTINSYNC; + refclock_report(peer, CEVNT_BADDATE); + return; + } + } + if('I' != up->quarzstatus) + { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_NOTICE, "NeoClock4X(%d): using uninitialized quartz clock for time synchronization: %s", + up->unit, pp->a_lastcode); + } + + /* + * If NeoClock4X is not synchronized to a radio clock + * check if we're allowed to synchronize with the quartz + * clock. + */ + up->timesource = pp->a_lastcode[NEOCLOCK4X_OFFSET_TIMESOURCE]; + if(0==(pp->sloppyclockflag & CLK_FLAG2)) + { + if('A' != up->timesource) + { + /* not allowed to sync with quartz clock */ + if(0==(pp->sloppyclockflag & CLK_FLAG1)) + { + refclock_report(peer, CEVNT_BADTIME); + pp->leap = LEAP_NOTINSYNC; + return; + } + } + } + + /* this should only used when first install is done */ + if(pp->sloppyclockflag & CLK_FLAG4) + { + msyslog(LOG_DEBUG, "NeoClock4X(%d): received data: %s", + up->unit, pp->a_lastcode); + } + + /* 123456789012345678901234567890123456789012345 */ + /* S/N123456DCF1004021010001202ASX1213CR\r\n */ + + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_YEAR], &pp->year, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_MONTH], &month, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_DAY], &day, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_HOUR], &pp->hour, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_MINUTE], &pp->minute, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_SECOND], &pp->second, 2); + neol_atoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_HSEC], &dsec, 2); + pp->nsec = dsec * 10000; /* convert 1/100s from neoclock to nanoseconds */ + + memcpy(up->radiosignal, &pp->a_lastcode[NEOCLOCK4X_OFFSET_RADIOSIGNAL], 3); + up->radiosignal[3] = 0; + memcpy(up->serial, &pp->a_lastcode[NEOCLOCK4X_OFFSET_SERIAL], 6); + up->serial[6] = 0; + up->dststatus = pp->a_lastcode[NEOCLOCK4X_OFFSET_DSTSTATUS]; + neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_ANTENNA1], &up->antenna1, 2); + neol_hexatoi_len(&pp->a_lastcode[NEOCLOCK4X_OFFSET_ANTENNA2], &up->antenna2, 2); + + /* + Validate received values at least enough to prevent internal + array-bounds problems, etc. + */ + if((pp->hour < 0) || (pp->hour > 23) || + (pp->minute < 0) || (pp->minute > 59) || + (pp->second < 0) || (pp->second > 60) /*Allow for leap seconds.*/ || + (day < 1) || (day > 31) || + (month < 1) || (month > 12) || + (pp->year < 0) || (pp->year > 99)) { + /* Data out of range. */ + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_WARNING, "NeoClock4X(%d): date/time out of range: %s", + up->unit, pp->a_lastcode); + refclock_report(peer, CEVNT_BADDATE); + return; + } + + /* Year-2000 check not needed anymore. Same problem + * will arise at 2099 but what should we do...? + * + * wrap 2-digit date into 4-digit + * + * if(pp->year < YEAR_PIVOT) + * { + * pp->year += 100; + * } + */ + pp->year += 2000; + + /* adjust NeoClock4X local time to UTC */ + calc_utc = neol_mktime(pp->year, month, day, pp->hour, pp->minute, pp->second); + calc_utc -= 3600; + /* adjust NeoClock4X daylight saving time if needed */ + if('S' == up->dststatus) + calc_utc -= 3600; + neol_localtime(calc_utc, &pp->year, &month, &day, &pp->hour, &pp->minute, &pp->second); + + /* + some preparations + */ + pp->day = ymd2yd(pp->year, month, day); + pp->leap = 0; + + if(pp->sloppyclockflag & CLK_FLAG4) + { + msyslog(LOG_DEBUG, "NeoClock4X(%d): calculated UTC date/time: %04d-%02d-%02d %02d:%02d:%02d.%03ld", + up->unit, + pp->year, month, day, + pp->hour, pp->minute, pp->second, pp->nsec/1000); + } + + up->utc_year = pp->year; + up->utc_month = month; + up->utc_day = day; + up->utc_hour = pp->hour; + up->utc_minute = pp->minute; + up->utc_second = pp->second; + up->utc_msec = pp->nsec/1000; + + if(!refclock_process(pp)) + { + NLOG(NLOG_CLOCKEVENT) + msyslog(LOG_WARNING, "NeoClock4X(%d): refclock_process failed!", up->unit); + refclock_report(peer, CEVNT_FAULT); + return; + } + refclock_receive(peer); + + /* report good status */ + refclock_report(peer, CEVNT_NOMINAL); + + record_clock_stats(&peer->srcadr, pp->a_lastcode); +} + +static void +neoclock4x_poll(int unit, + struct peer *peer) +{ + struct neoclock4x_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct neoclock4x_unit *)pp->unitptr; + + pp->polls++; + up->recvnow = 1; +} + +static void +neoclock4x_control(int unit, + struct refclockstat *in, + struct refclockstat *out, + struct peer *peer) +{ + struct neoclock4x_unit *up; + struct refclockproc *pp; + + if(NULL == peer) + { + msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit); + return; + } + + pp = peer->procptr; + if(NULL == pp) + { + msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit); + return; + } + + up = (struct neoclock4x_unit *)pp->unitptr; + if(NULL == up) + { + msyslog(LOG_ERR, "NeoClock4X(%d): control: unit invalid/inactive", unit); + return; + } + + if(NULL != in) + { + /* check to see if a user supplied time offset is given */ + if(in->haveflags & CLK_HAVETIME1) + { + pp->fudgetime1 = in->fudgetime1; + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_NOTICE, "NeoClock4X(%d): using fudgetime1 with %0.5fs from ntp.conf.", + unit, pp->fudgetime1); + } + + /* notify */ + if(pp->sloppyclockflag & CLK_FLAG1) + { + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_NOTICE, "NeoClock4X(%d): quartz clock is used to synchronize time if radio clock has no reception.", unit); + } + else + { + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_NOTICE, "NeoClock4X(%d): time is only adjusted with radio signal reception.", unit); + } + } + + if(NULL != out) + { + static char outstatus[800]; /* status output buffer */ + char *tt; + char tmpbuf[80]; + + outstatus[0] = '\0'; + out->kv_list = (struct ctl_var *)0; + out->type = REFCLK_NEOCLOCK4X; + + snprintf(tmpbuf, sizeof(tmpbuf)-1, + "%04d-%02d-%02d %02d:%02d:%02d.%03d", + up->utc_year, up->utc_month, up->utc_day, + up->utc_hour, up->utc_minute, up->utc_second, + up->utc_msec); + tt = add_var(&out->kv_list, sizeof(tmpbuf)-1, RO|DEF); + snprintf(tt, sizeof(tmpbuf)-1, "calc_utc=\"%s\"", tmpbuf); + + tt = add_var(&out->kv_list, 40, RO|DEF); + snprintf(tt, 39, "radiosignal=\"%s\"", up->radiosignal); + tt = add_var(&out->kv_list, 40, RO|DEF); + snprintf(tt, 39, "antenna1=\"%d\"", up->antenna1); + tt = add_var(&out->kv_list, 40, RO|DEF); + snprintf(tt, 39, "antenna2=\"%d\"", up->antenna2); + tt = add_var(&out->kv_list, 40, RO|DEF); + if('A' == up->timesource) + snprintf(tt, 39, "timesource=\"radio\""); + else if('C' == up->timesource) + snprintf(tt, 39, "timesource=\"quartz\""); + else + snprintf(tt, 39, "timesource=\"unknown\""); + tt = add_var(&out->kv_list, 40, RO|DEF); + if('I' == up->quarzstatus) + snprintf(tt, 39, "quartzstatus=\"synchronized\""); + else if('X' == up->quarzstatus) + snprintf(tt, 39, "quartzstatus=\"not synchronized\""); + else + snprintf(tt, 39, "quartzstatus=\"unknown\""); + tt = add_var(&out->kv_list, 40, RO|DEF); + if('S' == up->dststatus) + snprintf(tt, 39, "dststatus=\"summer\""); + else if('W' == up->dststatus) + snprintf(tt, 39, "dststatus=\"winter\""); + else + snprintf(tt, 39, "dststatus=\"unknown\""); + tt = add_var(&out->kv_list, 80, RO|DEF); + snprintf(tt, 79, "firmware=\"%s\"", up->firmware); + tt = add_var(&out->kv_list, 40, RO|DEF); + snprintf(tt, 39, "firmwaretag=\"%c\"", up->firmwaretag); + tt = add_var(&out->kv_list, 80, RO|DEF); + snprintf(tt, 79, "driver version=\"%s\"", NEOCLOCK4X_DRIVER_VERSION); + tt = add_var(&out->kv_list, 80, RO|DEF); + snprintf(tt, 79, "serialnumber=\"%s\"", up->serial); + } +} + +static int +neol_hexatoi_len(const char str[], + int *result, + int maxlen) +{ + int hexdigit; + int i; + int n = 0; + + for(i=0; isxdigit(str[i]) && i < maxlen; i++) + { + hexdigit = isdigit(str[i]) ? toupper(str[i]) - '0' : toupper(str[i]) - 'A' + 10; + n = 16 * n + hexdigit; + } + *result = n; + return (n); +} + +static int +neol_atoi_len(const char str[], + int *result, + int maxlen) +{ + int digit; + int i; + int n = 0; + + for(i=0; isdigit(str[i]) && i < maxlen; i++) + { + digit = str[i] - '0'; + n = 10 * n + digit; + } + *result = n; + return (n); +} + +/* Converts Gregorian date to seconds since 1970-01-01 00:00:00. + * Assumes input in normal date format, i.e. 1980-12-31 23:59:59 + * => year=1980, mon=12, day=31, hour=23, min=59, sec=59. + * + * [For the Julian calendar (which was used in Russia before 1917, + * Britain & colonies before 1752, anywhere else before 1582, + * and is still in use by some communities) leave out the + * -year/100+year/400 terms, and add 10.] + * + * This algorithm was first published by Gauss (I think). + * + * WARNING: this function will overflow on 2106-02-07 06:28:16 on + * machines were long is 32-bit! (However, as time_t is signed, we + * will already get problems at other places on 2038-01-19 03:14:08) + */ +static unsigned long +neol_mktime(int year, + int mon, + int day, + int hour, + int min, + int sec) +{ + if (0 >= (int) (mon -= 2)) { /* 1..12 . 11,12,1..10 */ + mon += 12; /* Puts Feb last since it has leap day */ + year -= 1; + } + return ((( + (unsigned long)(year/4 - year/100 + year/400 + 367*mon/12 + day) + + year*365 - 719499 + )*24 + hour /* now have hours */ + )*60 + min /* now have minutes */ + )*60 + sec; /* finally seconds */ +} + +static void +neol_localtime(unsigned long utc, + int* year, + int* month, + int* day, + int* hour, + int* min, + int* sec) +{ + *sec = utc % 60; + utc /= 60; + *min = utc % 60; + utc /= 60; + *hour = utc % 24; + utc /= 24; + + /* JDN Date 1/1/1970 */ + neol_jdn_to_ymd(utc + 2440588L, year, month, day); +} + +static void +neol_jdn_to_ymd(unsigned long jdn, + int *yy, + int *mm, + int *dd) +{ + unsigned long x, z, m, d, y; + unsigned long daysPer400Years = 146097UL; + unsigned long fudgedDaysPer4000Years = 1460970UL + 31UL; + + x = jdn + 68569UL; + z = 4UL * x / daysPer400Years; + x = x - (daysPer400Years * z + 3UL) / 4UL; + y = 4000UL * (x + 1) / fudgedDaysPer4000Years; + x = x - 1461UL * y / 4UL + 31UL; + m = 80UL * x / 2447UL; + d = x - 2447UL * m / 80UL; + x = m / 11UL; + m = m + 2UL - 12UL * x; + y = 100UL * (z - 49UL) + y + x; + + *yy = (int)y; + *mm = (int)m; + *dd = (int)d; +} + +#if 0 +/* + * delay in milliseconds + */ +static void +neol_mdelay(int milliseconds) +{ + struct timeval tv; + + if(milliseconds) + { + tv.tv_sec = 0; + tv.tv_usec = milliseconds * 1000; + select(1, NULL, NULL, NULL, &tv); + } +} +#endif + +#if !defined(NEOCLOCK4X_FIRMWARE) +static int +neol_query_firmware(int fd, + int unit, + char *firmware, + int maxlen) +{ + char tmpbuf[256]; + int len; + int lastsearch; + unsigned char c; + int last_c_was_crlf; + int last_crlf_conv_len; + int init; + int read_errors; + int flag = 0; + int chars_read; + + /* wait a little bit */ + sleep(1); + if(-1 != write(fd, "V", 1)) + { + /* wait a little bit */ + sleep(1); + memset(tmpbuf, 0x00, sizeof(tmpbuf)); + + len = 0; + lastsearch = 0; + last_c_was_crlf = 0; + last_crlf_conv_len = 0; + init = 1; + read_errors = 0; + chars_read = 0; + for(;;) + { + if(read_errors > 5) + { + msyslog(LOG_ERR, "NeoClock4X(%d): can't read firmware version (timeout)", unit); + strcpy(tmpbuf, "unknown due to timeout"); + break; + } + if(chars_read > 500) + { + msyslog(LOG_ERR, "NeoClock4X(%d): can't read firmware version (garbage)", unit); + strcpy(tmpbuf, "unknown due to garbage input"); + break; + } + if(-1 == read(fd, &c, 1)) + { + if(EAGAIN != errno) + { + msyslog(LOG_DEBUG, "NeoClock4x(%d): read: %s", unit ,strerror(errno)); + read_errors++; + } + else + { + sleep(1); + } + continue; + } + else + { + chars_read++; + } + + if(init) + { + if(0xA9 != c) /* wait for (c) char in input stream */ + continue; + + strcpy(tmpbuf, "(c)"); + len = 3; + init = 0; + continue; + } + +#if 0 + msyslog(LOG_NOTICE, "NeoClock4X(%d): firmware %c = %02Xh", unit, c, c); +#endif + + if(0x0A == c || 0x0D == c) + { + if(last_c_was_crlf) + { + char *ptr; + ptr = strstr(&tmpbuf[lastsearch], "S/N"); + if(NULL != ptr) + { + tmpbuf[last_crlf_conv_len] = 0; + flag = 1; + break; + } + /* convert \n to / */ + last_crlf_conv_len = len; + tmpbuf[len++] = ' '; + tmpbuf[len++] = '/'; + tmpbuf[len++] = ' '; + lastsearch = len; + } + last_c_was_crlf = 1; + } + else + { + last_c_was_crlf = 0; + if(0x00 != c) + tmpbuf[len++] = (char) c; + } + tmpbuf[len] = '\0'; + if(len > sizeof(tmpbuf)-5) + break; + } + } + else + { + msyslog(LOG_ERR, "NeoClock4X(%d): can't query firmware version", unit); + strcpy(tmpbuf, "unknown error"); + } + strncpy(firmware, tmpbuf, maxlen); + firmware[maxlen] = '\0'; + + if(flag) + { + NLOG(NLOG_CLOCKINFO) + msyslog(LOG_INFO, "NeoClock4X(%d): firmware version: %s", unit, firmware); + } + + return (flag); +} + +static int +neol_check_firmware(int unit, + const char *firmware, + char *firmwaretag) +{ + char *ptr; + + *firmwaretag = '?'; + ptr = strstr(firmware, "NDF:"); + if(NULL != ptr) + { + if((strlen(firmware) - strlen(ptr)) >= 7) + { + if(':' == *(ptr+5) && '*' == *(ptr+6)) + *firmwaretag = *(ptr+4); + } + } + + if('A' != *firmwaretag) + { + msyslog(LOG_CRIT, "NeoClock4X(%d): firmware version \"%c\" not supported with this driver version!", unit, *firmwaretag); + return (0); + } + + return (1); +} +#endif + +#else +int refclock_neoclock4x_bs; +#endif /* REFCLOCK */ + +/* + * History: + * refclock_neoclock4x.c + * + * 2002/04/27 cjh + * Revision 1.0 first release + * + * 2002/07/15 cjh + * preparing for bitkeeper reposity + * + * 2002/09/09 cjh + * Revision 1.1 + * - don't assume sprintf returns an int anymore + * - change the way the firmware version is read + * - some customers would like to put a device called + * data diode to the NeoClock4X device to disable + * the write line. We need to now the firmware + * version even in this case. We made a compile time + * definition in this case. The code was previously + * only available on request. + * + * 2003/01/08 cjh + * Revision 1.11 + * - changing xprinf to xnprinf to avoid buffer overflows + * - change some logic + * - fixed memory leaks if drivers can't initialize + * + * 2003/01/10 cjh + * Revision 1.12 + * - replaced ldiv + * - add code to support FreeBSD + * + * 2003/07/07 cjh + * Revision 1.13 + * - fix reporting of clock status + * changes. previously a bad clock + * status was never reset. + */ diff --git a/ntpd/refclock_nmea.c b/ntpd/refclock_nmea.c new file mode 100644 index 0000000..28d6263 --- /dev/null +++ b/ntpd/refclock_nmea.c @@ -0,0 +1,723 @@ +/* + * refclock_nmea.c - clock driver for an NMEA GPS CLOCK + * Michael Petry Jun 20, 1994 + * based on refclock_heathn.c + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(SYS_WINNT) +#undef close +#define close closesocket +#endif + +#if defined(REFCLOCK) && defined(CLOCK_NMEA) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_PPSAPI +# ifdef HAVE_TIMEPPS_H +# include <timepps.h> +# else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +# endif +#endif /* HAVE_PPSAPI */ + +/* + * This driver supports the NMEA GPS Receiver with + * + * Protype was refclock_trak.c, Thanks a lot. + * + * The receiver used spits out the NMEA sentences for boat navigation. + * And you thought it was an information superhighway. Try a raging river + * filled with rapids and whirlpools that rip away your data and warp time. + * + * If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in. + * On startup if initialization of the PPSAPI fails, it will fall back + * to the "normal" timestamps. + * + * The PPSAPI part of the driver understands fudge flag2 and flag3. If + * flag2 is set, it will use the clear edge of the pulse. If flag3 is + * set, kernel hardpps is enabled. + * + * GPS sentences other than RMC (the default) may be enabled by setting + * the relevent bits of 'mode' in the server configuration line + * server 127.127.20.x mode X + * + * bit 0 - enables RMC (1) + * bit 1 - enables GGA (2) + * bit 2 - enables GLL (4) + * multiple sentences may be selected + */ + +/* + * Definitions + */ +#ifdef SYS_WINNT +# define DEVICE "COM%d:" /* COM 1 - 3 supported */ +#else +# define DEVICE "/dev/gps%d" /* name of radio device */ +#endif +#define SPEED232 B4800 /* uart speed (4800 bps) */ +#define PRECISION (-9) /* precision assumed (about 2 ms) */ +#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference id */ +#define DESCRIPTION "NMEA GPS Clock" /* who we are */ +#define NANOSECOND 1000000000 /* one second (ns) */ +#define RANGEGATE 500000 /* range gate (ns) */ + +#define LENNMEA 75 /* min timecode length */ + +/* + * Tables to compute the ddd of year form icky dd/mm timecode. Viva la + * leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* + * Unit control structure + */ +struct nmeaunit { + int pollcnt; /* poll message counter */ + int polled; /* Hand in a sample? */ + l_fp tstamp; /* timestamp of last poll */ +#ifdef HAVE_PPSAPI + struct timespec ts; /* last timestamp */ + pps_params_t pps_params; /* pps parameters */ + pps_info_t pps_info; /* last pps data */ + pps_handle_t handle; /* pps handlebars */ +#endif /* HAVE_PPSAPI */ +}; + +/* + * Function prototypes + */ +static int nmea_start P((int, struct peer *)); +static void nmea_shutdown P((int, struct peer *)); +#ifdef HAVE_PPSAPI +static void nmea_control P((int, struct refclockstat *, struct + refclockstat *, struct peer *)); +static int nmea_ppsapi P((struct peer *, int, int)); +static int nmea_pps P((struct nmeaunit *, l_fp *)); +#endif /* HAVE_PPSAPI */ +static void nmea_receive P((struct recvbuf *)); +static void nmea_poll P((int, struct peer *)); +static void gps_send P((int, const char *, struct peer *)); +static char *field_parse P((char *, int)); + +/* + * Transfer vector + */ +struct refclock refclock_nmea = { + nmea_start, /* start up driver */ + nmea_shutdown, /* shut down driver */ + nmea_poll, /* transmit poll message */ +#ifdef HAVE_PPSAPI + nmea_control, /* fudge control */ +#else + noentry, /* fudge control */ +#endif /* HAVE_PPSAPI */ + noentry, /* initialize driver */ + noentry, /* buginfo */ + NOFLAGS /* not used */ +}; + +/* + * nmea_start - open the GPS devices and initialize data for processing + */ +static int +nmea_start( + int unit, + struct peer *peer + ) +{ + register struct nmeaunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + + fd = refclock_open(device, SPEED232, LDISC_CLK); + if (fd < 0) + return (0); + + /* + * Allocate and initialize unit structure + */ + up = (struct nmeaunit *)emalloc(sizeof(struct nmeaunit)); + if (up == NULL) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct nmeaunit)); + pp = peer->procptr; + pp->io.clock_recv = nmea_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->pollcnt = 2; + gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer); + +#ifdef HAVE_PPSAPI + /* + * Start the PPSAPI interface if it is there. Default to use + * the assert edge and do not enable the kernel hardpps. + */ + if (time_pps_create(fd, &up->handle) < 0) { + up->handle = 0; + msyslog(LOG_ERR, + "refclock_nmea: time_pps_create failed: %m"); + return (1); + } + return(nmea_ppsapi(peer, 0, 0)); +#else + return (1); +#endif /* HAVE_PPSAPI */ +} + +/* + * nmea_shutdown - shut down a GPS clock + */ +static void +nmea_shutdown( + int unit, + struct peer *peer + ) +{ + register struct nmeaunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct nmeaunit *)pp->unitptr; +#ifdef HAVE_PPSAPI + if (up->handle != 0) + time_pps_destroy(up->handle); +#endif /* HAVE_PPSAPI */ + io_closeclock(&pp->io); + free(up); +} + +#ifdef HAVE_PPSAPI +/* + * nmea_control - fudge control + */ +static void +nmea_control( + int unit, /* unit (not used */ + struct refclockstat *in, /* input parameters (not uded) */ + struct refclockstat *out, /* output parameters (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2, + pp->sloppyclockflag & CLK_FLAG3); +} + + +/* + * Initialize PPSAPI + */ +int +nmea_ppsapi( + struct peer *peer, /* peer structure pointer */ + int enb_clear, /* clear enable */ + int enb_hardpps /* hardpps enable */ + ) +{ + struct refclockproc *pp; + struct nmeaunit *up; + int capability; + + pp = peer->procptr; + up = (struct nmeaunit *)pp->unitptr; + if (time_pps_getcap(up->handle, &capability) < 0) { + msyslog(LOG_ERR, + "refclock_nmea: time_pps_getcap failed: %m"); + return (0); + } + memset(&up->pps_params, 0, sizeof(pps_params_t)); + if (enb_clear) + up->pps_params.mode = capability & PPS_CAPTURECLEAR; + else + up->pps_params.mode = capability & PPS_CAPTUREASSERT; + if (!up->pps_params.mode) { + msyslog(LOG_ERR, + "refclock_nmea: invalid capture edge %d", + !enb_clear); + return (0); + } + up->pps_params.mode |= PPS_TSFMT_TSPEC; + if (time_pps_setparams(up->handle, &up->pps_params) < 0) { + msyslog(LOG_ERR, + "refclock_nmea: time_pps_setparams failed: %m"); + return (0); + } + if (enb_hardpps) { + if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS, + up->pps_params.mode & ~PPS_TSFMT_TSPEC, + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, + "refclock_nmea: time_pps_kcbind failed: %m"); + return (0); + } + pps_enable = 1; + } + peer->precision = PPS_PRECISION; + +#if DEBUG + if (debug) { + time_pps_getparams(up->handle, &up->pps_params); + printf( + "refclock_ppsapi: capability 0x%x version %d mode 0x%x kern %d\n", + capability, up->pps_params.api_version, + up->pps_params.mode, enb_hardpps); + } +#endif + + return (1); +} + +/* + * Get PPSAPI timestamps. + * + * Return 0 on failure and 1 on success. + */ +static int +nmea_pps( + struct nmeaunit *up, + l_fp *tsptr + ) +{ + pps_info_t pps_info; + struct timespec timeout, ts; + double dtemp; + l_fp tstmp; + + /* + * Convert the timespec nanoseconds field to ntp l_fp units. + */ + if (up->handle == 0) + return (0); + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t)); + if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info, + &timeout) < 0) + return (0); + if (up->pps_params.mode & PPS_CAPTUREASSERT) { + if (pps_info.assert_sequence == + up->pps_info.assert_sequence) + return (0); + ts = up->pps_info.assert_timestamp; + } else if (up->pps_params.mode & PPS_CAPTURECLEAR) { + if (pps_info.clear_sequence == + up->pps_info.clear_sequence) + return (0); + ts = up->pps_info.clear_timestamp; + } else { + return (0); + } + if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec)) + return (0); + up->ts = ts; + + tstmp.l_ui = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec * FRAC / 1e9; + tstmp.l_uf = (u_int32)dtemp; + *tsptr = tstmp; + return (1); +} +#endif /* HAVE_PPSAPI */ + +/* + * nmea_receive - receive data from the serial interface + */ +static void +nmea_receive( + struct recvbuf *rbufp + ) +{ + register struct nmeaunit *up; + struct refclockproc *pp; + struct peer *peer; + int month, day; + int i; + char *cp, *dp; + int cmdtype; + /* Use these variables to hold data until we decide its worth keeping */ + char rd_lastcode[BMAX]; + l_fp rd_tmp; + u_short rd_lencode; + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct nmeaunit *)pp->unitptr; + rd_lencode = (u_short)refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); + + /* + * There is a case that a <CR><LF> gives back a "blank" line + */ + if (rd_lencode == 0) + return; + +#ifdef DEBUG + if (debug) + printf("nmea: gpsread %d %s\n", rd_lencode, + rd_lastcode); +#endif + + /* + * We check the timecode format and decode its contents. The + * we only care about a few of them. The most important being + * the $GPRMC format + * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC + * For Magellan (ColorTrak) GLL probably datum (order of sentences) + * also mode (0,1,2,3) select sentence ANY/ALL, RMC, GGA, GLL + * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21 + * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F + * $GPRMB,... + * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 + * $GPAPB,... + * $GPGSA,... + * $GPGSV,... + * $GPGSV,... + */ +#define GPXXX 0 +#define GPRMC 1 +#define GPGGA 2 +#define GPGLL 4 + cp = rd_lastcode; + cmdtype=0; + if(strncmp(cp,"$GPRMC",6)==0) { + cmdtype=GPRMC; + } + else if(strncmp(cp,"$GPGGA",6)==0) { + cmdtype=GPGGA; + } + else if(strncmp(cp,"$GPGLL",6)==0) { + cmdtype=GPGLL; + } + else if(strncmp(cp,"$GPXXX",6)==0) { + cmdtype=GPXXX; + } + else + return; + + + /* See if I want to process this message type */ + if ( ((peer->ttl == 0) && (cmdtype != GPRMC)) + || ((peer->ttl != 0) && !(cmdtype & peer->ttl)) ) + return; + + pp->lencode = rd_lencode; + strcpy(pp->a_lastcode,rd_lastcode); + cp = pp->a_lastcode; + + pp->lastrec = up->tstamp = rd_tmp; + up->pollcnt = 2; + +#ifdef DEBUG + if (debug) + printf("nmea: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + + /* Grab field depending on clock string type */ + switch( cmdtype ) { + case GPRMC: + /* + * Test for synchronization. Check for quality byte. + */ + dp = field_parse(cp,2); + if( dp[0] != 'A') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + + /* Now point at the time field */ + dp = field_parse(cp,1); + break; + + + case GPGGA: + /* + * Test for synchronization. Check for quality byte. + */ + dp = field_parse(cp,6); + if( dp[0] == '0') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + + /* Now point at the time field */ + dp = field_parse(cp,1); + break; + + + case GPGLL: + /* + * Test for synchronization. Check for quality byte. + */ + dp = field_parse(cp,6); + if( dp[0] != 'A') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + + /* Now point at the time field */ + dp = field_parse(cp,5); + break; + + + case GPXXX: + return; + default: + return; + + } + + /* + * Check time code format of NMEA + */ + + if( !isdigit((int)dp[0]) || + !isdigit((int)dp[1]) || + !isdigit((int)dp[2]) || + !isdigit((int)dp[3]) || + !isdigit((int)dp[4]) || + !isdigit((int)dp[5]) + ) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + + /* + * Convert time and check values. + */ + pp->hour = ((dp[0] - '0') * 10) + dp[1] - '0'; + pp->minute = ((dp[2] - '0') * 10) + dp[3] - '0'; + pp->second = ((dp[4] - '0') * 10) + dp[5] - '0'; + /* Default to 0 milliseconds, if decimal convert milliseconds in + one, two or three digits + */ + pp->nsec = 0; + if (dp[6] == '.') { + if (isdigit((int)dp[7])) { + pp->nsec = (dp[7] - '0') * 100000000; + if (isdigit((int)dp[8])) { + pp->nsec += (dp[8] - '0') * 10000000; + if (isdigit((int)dp[9])) { + pp->nsec += (dp[9] - '0') * 1000000; + } + } + } + } + + if (pp->hour > 23 || pp->minute > 59 || pp->second > 59 + || pp->nsec > 1000000000) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + + /* + * Convert date and check values. + */ + if (cmdtype==GPRMC) { + dp = field_parse(cp,9); + day = dp[0] - '0'; + day = (day * 10) + dp[1] - '0'; + month = dp[2] - '0'; + month = (month * 10) + dp[3] - '0'; + pp->year = dp[4] - '0'; + pp->year = (pp->year * 10) + dp[5] - '0'; + } + else { + /* only time */ + time_t tt = time(NULL); + struct tm * t = gmtime(&tt); + day = t->tm_mday; + month = t->tm_mon + 1; + pp->year= t->tm_year; + } + + if (month < 1 || month > 12 || day < 1) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* Hmmmm this will be a nono for 2100,2200,2300 but I don't think I'll be here */ + /* good thing that 2000 is a leap year */ + /* pp->year will be 00-99 if read from GPS, 00-> (years since 1900) from tm_year */ + if (pp->year % 4) { + if (day > day1tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } else { + if (day > day2tab[month - 1]) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } + pp->day = day; + + +#ifdef HAVE_PPSAPI + /* + * If the PPSAPI is working, rather use its timestamps. + * assume that the PPS occurs on the second so blow any msec + */ + if (nmea_pps(up, &rd_tmp) == 1) { + pp->lastrec = up->tstamp = rd_tmp; + pp->nsec = 0; + } +#endif /* HAVE_PPSAPI */ + + /* + * Process the new sample in the median filter and determine the + * reference clock offset and dispersion. We use lastrec as both + * the reference time and receive time, in order to avoid being + * cute, like setting the reference time later than the receive + * time, which may cause a paranoid protocol module to chuck out + * the data. + */ + + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + + + /* + * Only go on if we had been polled. + */ + if (!up->polled) + return; + up->polled = 0; + pp->lastref = pp->lastrec; + refclock_receive(peer); + + /* If we get here - what we got from the clock is OK, so say so */ + refclock_report(peer, CEVNT_NOMINAL); + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + +} + +/* + * nmea_poll - called by the transmit procedure + * + * We go to great pains to avoid changing state here, since there may be + * more than one eavesdropper receiving the same timecode. + */ +static void +nmea_poll( + int unit, + struct peer *peer + ) +{ + register struct nmeaunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct nmeaunit *)pp->unitptr; + if (up->pollcnt == 0) + refclock_report(peer, CEVNT_TIMEOUT); + else + up->pollcnt--; + pp->polls++; + up->polled = 1; + + /* + * usually nmea_receive can get a timestamp every second + */ + + gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer); +} + +/* + * + * gps_send(fd,cmd, peer) Sends a command to the GPS receiver. + * as gps_send(fd,"rqts,u\r", peer); + * + * We don't currently send any data, but would like to send + * RTCM SC104 messages for differential positioning. It should + * also give us better time. Without a PPS output, we're + * Just fooling ourselves because of the serial code paths + * + */ +static void +gps_send( + int fd, + const char *cmd, + struct peer *peer + ) +{ + + if (write(fd, cmd, strlen(cmd)) == -1) { + refclock_report(peer, CEVNT_FAULT); + } +} + +static char * +field_parse( + char *cp, + int fn + ) +{ + char *tp; + int i = fn; + + for (tp = cp; *tp != '\0'; tp++) { + if (*tp == ',') + i--; + if (i == 0) + break; + } + return (++tp); +} +#else +int refclock_nmea_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_oncore.c b/ntpd/refclock_oncore.c new file mode 100644 index 0000000..14db92f --- /dev/null +++ b/ntpd/refclock_oncore.c @@ -0,0 +1,3723 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.ORG> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * refclock_oncore.c + * + * Driver for some of the various the Motorola Oncore GPS receivers. + * should work with Basic, PVT6, VP, UT, UT+, GT, GT+, SL, M12, M12+T + * The receivers with TRAIM (VP, UT, UT+, M12+T), will be more accurate + * than the others. + * The receivers without position hold (GT, GT+) will be less accurate. + * + * Tested with: + * + * (UT) (VP) + * COPYRIGHT 1991-1997 MOTOROLA INC. COPYRIGHT 1991-1996 MOTOROLA INC. + * SFTW P/N # 98-P36848P SFTW P/N # 98-P36830P + * SOFTWARE VER # 2 SOFTWARE VER # 8 + * SOFTWARE REV # 2 SOFTWARE REV # 8 + * SOFTWARE DATE APR 24 1998 SOFTWARE DATE 06 Aug 1996 + * MODEL # R1121N1114 MODEL # B4121P1155 + * HWDR P/N # 1 HDWR P/N # _ + * SERIAL # R0010A SERIAL # SSG0226478 + * MANUFACTUR DATE 6H07 MANUFACTUR DATE 7E02 + * OPTIONS LIST IB + * + * (Basic) (M12) + * COPYRIGHT 1991-1994 MOTOROLA INC. COPYRIGHT 1991-2000 MOTOROLA INC. + * SFTW P/N # 98-P39949M SFTW P/N # 61-G10002A + * SOFTWARE VER # 5 SOFTWARE VER # 1 + * SOFTWARE REV # 0 SOFTWARE REV # 3 + * SOFTWARE DATE 20 JAN 1994 SOFTWARE DATE Mar 13 2000 + * MODEL # A11121P116 MODEL # P143T12NR1 + * HDWR P/N # _ HWDR P/N # 1 + * SERIAL # SSG0049809 SERIAL # P003UD + * MANUFACTUR DATE 417AMA199 MANUFACTUR DATE 0C27 + * OPTIONS LIST AB + * + * -------------------------------------------------------------------------- + * This code uses the two devices + * /dev/oncore.serial.n + * /dev/oncore.pps.n + * which may be linked to the same device. + * and can read initialization data from the file + * /etc/ntp.oncoreN, /etc/ntp.oncore.N, or /etc/ntp.oncore, where + * n or N are the unit number, viz 127.127.30.N. + * -------------------------------------------------------------------------- + * Reg.Clemens <reg@dwf.com> Sep98. + * Original code written for FreeBSD. + * With these mods it works on FreeBSD, SunOS, Solaris and Linux + * (SunOS 4.1.3 + ppsclock) + * (Solaris7 + MU4) + * (RedHat 5.1 2.0.35 + PPSKit, 2.1.126 + or later). + * + * Lat,Long,Ht, cable-delay, offset, and the ReceiverID (along with the + * state machine state) are printed to CLOCKSTATS if that file is enabled + * in /etc/ntp.conf. + * + * -------------------------------------------------------------------------- + * + * According to the ONCORE manual (TRM0003, Rev 3.2, June 1998, page 3.13) + * doing an average of 10000 valid 2D and 3D fixes is what the automatic + * site survey mode does. Looking at the output from the receiver + * it seems like it is only using 3D fixes. + * When we do it ourselves, take 10000 3D fixes. + */ + +#define POS_HOLD_AVERAGE 10000 /* nb, 10000s ~= 2h45m */ + +/* + * ONCORE_SHMEM_STATUS will create a mmap(2)'ed file named according to a + * "STATUS" line in the oncore config file, which contains the most recent + * copy of all types of messages we recognize. This file can be mmap(2)'ed + * by monitoring and statistics programs. + * + * See separate HTML documentation for this option. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_ONCORE) && defined(HAVE_PPSAPI) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> +#include <sys/stat.h> +#ifdef ONCORE_SHMEM_STATUS +# ifdef HAVE_SYS_MMAN_H +# include <sys/mman.h> +# ifndef MAP_FAILED +# define MAP_FAILED ((u_char *) -1) +# endif /* not MAP_FAILED */ +# endif /* HAVE_SYS_MMAN_H */ +#endif /* ONCORE_SHMEM_STATUS */ + +#ifdef HAVE_PPSAPI +# ifdef HAVE_TIMEPPS_H +# include <timepps.h> +# else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +# endif +#endif + +#ifdef HAVE_SYS_SIO_H +# include <sys/sio.h> +#endif + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif + +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +#ifndef HAVE_STRUCT_PPSCLOCKEV +struct ppsclockev { +# ifdef HAVE_STRUCT_TIMESPEC + struct timespec tv; +# else + struct timeval tv; +# endif + u_int serial; +}; +#endif /* not HAVE_STRUCT_PPSCLOCKEV */ + +enum receive_state { + ONCORE_NO_IDEA, + ONCORE_CHECK_ID, + ONCORE_CHECK_CHAN, + ONCORE_HAVE_CHAN, + ONCORE_RESET_SENT, + ONCORE_TEST_SENT, + ONCORE_INIT, + ONCORE_ALMANAC, + ONCORE_RUN +}; + +enum site_survey_state { + ONCORE_SS_UNKNOWN, + ONCORE_SS_TESTING, + ONCORE_SS_HW, + ONCORE_SS_SW, + ONCORE_SS_DONE +}; + +enum antenna_state { + ONCORE_ANTENNA_UNKNOWN = -1, + ONCORE_ANTENNA_OK = 0, + ONCORE_ANTENNA_OC = 1, + ONCORE_ANTENNA_UC = 2, + ONCORE_ANTENNA_NV = 3 +}; + +/* Model Name, derived from the @@Cj message. + * Used to initialize some variables. + */ + +enum oncore_model { + ONCORE_BASIC, + ONCORE_PVT6, + ONCORE_VP, + ONCORE_UT, + ONCORE_UTPLUS, + ONCORE_GT, + ONCORE_GTPLUS, + ONCORE_SL, + ONCORE_M12, + ONCORE_UNKNOWN +}; + +/* the bits that describe these properties are in the same place + * on the VP/UT, but have moved on the M12. As such we extract + * them, and use them from this struct. + * + */ + +struct RSM { + u_char posn0D; + u_char posn2D; + u_char posn3D; + u_char bad_almanac; + u_char bad_fix; +}; + +/* It is possible to test the VP/UT each cycle (@@Ea or equivalent) to + * see what mode it is in. The bits on the M12 are multiplexed with + * other messages, so we have to 'keep' the last known mode here. + */ + +enum posn_mode { + MODE_UNKNOWN, + MODE_0D, + MODE_2D, + MODE_3D +}; + +struct instance { + int unit; /* 127.127.30.unit */ + struct refclockproc *pp; + struct peer *peer; + + int ttyfd; /* TTY file descriptor */ + int ppsfd; /* PPS file descriptor */ + int shmemfd; /* Status shm descriptor */ +#ifdef HAVE_PPSAPI + pps_handle_t pps_h; + pps_params_t pps_p; +#endif + enum receive_state o_state; /* Receive state */ + enum posn_mode mode; /* 0D, 2D, 3D */ + enum site_survey_state site_survey; /* Site Survey state */ + enum antenna_state ant_state; /* antenna state */ + + int Bj_day; + + u_long delay; /* ns */ + long offset; /* ns */ + + u_char *shmem; + char *shmem_fname; + u_int shmem_Cb; + u_int shmem_Ba; + u_int shmem_Ea; + u_int shmem_Ha; + u_char shmem_reset; + u_char shmem_Posn; + u_char shmem_bad_Ea; + u_char almanac_from_shmem; + + double ss_lat; + double ss_long; + double ss_ht; + double dH; + int ss_count; + u_char posn_set; + + enum oncore_model model; + u_int version; + u_int revision; + + u_char chan; /* 6 for PVT6 or BASIC, 8 for UT/VP, 12 for m12, 0 if unknown */ + s_char traim; /* do we have traim? yes UT/VP, no BASIC, GT, M12+T, -1 unknown, 0 no, +1 yes */ + + /* the following 7 are all timing counters */ + u_char traim_delay; /* seconds counter, waiting for reply */ + u_char count; /* cycles thru Ea before starting */ + u_char count1; /* cycles thru Ea after SS_TESTING, waiting for SS_HW */ + u_char count2; /* cycles thru Ea after count, to check for @@Ea */ + u_char count3; /* cycles thru Ea checking for # channels */ + u_char count4; /* cycles thru leap after Gj to issue Bj */ + u_char pollcnt; + u_char timeout; /* count to retry Cj after Fa self-test */ + + struct RSM rsm; /* bits extracted from Receiver Status Msg in @@Ea */ + u_char printed; + u_char polled; + u_long ev_serial; + int Rcvptr; + u_char Rcvbuf[500]; + u_char BEHa[160]; /* Ba, Ea or Ha */ + u_char BEHn[80]; /* Bn , En , or Hn */ + u_char Cj[300]; + u_char Ag; /* Satellite mask angle */ + u_char saw_At; + u_char saw_Ay; + u_char saw_Az; + s_char saw_Gj; + u_char have_dH; + u_char init_type; + s_char saw_tooth; + s_char chan_in; /* chan number from INPUT, will always use it */ + u_char chan_id; /* chan number determined from part number */ + u_char chan_ck; /* chan number determined by sending commands to hardware */ + s_char traim_in; /* TRAIM from INPUT, will always use it */ + s_char traim_id; /* TRAIM determined from part number */ + u_char traim_ck; /* TRAIM determined by sending commands to hardware */ + u_char once; /* one pass code at top of BaEaHa */ + s_char assert; + u_char hardpps; +}; + +#define rcvbuf instance->Rcvbuf +#define rcvptr instance->Rcvptr + +static int oncore_start P((int, struct peer *)); +static void oncore_control P((int, struct refclockstat *, struct refclockstat *, struct peer *)); +static void oncore_poll P((int, struct peer *)); +static void oncore_shutdown P((int, struct peer *)); +static void oncore_consume P((struct instance *)); +static void oncore_read_config P((struct instance *)); +static void oncore_receive P((struct recvbuf *)); +static int oncore_ppsapi P((struct instance *)); +static void oncore_get_timestamp P((struct instance *, long, long)); +static void oncore_init_shmem P((struct instance *)); + +static void oncore_antenna_report P((struct instance *, enum antenna_state)); +static void oncore_chan_test P((struct instance *)); +static void oncore_check_almanac P((struct instance *)); +static void oncore_check_antenna P((struct instance *)); +static void oncore_check_leap_sec P((struct instance *)); +static int oncore_checksum_ok P((u_char *, int)); +static void oncore_compute_dH P((struct instance *)); +static void oncore_load_almanac P((struct instance *)); +static void oncore_print_Cb P((struct instance *, u_char *)); +/* static void oncore_print_array P((u_char *, int)); */ +static void oncore_print_posn P((struct instance *)); +static void oncore_sendmsg P((int, u_char *, size_t)); +static void oncore_set_posn P((struct instance *)); +static void oncore_set_traim P((struct instance *)); +static void oncore_shmem_get_3D P((struct instance *)); +static void oncore_ss P((struct instance *)); +static int oncore_wait_almanac P((struct instance *)); + +static void oncore_msg_any P((struct instance *, u_char *, size_t, int)); +static void oncore_msg_Adef P((struct instance *, u_char *, size_t)); +static void oncore_msg_Ag P((struct instance *, u_char *, size_t)); +static void oncore_msg_As P((struct instance *, u_char *, size_t)); +static void oncore_msg_At P((struct instance *, u_char *, size_t)); +static void oncore_msg_Ay P((struct instance *, u_char *, size_t)); +static void oncore_msg_Az P((struct instance *, u_char *, size_t)); +static void oncore_msg_BaEaHa P((struct instance *, u_char *, size_t)); +static void oncore_msg_Bd P((struct instance *, u_char *, size_t)); +static void oncore_msg_Bj P((struct instance *, u_char *, size_t)); +static void oncore_msg_BnEnHn P((struct instance *, u_char *, size_t)); +static void oncore_msg_CaFaIa P((struct instance *, u_char *, size_t)); +static void oncore_msg_Cb P((struct instance *, u_char *, size_t)); +static void oncore_msg_Cf P((struct instance *, u_char *, size_t)); +static void oncore_msg_Cj P((struct instance *, u_char *, size_t)); +static void oncore_msg_Cj_id P((struct instance *, u_char *, size_t)); +static void oncore_msg_Cj_init P((struct instance *, u_char *, size_t)); +static void oncore_msg_Ga P((struct instance *, u_char *, size_t)); +static void oncore_msg_Gb P((struct instance *, u_char *, size_t)); +static void oncore_msg_Gd P((struct instance *, u_char *, size_t)); +static void oncore_msg_Gj P((struct instance *, u_char *, size_t)); +static void oncore_msg_Sz P((struct instance *, u_char *, size_t)); + +struct refclock refclock_oncore = { + oncore_start, /* start up driver */ + oncore_shutdown, /* shut down driver */ + oncore_poll, /* transmit poll message */ + oncore_control, /* fudge (flag) control messages */ + noentry, /* not used */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +/* + * Understanding the next bit here is not easy unless you have a manual + * for the the various Oncore Models. + */ + +static struct msg_desc { + const char flag[3]; + const int len; + void (*handler) P((struct instance *, u_char *, size_t)); + const char *fmt; + int shmem; +} oncore_messages[] = { + /* Ea and En first since they're most common */ + { "Ea", 76, oncore_msg_BaEaHa, "mdyyhmsffffaaaaoooohhhhmmmmvvhhddtntimsdimsdimsdimsdimsdimsdimsdimsdsC" }, + { "Ba", 68, oncore_msg_BaEaHa, "mdyyhmsffffaaaaoooohhhhmmmmvvhhddtntimsdimsdimsdimsdimsdimsdsC" }, + { "Ha", 154, oncore_msg_BaEaHa, "mdyyhmsffffaaaaoooohhhhmmmmaaaaoooohhhhmmmmVVvvhhddntimsiddimsiddimsiddimsiddimsiddimsiddimsiddimsiddimsiddimsiddimsiddimsiddssrrccooooTTushmvvvvvvC" }, + { "Bn", 59, oncore_msg_BnEnHn, "otaapxxxxxxxxxxpysreensffffsffffsffffsffffsffffsffffC" }, + { "En", 69, oncore_msg_BnEnHn, "otaapxxxxxxxxxxpysreensffffsffffsffffsffffsffffsffffsffffsffffC" }, + { "Hn", 78, oncore_msg_BnEnHn, "" }, + { "Ab", 10, 0, "" }, + { "Ac", 11, 0, "" }, + { "Ad", 11, oncore_msg_Adef, "" }, + { "Ae", 11, oncore_msg_Adef, "" }, + { "Af", 15, oncore_msg_Adef, "" }, + { "Ag", 8, oncore_msg_Ag, "" }, /* Satellite mask angle */ + { "As", 20, oncore_msg_As, "" }, + { "At", 8, oncore_msg_At, "" }, + { "Au", 12, 0, "" }, + { "Av", 8, 0, "" }, + { "Aw", 8, 0, "" }, + { "Ay", 11, oncore_msg_Ay, "" }, + { "Az", 11, oncore_msg_Az, "" }, + { "AB", 8, 0, "" }, + { "Bb", 92, 0, "" }, + { "Bd", 23, oncore_msg_Bd, "" }, + { "Bj", 8, oncore_msg_Bj, "" }, + { "Ca", 9, oncore_msg_CaFaIa, "" }, + { "Cb", 33, oncore_msg_Cb, "" }, + { "Cf", 7, oncore_msg_Cf, "" }, + { "Cg", 8, 0, "" }, + { "Ch", 9, 0, "" }, + { "Cj", 294, oncore_msg_Cj, "" }, + { "Ek", 71, 0, "" }, + { "Fa", 9, oncore_msg_CaFaIa, "" }, + { "Ga", 20, oncore_msg_Ga, "" }, + { "Gb", 17, oncore_msg_Gb, "" }, + { "Gc", 8, 0, "" }, + { "Gd", 8, oncore_msg_Gd, "" }, + { "Ge", 8, 0, "" }, + { "Gj", 21, oncore_msg_Gj, "" }, + { "Ia", 10, oncore_msg_CaFaIa, "" }, + { "Sz", 8, oncore_msg_Sz, "" }, + { {0}, 7, 0, "" } +}; + + +static u_char oncore_cmd_Aa[] = { 'A', 'a', 0, 0, 0 }; /* 6/8 Time of Day */ +static u_char oncore_cmd_Ab[] = { 'A', 'b', 0, 0, 0 }; /* 6/8 GMT Correction */ +static u_char oncore_cmd_AB[] = { 'A', 'B', 4 }; /* VP Application Type: Static */ +static u_char oncore_cmd_Ac[] = { 'A', 'c', 0, 0, 0, 0 }; /* 6/8 Date */ +static u_char oncore_cmd_Ad[] = { 'A', 'd', 0,0,0,0 }; /* 6/8 Latitude */ +static u_char oncore_cmd_Ae[] = { 'A', 'e', 0,0,0,0 }; /* 6/8 Longitude */ +static u_char oncore_cmd_Af[] = { 'A', 'f', 0,0,0,0, 0 }; /* 6/8 Height */ +static u_char oncore_cmd_Ag[] = { 'A', 'g', 0 }; /* 6/8/12 Satellite Mask Angle */ +static u_char oncore_cmd_Agx[] = { 'A', 'g', 0xff }; /* 6/8/12 Satellite Mask Angle: read */ +static u_char oncore_cmd_As[] = { 'A', 's', 0,0,0,0, 0,0,0,0, 0,0,0,0, 0 }; /* 6/8/12 Posn Hold Parameters */ +static u_char oncore_cmd_Asx[] = { 'A', 's', 0x7f,0xff,0xff,0xff, /* 6/8/12 Posn Hold Readback */ + 0x7f,0xff,0xff,0xff, /* on UT+ this doesnt work with 0xff */ + 0x7f,0xff,0xff,0xff, 0xff }; /* but does work with 0x7f (sigh). */ +static u_char oncore_cmd_At0[] = { 'A', 't', 0 }; /* 6/8 Posn Hold: off */ +static u_char oncore_cmd_At1[] = { 'A', 't', 1 }; /* 6/8 Posn Hold: on */ +static u_char oncore_cmd_At2[] = { 'A', 't', 2 }; /* 6/8 Posn Hold: Start Site Survey */ +static u_char oncore_cmd_Atx[] = { 'A', 't', 0xff }; /* 6/8 Posn Hold: Read Back */ +static u_char oncore_cmd_Au[] = { 'A', 'u', 0,0,0,0, 0 }; /* GT/M12 Altitude Hold Ht. */ +static u_char oncore_cmd_Av0[] = { 'A', 'v', 0 }; /* VP/GT Altitude Hold: off */ +static u_char oncore_cmd_Av1[] = { 'A', 'v', 1 }; /* VP/GT Altitude Hold: on */ +static u_char oncore_cmd_Aw[] = { 'A', 'w', 1 }; /* 6/8/12 UTC/GPS time selection */ +static u_char oncore_cmd_Ay[] = { 'A', 'y', 0, 0, 0, 0 }; /* Timing 1PPS time offset: set */ +static u_char oncore_cmd_Ayx[] = { 'A', 'y', 0xff, 0xff, 0xff, 0xff }; /* Timing 1PPS time offset: Read */ +static u_char oncore_cmd_Az[] = { 'A', 'z', 0, 0, 0, 0 }; /* 6/8UT/12 1PPS Cable Delay: set */ +static u_char oncore_cmd_Azx[] = { 'A', 'z', 0xff, 0xff, 0xff, 0xff }; /* 6/8UT/12 1PPS Cable Delay: Read */ +static u_char oncore_cmd_Ba0[] = { 'B', 'a', 0 }; /* 6 Position/Data/Status: off */ +static u_char oncore_cmd_Ba[] = { 'B', 'a', 1 }; /* 6 Position/Data/Status: on */ +static u_char oncore_cmd_Bb[] = { 'B', 'b', 1 }; /* 6/8/12 Visible Satellites */ +static u_char oncore_cmd_Bd[] = { 'B', 'd', 1 }; /* 6/8/12? Almanac Status Msg. */ +static u_char oncore_cmd_Be[] = { 'B', 'e', 1 }; /* 6/8/12 Request Almanac Data */ +static u_char oncore_cmd_Bj[] = { 'B', 'j', 0 }; /* 6/8 Leap Second Pending */ +static u_char oncore_cmd_Bn0[] = { 'B', 'n', 0, 1, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 6 TRAIM setup/status: msg off, traim on */ +static u_char oncore_cmd_Bn[] = { 'B', 'n', 1, 1, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 6 TRAIM setup/status: msg on traim on */ +static u_char oncore_cmd_Bnx[] = { 'B', 'n', 1, 0, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 6 TRAIM setup/status: msg on traim off */ +static u_char oncore_cmd_Ca[] = { 'C', 'a' }; /* 6 Self Test */ +static u_char oncore_cmd_Cf[] = { 'C', 'f' }; /* 6/8/12 Set to Defaults */ +static u_char oncore_cmd_Cg[] = { 'C', 'g', 1 }; /* VP Posn Fix/Idle Mode */ +static u_char oncore_cmd_Cj[] = { 'C', 'j' }; /* 6/8/12 Receiver ID */ +static u_char oncore_cmd_Ea0[] = { 'E', 'a', 0 }; /* 8 Position/Data/Status: off */ +static u_char oncore_cmd_Ea[] = { 'E', 'a', 1 }; /* 8 Position/Data/Status: on */ +static u_char oncore_cmd_Ek[] = { 'E', 'k', 0 }; /* just turn off */ /* 8 Posn/Status/Data - extension */ +static u_char oncore_cmd_En0[] = { 'E', 'n', 0, 1, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 8/GT TRAIM setup/status: msg off, traim on */ +static u_char oncore_cmd_En[] = { 'E', 'n', 1, 1, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 8/GT TRAIM setup/status: msg on traim on */ +static u_char oncore_cmd_Enx[] = { 'E', 'n', 1, 0, 0,10, 2, 0,0,0, 0,0,0,0,0,0,0 }; /* 8/GT TRAIM setup/status: msg on traim off */ +static u_char oncore_cmd_Fa[] = { 'F', 'a' }; /* 8 Self Test */ +static u_char oncore_cmd_Ga[] = { 'G', 'a', 0,0,0,0, 0,0,0,0, 0,0,0,0, 0 }; /* 12 Position Set */ +static u_char oncore_cmd_Gax[] = { 'G', 'a', 0xff, 0xff, 0xff, 0xff, /* 12 Position Set: Read */ + 0xff, 0xff, 0xff, 0xff, /* */ + 0xff, 0xff, 0xff, 0xff, 0xff }; /* */ +static u_char oncore_cmd_Gb[] = { 'G', 'b', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* 12 set Date/Time */ +static u_char oncore_cmd_Gc[] = { 'G', 'c', 1 }; /* 12 PPS Control: On Cont */ +static u_char oncore_cmd_Gd0[] = { 'G', 'd', 0 }; /* 12 Position Control: 3D (no hold) */ +static u_char oncore_cmd_Gd1[] = { 'G', 'd', 1 }; /* 12 Position Control: 0D (3D hold) */ +static u_char oncore_cmd_Gd2[] = { 'G', 'd', 2 }; /* 12 Position Control: 2D (Alt Hold) */ +static u_char oncore_cmd_Gd3[] = { 'G', 'd', 3 }; /* 12 Position Coltrol: Start Site Survey */ +static u_char oncore_cmd_Ge0[] = { 'G', 'e', 0 }; /* M12+T TRAIM: off */ +static u_char oncore_cmd_Ge[] = { 'G', 'e', 1 }; /* M12+T TRAIM: on */ +static u_char oncore_cmd_Gj[] = { 'G', 'j' }; /* 8?/12 Leap Second Pending */ +static u_char oncore_cmd_Ha0[] = { 'H', 'a', 0 }; /* 12 Position/Data/Status: off */ +static u_char oncore_cmd_Ha[] = { 'H', 'a', 1 }; /* 12 Position/Data/Status: on */ +static u_char oncore_cmd_Hn0[] = { 'H', 'n', 0 }; /* 12 TRAIM Status: off */ +static u_char oncore_cmd_Hn[] = { 'H', 'n', 1 }; /* 12 TRAIM Status: on */ +static u_char oncore_cmd_Ia[] = { 'I', 'a' }; /* 12 Self Test */ + +/* it appears that as of 1997/1998, the UT had As,At, but not Au,Av + * the GT had Au,Av, but not As,At + * This was as of v2.0 of both firmware sets. possibly 1.3 for UT. + * Bj in UT at v1.3 + * dont see Bd in UT/GT thru 1999 + * Gj in UT as of 3.0, 1999 , Bj as of 1.3 + */ + +static char *Month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jly", + "Aug", "Sep", "Oct", "Nov", "Dec" }; + +#define DEVICE1 "/dev/oncore.serial.%d" /* name of serial device */ +#define DEVICE2 "/dev/oncore.pps.%d" /* name of pps device */ +#define INIT_FILE "/etc/ntp.oncore" /* optional init file */ + +#define SPEED B9600 /* Oncore Binary speed (9600 bps) */ + +/* + * Assemble and disassemble 32bit signed quantities from a buffer. + * + */ + + /* to buffer, int w, u_char *buf */ +#define w32_buf(buf,w) { u_int i_tmp; \ + i_tmp = (w<0) ? (~(-w)+1) : (w); \ + (buf)[0] = (i_tmp >> 24) & 0xff; \ + (buf)[1] = (i_tmp >> 16) & 0xff; \ + (buf)[2] = (i_tmp >> 8) & 0xff; \ + (buf)[3] = (i_tmp ) & 0xff; \ + } + +#define w32(buf) (((buf)[0]&0xff) << 24 | \ + ((buf)[1]&0xff) << 16 | \ + ((buf)[2]&0xff) << 8 | \ + ((buf)[3]&0xff) ) + + /* from buffer, char *buf, result to an int */ +#define buf_w32(buf) (((buf)[0]&0200) ? (-(~w32(buf)+1)) : w32(buf)) + + +/* + * oncore_start - initialize data for processing + */ + +static int +oncore_start( + int unit, + struct peer *peer + ) +{ + register struct instance *instance; + struct refclockproc *pp; + int fd1, fd2; + char device1[30], device2[30]; + const char *cp; + struct stat stat1, stat2; + + /* OPEN DEVICES */ + /* opening different devices for fd1 and fd2 presents no problems */ + /* opening the SAME device twice, seems to be OS dependent. + (a) on Linux (no streams) no problem + (b) on SunOS (and possibly Solaris, untested), (streams) + never see the line discipline. + Since things ALWAYS work if we only open the device once, we check + to see if the two devices are in fact the same, then proceed to + do one open or two. + */ + + (void)sprintf(device1, DEVICE1, unit); + (void)sprintf(device2, DEVICE2, unit); + + if (stat(device1, &stat1)) { + perror("ONCORE: stat fd1"); + exit(1); + } + + if (stat(device2, &stat2)) { + perror("ONCORE: stat fd2"); + exit(1); + } + + /* create instance structure for this unit */ + + if (!(instance = (struct instance *) malloc(sizeof *instance))) { + perror("malloc"); + return (0); + } + memset((char *) instance, 0, sizeof *instance); + + if ((stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino)) { + /* same device here */ + if (!(fd1 = refclock_open(device1, SPEED, LDISC_RAW +#if !defined(HAVE_PPSAPI) && !defined(TIOCDCDTIMESTAMP) + | LDISC_PPS +#endif + ))) { + perror("ONCORE: fd1"); + exit(1); + } + fd2 = fd1; + } else { /* different devices here */ + if (!(fd1=refclock_open(device1, SPEED, LDISC_RAW))) { + perror("ONCORE: fd1"); + exit(1); + } + if ((fd2=open(device2, O_RDWR)) < 0) { + perror("ONCORE: fd2"); + exit(1); + } + } + + /* initialize miscellaneous variables */ + + pp = peer->procptr; + pp->unitptr = (caddr_t) instance; + instance->pp = pp; + instance->unit = unit; + instance->peer = peer; + instance->assert = 1; + instance->once = 1; + + cp = "ONCORE DRIVER -- CONFIGURING"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->o_state = ONCORE_NO_IDEA; + cp = "state = ONCORE_NO_IDEA"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->ttyfd = fd1; + instance->ppsfd = fd2; + + instance->Bj_day = -1; + instance->traim = -1; + instance->traim_in = -1; + instance->chan_in = -1; + instance->model = ONCORE_UNKNOWN; + instance->mode = MODE_UNKNOWN; + instance->site_survey = ONCORE_SS_UNKNOWN; + instance->Ag = 0xff; /* Satellite mask angle, unset by user */ + instance->ant_state = ONCORE_ANTENNA_UNKNOWN; + + peer->precision = -26; + peer->minpoll = 4; + peer->maxpoll = 4; + pp->clockdesc = "Motorola Oncore GPS Receiver"; + memcpy((char *)&pp->refid, "GPS\0", (size_t) 4); + + /* go read any input data in /etc/ntp.oncoreX or /etc/ntp/oncore.X */ + + oncore_read_config(instance); + +#ifdef HAVE_PPSAPI + if (time_pps_create(fd2, &instance->pps_h) < 0) { + perror("time_pps_create"); + return(0); + } + + if (instance->assert) + cp = "Initializing timing to Assert."; + else + cp = "Initializing timing to Clear."; + record_clock_stats(&(instance->peer->srcadr), cp); + + if (instance->hardpps) { + cp = "HARDPPS Set."; + record_clock_stats(&(instance->peer->srcadr), cp); + } + + if (!oncore_ppsapi(instance)) + return(0); +#endif + + pp->io.clock_recv = oncore_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd1; + if (!io_addclock(&pp->io)) { + perror("io_addclock"); + (void) close(fd1); + free(instance); + return (0); + } + +#ifdef ONCORE_SHMEM_STATUS + /* + * Before starting ONCORE, lets setup SHMEM + * This will include merging an old SHMEM into the new one if + * an old one is found. + */ + + oncore_init_shmem(instance); +#endif + + /* + * This will return the Model of the Oncore receiver. + * and start the Initialization loop in oncore_msg_Cj. + */ + + instance->o_state = ONCORE_CHECK_ID; + cp = "state = ONCORE_CHECK_ID"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->timeout = 4; + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cg, sizeof(oncore_cmd_Cg)); /* Set Posn Fix mode (not Idle (VP)) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof(oncore_cmd_Cj)); + + instance->pollcnt = 2; + return (1); +} + + +/* + * Fudge control (get Flag2 and Flag3, not available at oncore_start time. + */ + +static void +oncore_control( + int unit, /* unit (not used) */ + struct refclockstat *in, /* input parameters (not used) */ + struct refclockstat *out, /* output parameters (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + char *cp; + struct refclockproc *pp; + struct instance *instance; + + pp = peer->procptr; + instance = (struct instance *) pp->unitptr; + + instance->assert = !(pp->sloppyclockflag & CLK_FLAG2); + instance->hardpps = pp->sloppyclockflag & CLK_FLAG3; + + if (instance->assert) + cp = "Resetting timing to Assert."; + else + cp = "Resetting timing to Clear."; + record_clock_stats(&(instance->peer->srcadr), cp); + + if (instance->hardpps) { + cp = "HARDPPS Set."; + record_clock_stats(&(instance->peer->srcadr), cp); + } + + (void) oncore_ppsapi(instance); +} + + + +/* + * oncore_shutdown - shut down the clock + */ + +static void +oncore_shutdown( + int unit, + struct peer *peer + ) +{ + register struct instance *instance; + struct refclockproc *pp; + + pp = peer->procptr; + instance = (struct instance *) pp->unitptr; + + io_closeclock(&pp->io); + + close(instance->ttyfd); + close(instance->ppsfd); + if (instance->shmemfd) + close(instance->shmemfd); + free(instance); +} + + + +/* + * oncore_poll - called by the transmit procedure + */ + +static void +oncore_poll( + int unit, + struct peer *peer + ) +{ + struct instance *instance; + + instance = (struct instance *) peer->procptr->unitptr; + if (instance->timeout) { + char *cp; + + instance->timeout--; + if (instance->timeout == 0) { + cp = "Oncore: No response from @@Cj, shutting down driver"; + record_clock_stats(&(instance->peer->srcadr), cp); + oncore_shutdown(unit, peer); + } else { + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof(oncore_cmd_Cj)); + cp = "Oncore: Resend @@Cj"; + record_clock_stats(&(instance->peer->srcadr), cp); + } + return; + } + + if (!instance->pollcnt) + refclock_report(peer, CEVNT_TIMEOUT); + else + instance->pollcnt--; + peer->procptr->polls++; + instance->polled = 1; +} + + + +/* + * Initialize PPSAPI + */ + +#ifdef HAVE_PPSAPI +static int +oncore_ppsapi( + struct instance *instance + ) +{ + int mode; + + if (time_pps_getcap(instance->pps_h, &mode) < 0) { + msyslog(LOG_ERR, "refclock_ioctl: time_pps_getcap failed: %m"); + return (0); + } + + if (time_pps_getparams(instance->pps_h, &instance->pps_p) < 0) { + msyslog(LOG_ERR, "refclock_ioctl: time_pps_getparams failed: %m"); + return (0); + } + + /* nb. only turn things on, if someone else has turned something + * on before we get here, leave it alone! + */ + + if (instance->assert) { /* nb, default or ON */ + instance->pps_p.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; + instance->pps_p.assert_offset.tv_sec = 0; + instance->pps_p.assert_offset.tv_nsec = 0; + } else { + instance->pps_p.mode = PPS_CAPTURECLEAR | PPS_OFFSETCLEAR; + instance->pps_p.clear_offset.tv_sec = 0; + instance->pps_p.clear_offset.tv_nsec = 0; + } + instance->pps_p.mode |= PPS_TSFMT_TSPEC; + instance->pps_p.mode &= mode; /* only set what is legal */ + + if (time_pps_setparams(instance->pps_h, &instance->pps_p) < 0) { + perror("time_pps_setparams"); + exit(1); + } + + /* If HARDPPS is on, we tell kernel */ + + if (instance->hardpps) { + int i; + + if (instance->assert) + i = PPS_CAPTUREASSERT; + else + i = PPS_CAPTURECLEAR; + + if (i&mode) { + if (time_pps_kcbind(instance->pps_h, PPS_KC_HARDPPS, i, + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, "refclock_ioctl: time_pps_kcbind failed: %m"); + return (0); + } + pps_enable = 1; + } + } + return(1); +} +#endif + + + +#ifdef ONCORE_SHMEM_STATUS +static void +oncore_init_shmem( + struct instance *instance + ) +{ + int i, l, n, fd, shmem_old_size, n1; + char *buf, Msg[160]; + u_char *cp, *cp1, *shmem_old; + struct msg_desc *mp; + struct stat sbuf; + size_t shmem_length; + + /* + * The first thing we do is see if there is an instance->shmem_fname file (still) + * out there from a previous run. If so, we copy it in and use it to initialize + * shmem (so we won't lose our almanac if we need it). + */ + + shmem_old = 0; + if ((fd = open(instance->shmem_fname, O_RDONLY)) < 0) + perror("LOAD:SHMEM"); + else { + fstat(fd, &sbuf); + shmem_old_size = sbuf.st_size; + shmem_old = (u_char *) malloc((unsigned) sbuf.st_size); + if (shmem_old == NULL) { + perror("malloc"); + close(fd); + return; + } + + read(fd, shmem_old, shmem_old_size); + close(fd); + } + + /* OK, we now create the NEW SHMEM. */ + + if ((instance->shmemfd = open(instance->shmem_fname, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0) { + perror(instance->shmem_fname); + return; + } + + /* see how big it needs to be */ + + n = 1; + for (mp=oncore_messages; mp->flag[0]; mp++) { + mp->shmem = n; + /* Allocate space for multiplexed almanac, and 0D/2D/3D @@Ea records */ + if (!strcmp(mp->flag, "Cb")) { + instance->shmem_Cb = n; + n += (mp->len + 3) * 34; + } + if (!strcmp(mp->flag, "Ba")) { + instance->shmem_Ba = n; + n += (mp->len + 3) * 3; + } + if (!strcmp(mp->flag, "Ea")) { + instance->shmem_Ea = n; + n += (mp->len + 3) * 3; + } + if (!strcmp(mp->flag, "Ha")) { + instance->shmem_Ha = n; + n += (mp->len + 3) * 3; + } + n += (mp->len + 3); + } + shmem_length = n + 2; + fprintf(stderr, "ONCORE: SHMEM length: %d bytes\n", (int) shmem_length); + + buf = malloc(shmem_length); + if (buf == NULL) { + perror("malloc"); + close(instance->shmemfd); + return; + } + + memset(buf, 0, shmem_length); + + /* next build the new SHMEM buffer in memory */ + + for (mp=oncore_messages; mp->flag[0]; mp++) { + l = mp->shmem; + buf[l + 0] = mp->len >> 8; + buf[l + 1] = mp->len & 0xff; + buf[l + 2] = 0; + buf[l + 3] = '@'; + buf[l + 4] = '@'; + buf[l + 5] = mp->flag[0]; + buf[l + 6] = mp->flag[1]; + if (!strcmp(mp->flag, "Cb") || !strcmp(mp->flag, "Ba") || !strcmp(mp->flag, "Ea") || !strcmp(mp->flag, "Ha")) { + if (!strcmp(mp->flag, "Cb")) + n = 35; + else + n = 4; + for (i=1; i<n; i++) { + buf[l + i * (mp->len+3) + 0] = mp->len >> 8; + buf[l + i * (mp->len+3) + 1] = mp->len & 0xff; + buf[l + i * (mp->len+3) + 2] = 0; + buf[l + i * (mp->len+3) + 3] = '@'; + buf[l + i * (mp->len+3) + 4] = '@'; + buf[l + i * (mp->len+3) + 5] = mp->flag[0]; + buf[l + i * (mp->len+3) + 6] = mp->flag[1]; + } + } + } + + /* we now walk thru the two buffers (shmem_old and buf, soon to become shmem) + * copying the data in shmem_old to buf. When we are done we write it out + * and free both buffers. + * If the structures change (an addition or deletion) I will stop copying. + * The two will be the same unless we add/subtract from the oncore_messages list + * so this should work most of the time, and takes a lot less code than doing it right. + */ + + if (shmem_old) { + for (cp=buf+4, cp1=shmem_old+4; (n = 256*(*(cp-3)) + *(cp-2)); cp+=(n+3), cp1+=(n+3)) { + n1 = 256*(*(cp1-3)) + *(cp1-2); + if (n1 != n || strncmp(cp, cp1, 4)) + break; + + memcpy(cp, cp1, (size_t) n); + } + free(shmem_old); + } + + i = write(instance->shmemfd, buf, shmem_length); + free(buf); + + if (i != shmem_length) { + perror(instance->shmem_fname); + close(instance->shmemfd); + return; + } + + instance->shmem = (u_char *) mmap(0, shmem_length, + PROT_READ | PROT_WRITE, +#ifdef MAP_HASSEMAPHORE + MAP_HASSEMAPHORE | +#endif + MAP_SHARED, instance->shmemfd, (off_t)0); + + if (instance->shmem == (u_char *)MAP_FAILED) { + instance->shmem = 0; + close(instance->shmemfd); + return; + } + + sprintf(Msg, "SHMEM (size = %d) is CONFIGURED and available as %s", shmem_length, instance->shmem_fname); + record_clock_stats(&(instance->peer->srcadr), Msg); +} +#endif /* ONCORE_SHMEM_STATUS */ + + + +/* + * Read Input file if it exists. + */ + +static void +oncore_read_config( + struct instance *instance + ) +{ +/* + * First we try to open the configuration file + * /etc/oncoreN + * where N is the unit number viz 127.127.30.N. + * If we don't find it we try + * /etc/ntp.oncore.N + * and then + * /etc/ntp.oncore + * + * If we don't find any then we don't have the cable delay or PPS offset + * and we choose MODE (4) below. + * + * Five Choices for MODE + * (0) ONCORE is preinitialized, don't do anything to change it. + * nb, DON'T set 0D mode, DON'T set Delay, position... + * (1) NO RESET, Read Position, delays from data file, lock it in, go to 0D mode. + * (2) NO RESET, Read Delays from data file, do SITE SURVEY to get position, + * lock this in, go to 0D mode. + * (3) HARD RESET, Read Position, delays from data file, lock it in, go to 0D mode. + * (4) HARD RESET, Read Delays from data file, do SITE SURVEY to get position, + * lock this in, go to 0D mode. + * NB. If a POSITION is specified in the config file with mode=(2,4) [SITE SURVEY] + * then this position is set as the INITIAL position of the ONCORE. + * This can reduce the time to first fix. + * ------------------------------------------------------------------------------- + * Note that an Oncore UT without a battery backup retains NO information if it is + * power cycled, with a Battery Backup it remembers the almanac, etc. + * For an Oncore VP, there is an eeprom that will contain this data, along with the + * option of Battery Backup. + * So a UT without Battery Backup is equivalent to doing a HARD RESET on each + * power cycle, since there is nowhere to store the data. + * ------------------------------------------------------------------------------- + * + * If we open one or the other of the files, we read it looking for + * MODE, LAT, LON, (HT, HTGPS, HTMSL), DELAY, OFFSET, ASSERT, CLEAR, HARDPPS, + * STATUS, POSN3D, POSN2D, CHAN, TRAIM + * then initialize using method MODE. For Mode = (1,3) all of (LAT, LON, HT) must + * be present or mode reverts to (2,4). + * + * Read input file. + * + * # is comment to end of line + * = allowed between 1st and 2nd fields. + * + * Expect to see one line with 'MODE' as first field, followed by an integer + * in the range 0-4 (default = 4). + * + * Expect to see two lines with 'LONG', 'LAT' followed by 1-3 fields. + * All numbers are floating point. + * DDD.ddd + * DDD MMM.mmm + * DDD MMM SSS.sss + * + * Expect to see one line with 'HT' as first field, + * followed by 1-2 fields. First is a number, the second is 'FT' or 'M' + * for feet or meters. HT is the height above the GPS ellipsoid. + * If the receiver reports height in both GPS and MSL, then we will report + * the difference GPS-MSL on the clockstats file. + * + * There is an optional line, starting with DELAY, followed + * by 1 or two fields. The first is a number (a time) the second is + * 'MS', 'US' or 'NS' for miliseconds, microseconds or nanoseconds. + * DELAY is cable delay, typically a few tens of ns. + * + * There is an optional line, starting with OFFSET, followed + * by 1 or two fields. The first is a number (a time) the second is + * 'MS', 'US' or 'NS' for miliseconds, microseconds or nanoseconds. + * OFFSET is the offset of the PPS pulse from 0. (only fully implemented + * with the PPSAPI, we need to be able to tell the Kernel about this + * offset if the Kernel PLL is in use, but can only do this presently + * when using the PPSAPI interface. If not using the Kernel PLL, + * then there is no problem. + * + * There is an optional line, with either ASSERT or CLEAR on it, which + * determine which transition of the PPS signal is used for timing by the + * PPSAPI. If neither is present, then ASSERT is assumed. + * ASSERT/CLEAR can also be set with FLAG2 of the ntp.conf input. + * For Flag2, ASSERT=0, and hence is default. + * + * There is an optional line, with HARDPPS on it. Including this line causes + * the PPS signal to control the kernel PLL. + * HARDPPS can also be set with FLAG3 of the ntp.conf input. + * For Flag3, 0 is disabled, and the default. + * + * There are three options that have to do with using the shared memory option. + * First, to enable the option there must be a SHMEM line with a file name. + * The file name is the file associated with the shared memory. + * + * In shared memory, there is one 'record' for each returned variable. + * For the @@Ea data there are three 'records' containing position data. + * There will always be data in the record corresponding to the '0D' @@Ea record, + * and the user has a choice of filling the '3D' record by specifying POSN3D, + * or the '2D' record by specifying POSN2D. In either case the '2D' or '3D' + * record is filled once every 15s. + * + * Two additional variables that can be set are CHAN and TRAIM. These should be + * set correctly by the code examining the @@Cj record, but we bring them out here + * to allow the user to override either the # of channels, or the existence of TRAIM. + * CHAN expects to be followed by in integer: 6, 8, or 12. TRAIM expects to be + * followed by YES or NO. + * + * There is an optional line with MASK on it followed by one integer field in the + * range 0 to 89. This sets the satellite mask angle and will determine the minimum + * elevation angle for satellites to be tracked by the receiver. The default value + * is 10 deg for the VP and 0 deg for all other receivers. + * + * So acceptable input would be + * # these are my coordinates (RWC) + * LON -106 34.610 + * LAT 35 08.999 + * HT 1589 # could equally well say HT 5215 FT + * DELAY 60 ns + */ + + FILE *fd; + char *cp, *cc, *ca, line[100], units[2], device[20], Msg[160]; + int i, sign, lat_flg, long_flg, ht_flg, mode, mask; + double f1, f2, f3; + + sprintf(device, "%s%d", INIT_FILE, instance->unit); /* try "ntp.oncore0" first */ + if ((fd=fopen(device, "r")) == NULL) { /* it was in the original documentation */ + sprintf(device, "%s.%d", INIT_FILE, instance->unit); /* then try "ntp.oncore.0 */ + if ((fd=fopen(device, "r")) == NULL) { + if ((fd=fopen(INIT_FILE, "r")) == NULL) { /* and finally "ntp.oncore" */ + instance->init_type = 4; + return; + } + } + } + + mode = mask = 0; + lat_flg = long_flg = ht_flg = 0; + while (fgets(line, 100, fd)) { + + /* Remove comments */ + if ((cp = strchr(line, '#'))) + *cp = '\0'; + + /* Remove trailing space */ + for (i = strlen(line); + i > 0 && isascii((int)line[i - 1]) && isspace((int)line[i - 1]); + ) + line[--i] = '\0'; + + /* Remove leading space */ + for (cc = line; *cc && isascii((int)*cc) && isspace((int)*cc); cc++) + continue; + + /* Stop if nothing left */ + if (!*cc) + continue; + + /* Uppercase the command and find the arg */ + for (ca = cc; *ca; ca++) { + if (isascii((int)*ca)) { + if (islower((int)*ca)) { + *ca = toupper(*ca); + } else if (isspace((int)*ca) || (*ca == '=')) + break; + } + } + + /* Remove space (and possible =) leading the arg */ + for (; *ca && isascii((int)*ca) && (isspace((int)*ca) || (*ca == '=')); ca++) + continue; + + if (!strncmp(cc, "STATUS", (size_t) 6) || !strncmp(cc, "SHMEM", (size_t) 5)) { + i = strlen(ca); + instance->shmem_fname = (char *) malloc((unsigned) (i+1)); + strcpy(instance->shmem_fname, ca); + continue; + } + + /* Uppercase argument as well */ + for (cp = ca; *cp; cp++) + if (isascii((int)*cp) && islower((int)*cp)) + *cp = toupper(*cp); + + if (!strncmp(cc, "LAT", (size_t) 3)) { + f1 = f2 = f3 = 0; + sscanf(ca, "%lf %lf %lf", &f1, &f2, &f3); + sign = 1; + if (f1 < 0) { + f1 = -f1; + sign = -1; + } + instance->ss_lat = sign*1000*(fabs(f3) + 60*(fabs(f2) + 60*f1)); /*miliseconds*/ + lat_flg++; + } else if (!strncmp(cc, "LON", (size_t) 3)) { + f1 = f2 = f3 = 0; + sscanf(ca, "%lf %lf %lf", &f1, &f2, &f3); + sign = 1; + if (f1 < 0) { + f1 = -f1; + sign = -1; + } + instance->ss_long = sign*1000*(fabs(f3) + 60*(fabs(f2) + 60*f1)); /*miliseconds*/ + long_flg++; + } else if (!strncmp(cc, "HT", (size_t) 2)) { + f1 = 0; + units[0] = '\0'; + sscanf(ca, "%lf %1s", &f1, units); + if (units[0] == 'F') + f1 = 0.3048 * f1; + instance->ss_ht = 100 * f1; /* cm */ + ht_flg++; + } else if (!strncmp(cc, "DELAY", (size_t) 5)) { + f1 = 0; + units[0] = '\0'; + sscanf(ca, "%lf %1s", &f1, units); + if (units[0] == 'N') + ; + else if (units[0] == 'U') + f1 = 1000 * f1; + else if (units[0] == 'M') + f1 = 1000000 * f1; + else + f1 = 1000000000 * f1; + if (f1 < 0 || f1 > 1.e9) + f1 = 0; + if (f1 < 0 || f1 > 999999) { + sprintf(Msg, "PPS Cable delay of %fns out of Range, ignored", f1); + record_clock_stats(&(instance->peer->srcadr), Msg); + } else + instance->delay = f1; /* delay in ns */ + } else if (!strncmp(cc, "OFFSET", (size_t) 6)) { + f1 = 0; + units[0] = '\0'; + sscanf(ca, "%lf %1s", &f1, units); + if (units[0] == 'N') + ; + else if (units[0] == 'U') + f1 = 1000 * f1; + else if (units[0] == 'M') + f1 = 1000000 * f1; + else + f1 = 1000000000 * f1; + if (f1 < 0 || f1 > 1.e9) + f1 = 0; + if (f1 < 0 || f1 > 999999999.) { + sprintf(Msg, "PPS Offset of %fns out of Range, ignored", f1); + record_clock_stats(&(instance->peer->srcadr), Msg); + } else + instance->offset = f1; /* offset in ns */ + } else if (!strncmp(cc, "MODE", (size_t) 4)) { + sscanf(ca, "%d", &mode); + if (mode < 0 || mode > 4) + mode = 4; + } else if (!strncmp(cc, "ASSERT", (size_t) 6)) { + instance->assert = 1; + } else if (!strncmp(cc, "CLEAR", (size_t) 5)) { + instance->assert = 0; + } else if (!strncmp(cc, "HARDPPS", (size_t) 7)) { + instance->hardpps = 1; + } else if (!strncmp(cc, "POSN2D", (size_t) 6)) { + instance->shmem_Posn = 2; + } else if (!strncmp(cc, "POSN3D", (size_t) 6)) { + instance->shmem_Posn = 3; + } else if (!strncmp(cc, "CHAN", (size_t) 4)) { + sscanf(ca, "%d", &i); + if ((i == 6) || (i == 8) || (i == 12)) + instance->chan_in = i; + } else if (!strncmp(cc, "TRAIM", (size_t) 5)) { + instance->traim_in = 1; /* so TRAIM alone is YES */ + if (!strcmp(ca, "NO") || !strcmp(ca, "OFF")) /* Yes/No, On/Off */ + instance->traim_in = 0; + } else if (!strncmp(cc, "MASK", (size_t) 4)) { + sscanf(ca, "%d", &mask); + if (mask > -1 && mask < 90) + instance->Ag = mask; /* Satellite mask angle */ + } + } + fclose(fd); + + /* + * OK, have read all of data file, and extracted the good stuff. + * If lat/long/ht specified they ALL must be specified for mode = (1,3). + */ + + instance->posn_set = 1; + if (!( lat_flg && long_flg && ht_flg )) { + printf("ONCORE: incomplete data on %s\n", INIT_FILE); + instance->posn_set = 0; + if (mode == 1 || mode == 3) { + sprintf(Msg, "Input Mode = %d, but no/incomplete position, mode set to %d", mode, mode+1); + record_clock_stats(&(instance->peer->srcadr), Msg); + mode++; + } + } + instance->init_type = mode; + + sprintf(Msg, "Input mode = %d", mode); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* + * move data from NTP to buffer (toss the extra in the unlikely case it won't fit) + */ + +static void +oncore_receive( + struct recvbuf *rbufp + ) +{ + size_t i; + u_char *p; + struct peer *peer; + struct instance *instance; + + peer = (struct peer *)rbufp->recv_srcclock; + instance = (struct instance *) peer->procptr->unitptr; + p = (u_char *) &rbufp->recv_space; + +#if 0 + if (debug > 4) { + int i; + printf("ONCORE: >>>"); + for(i=0; i<rbufp->recv_length; i++) + printf("%02x ", p[i]); + printf("\n"); + printf("ONCORE: >>>"); + for(i=0; i<rbufp->recv_length; i++) + printf("%03o ", p[i]); + printf("\n"); + } +#endif + + i = rbufp->recv_length; + if (rcvbuf+rcvptr+i > &rcvbuf[sizeof rcvbuf]) + i = sizeof(rcvbuf) - rcvptr; /* and some char will be lost */ + memcpy(rcvbuf+rcvptr, p, i); + rcvptr += i; + oncore_consume(instance); +} + + + +/* + * Deal with any complete messages + */ + +static void +oncore_consume( + struct instance *instance + ) +{ + int i, m; + unsigned l; + + while (rcvptr >= 7) { + if (rcvbuf[0] != '@' || rcvbuf[1] != '@') { + /* We're not in sync, lets try to get there */ + for (i=1; i < rcvptr-1; i++) + if (rcvbuf[i] == '@' && rcvbuf[i+1] == '@') + break; + if (debug > 4) + printf("ONCORE[%d]: >>> skipping %d chars\n", instance->unit, i); + if (i != rcvptr) + memcpy(rcvbuf, rcvbuf+i, (size_t)(rcvptr-i)); + rcvptr -= i; + continue; + } + + /* Ok, we have a header now */ + l = sizeof(oncore_messages)/sizeof(oncore_messages[0]) -1; + for(m=0; m<l; m++) + if (!strncmp(oncore_messages[m].flag, (char *)(rcvbuf+2), (size_t) 2)) + break; + if (m == l) { + if (debug > 4) + printf("ONCORE[%d]: >>> Unknown MSG, skipping 4 (%c%c)\n", instance->unit, rcvbuf[2], rcvbuf[3]); + memcpy(rcvbuf, rcvbuf+4, (size_t) 4); + rcvptr -= 4; + continue; + } + + l = oncore_messages[m].len; +#if 0 + if (debug > 3) + printf("ONCORE[%d]: GOT: %c%c %d of %d entry %d\n", instance->unit, rcvbuf[2], rcvbuf[3], rcvptr, l, m); +#endif + /* Got the entire message ? */ + + if (rcvptr < l) + return; + + /* are we at the end of message? should be <Cksum><CR><LF> */ + + if (rcvbuf[l-2] != '\r' || rcvbuf[l-1] != '\n') { + if (debug) + printf("ONCORE[%d]: NO <CR><LF> at end of message\n", instance->unit); + } else { /* check the CheckSum */ + if (oncore_checksum_ok(rcvbuf, l)) { + if (instance->shmem != NULL) { + instance->shmem[oncore_messages[m].shmem + 2]++; + memcpy(instance->shmem + oncore_messages[m].shmem + 3, + rcvbuf, (size_t) l); + } + oncore_msg_any(instance, rcvbuf, (size_t) (l-3), m); + if (oncore_messages[m].handler) + oncore_messages[m].handler(instance, rcvbuf, (size_t) (l-3)); + } else if (debug) { + printf("ONCORE[%d]: Checksum mismatch!\n", instance->unit); + printf("ONCORE[%d]: @@%c%c ", instance->unit, rcvbuf[2], rcvbuf[3]); + for (i=4; i<l; i++) + printf("%03o ", rcvbuf[i]); + printf("\n"); + } + } + + if (l != rcvptr) + memcpy(rcvbuf, rcvbuf+l, (size_t) (rcvptr-l)); + rcvptr -= l; + } +} + + + +static void +oncore_get_timestamp( + struct instance *instance, + long dt1, /* tick offset THIS time step */ + long dt2 /* tick offset NEXT time step */ + ) +{ + int Rsm; + u_long i, j; + l_fp ts, ts_tmp; + double dmy; +#ifdef HAVE_STRUCT_TIMESPEC + struct timespec *tsp = 0; +#else + struct timeval *tsp = 0; +#endif +#ifdef HAVE_PPSAPI + int current_mode; + pps_params_t current_params; + struct timespec timeout; + pps_info_t pps_i; +#else /* ! HAVE_PPSAPI */ +#ifdef HAVE_CIOGETEV + struct ppsclockev ev; + int r = CIOGETEV; +#endif +#ifdef HAVE_TIOCGPPSEV + struct ppsclockev ev; + int r = TIOCGPPSEV; +#endif +#if TIOCDCDTIMESTAMP + struct timeval tv; +#endif +#endif /* ! HAVE_PPS_API */ + +#if 1 + /* If we are in SiteSurvey mode, then we are in 3D mode, and we fall thru. + * If we have Finished the SiteSurvey, then we fall thru for the 14/15 + * times we get here in 0D mode (the 1/15 is in 3D for SHMEM). + * This gives good time, which gets better when the SS is done. + */ + + if ((instance->site_survey == ONCORE_SS_DONE) && (instance->mode != MODE_0D)) +#else + /* old check, only fall thru for SS_DONE and 0D mode, 2h45m wait for ticks */ + + if ((instance->site_survey != ONCORE_SS_DONE) || (instance->mode != MODE_0D)) +#endif + return; + + /* Don't do anything without an almanac to define the GPS->UTC delta */ + + if (instance->rsm.bad_almanac) + return; + +#ifdef HAVE_PPSAPI + j = instance->ev_serial; + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + if (time_pps_fetch(instance->pps_h, PPS_TSFMT_TSPEC, &pps_i, + &timeout) < 0) { + printf("ONCORE: time_pps_fetch failed\n"); + return; + } + + if (instance->assert) { + tsp = &pps_i.assert_timestamp; + + if (debug > 2) { + i = (u_long) pps_i.assert_sequence; +#ifdef HAVE_STRUCT_TIMESPEC + printf("ONCORE[%d]: serial/j (%lu, %lu) %ld.%09ld\n", + instance->unit, i, j, + (long)tsp->tv_sec, (long)tsp->tv_nsec); +#else + printf("ONCORE[%d]: serial/j (%lu, %lu) %ld.%06ld\n", + instance->unit, i, j, + (long)tsp->tv_sec, (long)tsp->tv_usec); +#endif + } + + if (pps_i.assert_sequence == j) { + printf("ONCORE: oncore_get_timestamp, error serial pps\n"); + return; + } + instance->ev_serial = pps_i.assert_sequence; + } else { + tsp = &pps_i.clear_timestamp; + + if (debug > 2) { + i = (u_long) pps_i.clear_sequence; +#ifdef HAVE_STRUCT_TIMESPEC + printf("ONCORE[%d]: serial/j (%lu, %lu) %ld.%09ld\n", + instance->unit, i, j, (long)tsp->tv_sec, (long)tsp->tv_nsec); +#else + printf("ONCORE[%d]: serial/j (%lu, %lu) %ld.%06ld\n", + instance->unit, i, j, (long)tsp->tv_sec, (long)tsp->tv_usec); +#endif + } + + if (pps_i.clear_sequence == j) { + printf("ONCORE: oncore_get_timestamp, error serial pps\n"); + return; + } + instance->ev_serial = pps_i.clear_sequence; + } + + /* convert timespec -> ntp l_fp */ + + dmy = tsp->tv_nsec; + dmy /= 1e9; + ts.l_uf = dmy * 4294967296.0; + ts.l_ui = tsp->tv_sec; +#if 0 + alternate code for previous 4 lines is + dmy = 1.0e-9*tsp->tv_nsec; /* fractional part */ + DTOLFP(dmy, &ts); + dmy = tsp->tv_sec; /* integer part */ + DTOLFP(dmy, &ts_tmp); + L_ADD(&ts, &ts_tmp); + or more simply + dmy = 1.0e-9*tsp->tv_nsec; /* fractional part */ + DTOLFP(dmy, &ts); + ts.l_ui = tsp->tv_sec; +#endif /* 0 */ +#else +# if defined(HAVE_TIOCGPPSEV) || defined(HAVE_CIOGETEV) + j = instance->ev_serial; + if (ioctl(instance->ppsfd, r, (caddr_t) &ev) < 0) { + perror("ONCORE: IOCTL:"); + return; + } + + tsp = &ev.tv; + + if (debug > 2) +#ifdef HAVE_STRUCT_TIMESPEC + printf("ONCORE: serial/j (%d, %d) %ld.%09ld\n", + ev.serial, j, tsp->tv_sec, tsp->tv_nsec); +#else + printf("ONCORE: serial/j (%d, %d) %ld.%06ld\n", + ev.serial, j, tsp->tv_sec, tsp->tv_usec); +#endif + + if (ev.serial == j) { + printf("ONCORE: oncore_get_timestamp, error serial pps\n"); + return; + } + instance->ev_serial = ev.serial; + + /* convert timeval -> ntp l_fp */ + + TVTOTS(tsp, &ts); +# else +# if defined(TIOCDCDTIMESTAMP) + if(ioctl(instance->ppsfd, TIOCDCDTIMESTAMP, &tv) < 0) { + perror("ONCORE: ioctl(TIOCDCDTIMESTAMP)"); + return; + } + tsp = &tv; + TVTOTS(tsp, &ts); +# else +#error "Cannot compile -- no PPS mechanism configured!" +# endif +# endif +#endif + /* now have timestamp in ts */ + /* add in saw_tooth and offset, these will be ZERO if no TRAIM */ + + /* saw_tooth not really necessary if using TIMEVAL */ + /* since its only precise to us, but do it anyway. */ + + /* offset in ns, and is positive (late), we subtract */ + /* to put the PPS time transition back where it belongs */ + +#ifdef HAVE_PPSAPI + /* must hand the offset for the NEXT sec off to the Kernel to do */ + /* the addition, so that the Kernel PLL sees the offset too */ + + if (instance->assert) + instance->pps_p.assert_offset.tv_nsec = -dt2; + else + instance->pps_p.clear_offset.tv_nsec = -dt2; + + /* The following code is necessary, and not just a time_pps_setparams, + * using the saved instance->pps_p, since some other process on the + * machine may have diddled with the mode bits (say adding something + * that it needs). We take what is there and ADD what we need. + * [[ The results from the time_pps_getcap is unlikely to change so + * we could probably just save it, but I choose to do the call ]] + * Unfortunately, there is only ONE set of mode bits in the kernel per + * interface, and not one set for each open handle. + * + * There is still a race condition here where we might mess up someone + * elses mode, but if he is being careful too, he should survive. + */ + + if (time_pps_getcap(instance->pps_h, ¤t_mode) < 0) { + msyslog(LOG_ERR, "refclock_ioctl: time_pps_getcap failed: %m"); + return; + } + + if (time_pps_getparams(instance->pps_h, ¤t_params) < 0) { + msyslog(LOG_ERR, "refclock_ioctl: time_pps_getparams failed: %m"); + return; + } + + /* or current and mine */ + current_params.mode |= instance->pps_p.mode; + /* but only set whats legal */ + current_params.mode &= current_mode; + + current_params.assert_offset.tv_sec = 0; + current_params.assert_offset.tv_nsec = -dt2; + current_params.clear_offset.tv_sec = 0; + current_params.clear_offset.tv_nsec = -dt2; + + if (time_pps_setparams(instance->pps_h, ¤t_params)) + perror("time_pps_setparams"); +#else + /* if not PPSAPI, no way to inform kernel of OFFSET, just add the */ + /* offset for THIS second */ + + dmy = -1.0e-9*dt1; + DTOLFP(dmy, &ts_tmp); + L_ADD(&ts, &ts_tmp); +#endif + /* have time from UNIX origin, convert to NTP origin. */ + + ts.l_ui += JAN_1970; + instance->pp->lastrec = ts; + + /* print out information about this timestamp (long line) */ + + ts_tmp = ts; + ts_tmp.l_ui = 0; /* zero integer part */ + LFPTOD(&ts_tmp, dmy); /* convert fractional part to a double */ + j = 1.0e9*dmy; /* then to integer ns */ + + Rsm = 0; + if (instance->chan == 6) + Rsm = instance->BEHa[64]; + else if (instance->chan == 8) + Rsm = instance->BEHa[72]; + else if (instance->chan == 12) + Rsm = ((instance->BEHa[129]<<8) | instance->BEHa[130]); + + if (instance->chan == 6 || instance->chan == 8) { + sprintf(instance->pp->a_lastcode, /* MAX length 128, currently at 117 */ + "%u.%09lu %d %d %2d %2d %2d %2ld rstat %02x dop %4.1f nsat %2d,%d traim %d sigma %2d neg-sawtooth %3d sat %d%d%d%d%d%d%d%d", + ts.l_ui, j, + instance->pp->year, instance->pp->day, + instance->pp->hour, instance->pp->minute, instance->pp->second, + (long) tsp->tv_sec % 60, + Rsm, 0.1*(256*instance->BEHa[35]+instance->BEHa[36]), + /*rsat dop */ + instance->BEHa[38], instance->BEHa[39], instance->BEHn[21], + /* nsat visible, nsat tracked, traim */ + instance->BEHn[23]*256+instance->BEHn[24], (s_char) instance->BEHn[25], + /* sigma neg-sawtooth */ + /*sat*/ instance->BEHa[41], instance->BEHa[45], instance->BEHa[49], instance->BEHa[53], + instance->BEHa[57], instance->BEHa[61], instance->BEHa[65], instance->BEHa[69] + ); /* will be 0 for 6 chan */ + } else if (instance->chan == 12) { + sprintf(instance->pp->a_lastcode, + "%u.%09lu %d %d %2d %2d %2d %2ld rstat %02x dop %4.1f nsat %2d,%d traim %d sigma %d neg-sawtooth %3d sat %d%d%d%d%d%d%d%d%d%d%d%d", + ts.l_ui, j, + instance->pp->year, instance->pp->day, + instance->pp->hour, instance->pp->minute, instance->pp->second, + (long) tsp->tv_sec % 60, + Rsm, 0.1*(256*instance->BEHa[53]+instance->BEHa[54]), + /*rsat dop */ + instance->BEHa[55], instance->BEHa[56], instance->BEHn[6], + /* nsat visible, nsat tracked traim */ + instance->BEHn[12]*256+instance->BEHn[13], (s_char) instance->BEHn[14], + /* sigma neg-sawtooth */ + /*sat*/ instance->BEHa[58], instance->BEHa[64], instance->BEHa[70], instance->BEHa[76], + instance->BEHa[82], instance->BEHa[88], instance->BEHa[94], instance->BEHa[100], + instance->BEHa[106], instance->BEHa[112], instance->BEHa[118], instance->BEHa[124] + ); + } + + if (debug > 2) { + int n; + n = strlen(instance->pp->a_lastcode); + printf("ONCORE[%d]: len = %d %s\n", instance->unit, n, instance->pp->a_lastcode); + } + + /* and some things I dont understnd (magic ntp things) */ + + if (!refclock_process(instance->pp)) { + refclock_report(instance->peer, CEVNT_BADTIME); + return; + } + + record_clock_stats(&(instance->peer->srcadr), instance->pp->a_lastcode); + instance->pollcnt = 2; + + if (instance->polled) { + instance->polled = 0; +/* + instance->pp->dispersion = instance->pp->skew = 0; +*/ + instance->pp->lastref = instance->pp->lastrec; + refclock_receive(instance->peer); + } +} + + +/*************** oncore_msg_XX routines start here *******************/ + + +/* + * print Oncore response message. + */ + +static void +oncore_msg_any( + struct instance *instance, + u_char *buf, + size_t len, + int idx + ) +{ + int i; + const char *fmt = oncore_messages[idx].fmt; + const char *p; +#ifdef HAVE_GETCLOCK + struct timespec ts; +#endif + struct timeval tv; + + if (debug > 3) { +#ifdef HAVE_GETCLOCK + (void) getclock(TIMEOFDAY, &ts); + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec / 1000; +#else + GETTIMEOFDAY(&tv, 0); +#endif + printf("ONCORE[%d]: %ld.%06ld\n", instance->unit, (long) tv.tv_sec, (long) tv.tv_usec); + + if (!*fmt) { + printf(">>@@%c%c ", buf[2], buf[3]); + for(i=2; i < len && i < 2400 ; i++) + printf("%02x", buf[i]); + printf("\n"); + return; + } else { + printf("##"); + for (p = fmt; *p; p++) { + putchar(*p); + putchar('_'); + } + printf("\n%c%c", buf[2], buf[3]); + i = 4; + for (p = fmt; *p; p++) { + printf("%02x", buf[i++]); + } + printf("\n"); + } + } +} + + + +/* Latitude, Longitude, Height */ + +static void +oncore_msg_Adef( + struct instance *instance, + u_char *buf, + size_t len + ) +{ +} + + + +/* Mask Angle */ + +static void +oncore_msg_Ag( + struct instance *instance, + u_char *buf, + size_t len + ) +{ char Msg[160], *cp; + + cp = "set to"; + if (instance->o_state == ONCORE_RUN) + cp = "is"; + + instance->Ag = buf[4]; + sprintf(Msg, "Satellite mask angle %s %d degrees", cp, (int) instance->Ag); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* + * get Position hold position + */ + +static void +oncore_msg_As( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + instance->ss_lat = buf_w32(&buf[4]); + instance->ss_long = buf_w32(&buf[8]); + instance->ss_ht = buf_w32(&buf[12]); + + /* Print out Position */ + oncore_print_posn(instance); +} + + + +/* + * Try to use Oncore UT+ Auto Survey Feature + * If its not there (VP), set flag to do it ourselves. + */ + +static void +oncore_msg_At( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char *cp; + + instance->saw_At = 1; + if (instance->site_survey == ONCORE_SS_TESTING) { + if (buf[4] == 2) { + record_clock_stats(&(instance->peer->srcadr), + "Initiating hardware 3D site survey"); + + cp = "SSstate = ONCORE_SS_HW"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->site_survey = ONCORE_SS_HW; + } + } +} + + + +/* + * get PPS Offset + * Nb. @@Ay is not supported for early UT (no plus) model + */ + +static void +oncore_msg_Ay( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char Msg[120]; + + if (instance->saw_Ay) + return; + + instance->saw_Ay = 1; + + instance->offset = buf_w32(&buf[4]); + + sprintf(Msg, "PPS Offset is set to %ld ns", instance->offset); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* + * get Cable Delay + */ + +static void +oncore_msg_Az( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char Msg[120]; + + if (instance->saw_Az) + return; + + instance->saw_Az = 1; + + instance->delay = buf_w32(&buf[4]); + + sprintf(Msg, "Cable delay is set to %ld ns", instance->delay); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* Ba, Ea and Ha come here, these contain Position */ + +static void +oncore_msg_BaEaHa( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + const char *cp; + char Msg[160]; + int mode; + + /* OK, we are close to the RUN state now. + * But we have a few more items to initialize first. + * + * At the beginning of this routine there are several 'timers'. + * We enter this routine 1/sec, and since the upper levels of NTP have usurped + * the use of timers, we use the 1/sec entry to do things that + * we would normally do with timers... + */ + + if (instance->o_state == ONCORE_CHECK_CHAN) { /* here while checking for the # chan */ + if (buf[2] == 'B') { /* 6chan */ + if (instance->chan_ck < 6) instance->chan_ck = 6; + } else if (buf[2] == 'E') { /* 8chan */ + if (instance->chan_ck < 8) instance->chan_ck = 8; + } else if (buf[2] == 'H') { /* 12chan */ + if (instance->chan_ck < 12) instance->chan_ck = 12; + } + + if (instance->count3++ < 5) + return; + + instance->count3 = 0; + + if (instance->chan_in != -1) /* set in Input */ + instance->chan = instance->chan_in; + else /* set from test */ + instance->chan = instance->chan_ck; + + sprintf(Msg, "Input says chan = %d", instance->chan_in); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Model # says chan = %d", instance->chan_id); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Testing says chan = %d", instance->chan_ck); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Using chan = %d", instance->chan); + record_clock_stats(&(instance->peer->srcadr), Msg); + + instance->o_state = ONCORE_HAVE_CHAN; + cp = "state = ONCORE_HAVE_CHAN"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->timeout = 4; + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof(oncore_cmd_Cj)); + return; + } + + if (instance->o_state != ONCORE_ALMANAC && instance->o_state != ONCORE_RUN) + return; + + /* PAUSE 5sec */ + + if (instance->count) { + if (instance->count++ < 5) /* make sure results are stable, using position */ + return; + instance->count = 0; + } + + memcpy(instance->BEHa, buf, (size_t) (len+3)); /* Ba, Ea or Ha */ + + /* check the antenna and almanac for changes (did it get unplugged, is it ready?) */ + + oncore_check_almanac(instance); + oncore_check_antenna(instance); + + /* Almanac mode, waiting for Almanac, we can't do anything till we have it */ + /* When we have an almanac, we will start the Bn/En/@@Hn messages */ + + if (instance->o_state == ONCORE_ALMANAC) + if (oncore_wait_almanac(instance)) + return; + + /* do some things once when we get this far in BaEaHa */ + + if (instance->once) { + instance->once = 0; + instance->count2 = 1; + + /* Have we seen an @@At (position hold) command response */ + /* if not, message out */ + + if (instance->chan != 12 && !instance->saw_At) { + cp = "Not Good, no @@At command (no Position Hold), must be a GT/GT+"; + record_clock_stats(&(instance->peer->srcadr), cp); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av1, sizeof(oncore_cmd_Av1)); + } + + /* have an Almanac, can start the SiteSurvey + * (actually only need to get past the almanac_load where we diddle with At + * command,- we can't change it after we start the HW_SS below + */ + + mode = instance->init_type; + switch (mode) { + case 0: /* NO initialization, don't change anything */ + case 1: /* Use given Position */ + case 3: + instance->site_survey = ONCORE_SS_DONE; + cp = "SSstate = ONCORE_SS_DONE"; + record_clock_stats(&(instance->peer->srcadr), cp); + break; + + case 2: + case 4: /* Site Survey */ + cp = "SSstate = ONCORE_SS_TESTING"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->site_survey = ONCORE_SS_TESTING; + instance->count1 = 1; + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd3, sizeof(oncore_cmd_Gd3)); /* M12+T */ + else + oncore_sendmsg(instance->ttyfd, oncore_cmd_At2, sizeof(oncore_cmd_At2)); /* not GT, arg not VP */ + break; + } + + /* Read back PPS Offset for Output */ + /* Nb. This will fail silently for early UT (no plus) and M12 models */ + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ayx, sizeof(oncore_cmd_Ayx)); + + /* Read back Cable Delay for Output */ + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Azx, sizeof(oncore_cmd_Azx)); + + /* Read back Satellite Mask Angle for Output */ + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Agx, sizeof(oncore_cmd_Agx)); + } + + if (instance->count1) { + if (instance->count1++ > 5 || instance->site_survey == ONCORE_SS_HW) { + instance->count1 = 0; + if (instance->site_survey == ONCORE_SS_TESTING) { + /* + * For instance->site_survey to still be ONCORE_SS_TESTING, then after a 5sec + * wait after the @@At2/@@Gd3 command we have not changed the state to + * ONCORE_SS_HW. If the Hardware is capable of doing a Site Survey, then + * the variable would have been changed by now. + * There are three possibilities: + * 6/8chan + * (a) We did not get a response to the @@At0 or @@At2 commands, + * and it must be a GT/GT+/SL with no position hold mode. + * We will have to do it ourselves. + * (b) We saw the @@At0, @@At2 commands, but @@At2 failed, + * must be a VP or older UT which doesn't have Site Survey mode. + * We will have to do it ourselves. + * 12chan + * (c) We saw the @@Gd command, but @@Gd3 failed, + * We will have to do it ourselves. + */ + + sprintf(Msg, "Initiating software 3D site survey (%d samples)", + POS_HOLD_AVERAGE); + record_clock_stats(&(instance->peer->srcadr), Msg); + + record_clock_stats(&(instance->peer->srcadr), "SSstate = ONCORE_SS_SW"); + instance->site_survey = ONCORE_SS_SW; + + instance->ss_lat = instance->ss_long = instance->ss_ht = 0; + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd0, sizeof(oncore_cmd_Gd0)); /* disable */ + else { + oncore_sendmsg(instance->ttyfd, oncore_cmd_At0, sizeof(oncore_cmd_At0)); /* disable */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av0, sizeof(oncore_cmd_Av0)); /* disable */ + } + } + } + } + + /* check the mode we are in 0/2/3D */ + + if (instance->chan == 6) { + if (instance->BEHa[64]&0x8) + instance->mode = MODE_0D; + else if (instance->BEHa[64]&0x10) + instance->mode = MODE_2D; + else if (instance->BEHa[64]&0x20) + instance->mode = MODE_3D; + } else if (instance->chan == 8) { + if (instance->BEHa[72]&0x8) + instance->mode = MODE_0D; + else if (instance->BEHa[72]&0x10) + instance->mode = MODE_2D; + else if (instance->BEHa[72]&0x20) + instance->mode = MODE_3D; + } else if (instance->chan == 12) { + int bits; + + bits = (instance->BEHa[129]>>5) & 0x7; /* actually Ha */ + if (bits == 0x4) + instance->mode = MODE_0D; + else if (bits == 0x6) + instance->mode = MODE_2D; + else if (bits == 0x7) + instance->mode = MODE_3D; + } + + /* copy the record to the (extra) location in SHMEM */ + + if (instance->shmem) { + int i; + u_char *smp; /* pointer to start of shared mem for Ba/Ea/Ha */ + + switch(instance->chan) { + case 6: smp = &instance->shmem[instance->shmem_Ba]; break; + case 8: smp = &instance->shmem[instance->shmem_Ea]; break; + case 12: smp = &instance->shmem[instance->shmem_Ha]; break; + default: smp = (u_char) 0; break; + } + + switch (instance->mode) { + case MODE_0D: i = 1; break; /* 0D, Position Hold */ + case MODE_2D: i = 2; break; /* 2D, Altitude Hold */ + case MODE_3D: i = 3; break; /* 3D fix */ + default: i = 0; break; + } + + if (i) { + i *= (len+6); + smp[i + 2]++; + memcpy(&smp[i+3], buf, (size_t) (len+3)); + } + } + + /* + * check if timer active + * if it hasn't been cleared, then @@Bn/@@En/@@Hn did not respond + */ + + if (instance->traim_delay) { + if (instance->traim_delay++ > 5) { + instance->traim = 0; + instance->traim_delay = 0; + cp = "ONCORE: Did not detect TRAIM response, TRAIM = OFF"; + record_clock_stats(&(instance->peer->srcadr), cp); + + oncore_set_traim(instance); + } else + return; + + } + + /* by now should have a @@Ba/@@Ea/@@Ha with good data in it */ + + if (!instance->have_dH && !instance->traim_delay) + oncore_compute_dH(instance); + + /* + * must be ONCORE_RUN if we are here. + * Have # chan and TRAIM by now. + */ + + instance->pp->year = buf[6]*256+buf[7]; + instance->pp->day = ymd2yd(buf[6]*256+buf[7], buf[4], buf[5]); + instance->pp->hour = buf[8]; + instance->pp->minute = buf[9]; + instance->pp->second = buf[10]; + + /* + * Are we doing a Hardware or Software Site Survey? + */ + + if (instance->site_survey == ONCORE_SS_HW || instance->site_survey == ONCORE_SS_SW) + oncore_ss(instance); + + /* see if we ever saw a response from the @@Ayx above */ + + if (instance->count2) { + if (instance->count2++ > 5) { /* this delay to check on @@Ay command */ + instance->count2 = 0; + + /* Have we seen an Ay (1PPS time offset) command response */ + /* if not, and non-zero offset, zero the offset, and send message */ + + if (!instance->saw_Ay && instance->offset) { + cp = "No @@Ay command, PPS OFFSET ignored"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->offset = 0; + } + } + } + + /* + * Check the leap second status once per day. + */ + + oncore_check_leap_sec(instance); + + /* + * if SHMEM active, every 15s, steal one 'tick' to get 2D or 3D posn. + */ + + if (instance->shmem && !instance->shmem_bad_Ea && instance->shmem_Posn && (instance->site_survey == ONCORE_SS_DONE)) + oncore_shmem_get_3D(instance); + + if (!instance->traim) /* NO traim, no BnEnHn, go get tick */ + oncore_get_timestamp(instance, instance->offset, instance->offset); +} + + + +/* Almanac Status */ + +static void +oncore_msg_Bd( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char Msg[160]; + + sprintf(Msg, "Bd: Almanac %s, week = %d, t = %d, %d SVs: %x", + ((buf[4]) ? "LOADED" : "(NONE)"), buf[5], buf[6], buf[7], w32(&buf[8]) ); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* get leap-second warning message */ + +/* + * @@Bj does NOT behave as documented in current Oncore firmware. + * It turns on the LEAP indicator when the data is set, and does not, + * as documented, wait until the beginning of the month when the + * leap second will occur. + * Since this firmware bug will never be fixed in all the outstanding Oncore receivers + * @@Bj is only called in June/December. + */ + +static void +oncore_msg_Bj( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + const char *cp; + + switch(buf[4]) { + case 1: + instance->peer->leap = LEAP_ADDSECOND; + cp = "Set peer.leap to LEAP_ADDSECOND"; + break; + case 2: + instance->peer->leap = LEAP_DELSECOND; + cp = "Set peer.leap to LEAP_DELSECOND"; + break; + case 0: + default: + instance->peer->leap = LEAP_NOWARNING; + cp = "Set peer.leap to LEAP_NOWARNING"; + break; + } + record_clock_stats(&(instance->peer->srcadr), cp); +} + + + +static void +oncore_msg_BnEnHn( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + long dt1, dt2; + char *cp; + + if (instance->o_state != ONCORE_RUN) + return; + + if (instance->traim_delay) { /* flag that @@Bn/@@En/Hn returned */ + instance->traim_ck = 1; + instance->traim_delay = 0; + cp = "ONCORE: Detected TRAIM, TRAIM = ON"; + record_clock_stats(&(instance->peer->srcadr), cp); + + oncore_set_traim(instance); + } + + memcpy(instance->BEHn, buf, (size_t) len); /* Bn or En or Hn */ + + /* If Time RAIM doesn't like it, don't trust it */ + + if (buf[2] == 'H') { + if (instance->BEHn[6]) /* bad TRAIM */ + return; + + dt1 = instance->saw_tooth + instance->offset; /* dt this time step */ + instance->saw_tooth = (s_char) instance->BEHn[10]; /* update for next time Hn[10] */ + dt2 = instance->saw_tooth + instance->offset; /* dt next time step */ + } else { + if (instance->BEHn[21]) /* bad TRAIM */ + return; + + dt1 = instance->saw_tooth + instance->offset; /* dt this time step */ + instance->saw_tooth = (s_char) instance->BEHn[25]; /* update for next time */ + dt2 = instance->saw_tooth + instance->offset; /* dt next time step */ + } + + oncore_get_timestamp(instance, dt1, dt2); +} + + + +/* Here for @@Ca, @@Fa and @@Ia messages */ + +/* These are Self test Commands for 6, 8, and 12 chan receivers. + * There are good reasons NOT to do a @@Ca, @@Fa or @@Ia command with the ONCORE. + * It was found that under some circumstances the following + * command would fail if issued immediately after the return from the + * @@Fa, but a 2sec delay seemed to fix things. Since simply calling + * sleep(2) is wasteful, and may cause trouble for some OS's, repeating + * itimer, we set a flag, and test it at the next POLL. If it hasn't + * been cleared, we reissue the @@Cj that is issued below. + * Note that we do a @@Cj at the beginning, and again here. + * The first is to get the info, the 2nd is just used as a safe command + * after the @@Fa for all Oncores (and it was in this posn in the + * original code). + */ + +static void +oncore_msg_CaFaIa( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char *cp; + int i; + + if (instance->o_state == ONCORE_TEST_SENT) { + enum antenna_state antenna; + + instance->timeout = 0; + + if (debug > 2) { + if (buf[2] == 'I') + printf("ONCORE[%d]: >>@@%ca %x %x %x\n", instance->unit, buf[2], buf[4], buf[5], buf[6]); + else + printf("ONCORE[%d]: >>@@%ca %x %x\n", instance->unit, buf[2], buf[4], buf[5]); + } + + antenna = (buf[4] & 0xc0) >> 6; + buf[4] &= ~0xc0; + + i = buf[4] || buf[5]; + if (buf[2] == 'I') i = i || buf[6]; + if (i) { + if (buf[2] == 'I') { + msyslog(LOG_ERR, "ONCORE[%d]: self test failed: result %02x %02x %02x", + instance->unit, buf[4], buf[5], buf[6]); + } else { + msyslog(LOG_ERR, "ONCORE[%d]: self test failed: result %02x %02x", + instance->unit, buf[4], buf[5]); + } + cp = "ONCORE: self test failed, shutting down driver"; + record_clock_stats(&instance->peer->srcadr, cp); + + refclock_report(instance->peer, CEVNT_FAULT); + oncore_shutdown(instance->unit, instance->peer); + return; + } + + /* report the current antenna state */ + + oncore_antenna_report(instance, antenna); + + instance->o_state = ONCORE_INIT; + cp = "state = ONCORE_INIT"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->timeout = 4; + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof(oncore_cmd_Cj)); + } +} + + + +/* + * Demultiplex the almanac into shmem + */ + +static void +oncore_msg_Cb( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + int i; + + if (instance->shmem == NULL) + return; + + if (buf[4] == 5 && buf[5] > 0 && buf[5] < 26) + i = buf[5]; + else if (buf[4] == 4 && buf[5] <= 5) + i = buf[5] + 24; + else if (buf[4] == 4 && buf[5] <= 10) + i = buf[5] + 23; + else if (buf[4] == 4 && buf[5] == 25) + i = 34; + else { + char *cp; + + cp = "Cb: Response is NO ALMANAC"; + record_clock_stats(&(instance->peer->srcadr), cp); + return; + } + + i *= 36; + instance->shmem[instance->shmem_Cb + i + 2]++; + memcpy(instance->shmem + instance->shmem_Cb + i + 3, buf, (size_t) (len + 3)); + +#if 1 + { + char Msg[160]; + sprintf(Msg, "See Cb [%d,%d]", buf[4], buf[5]); + record_clock_stats(&(instance->peer->srcadr), Msg); + } +#endif +} + + + +/* + * Set to Factory Defaults (Reasonable for UT w/ no Battery Backup + * not so for VP (eeprom) or any unit with a battery + */ + +static void +oncore_msg_Cf( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + const char *cp; + + if (instance->o_state == ONCORE_RESET_SENT) { + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cg, sizeof(oncore_cmd_Cg)); /* Return to Posn Fix mode */ + /* Reset set VP to IDLE */ + instance->o_state = ONCORE_TEST_SENT; + cp = "state = ONCORE_TEST_SENT"; + record_clock_stats(&(instance->peer->srcadr), cp); + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cj, sizeof(oncore_cmd_Cj)); + } +} + + + +/* + * This is the Grand Central Station for the Preliminary Initialization. + * Once done here we move on to oncore_msg_BaEaHa for final Initialization and Running. + * + * We do an @@Cj whenever we need a safe command for all Oncores. + * The @@Cj gets us back here where we can switch to the next phase of setup. + * + * o Once at the very beginning (in start) to get the Model number. + * This info is printed, but no longer used. + * o Again after we have determined the number of Channels in the receiver. + * o And once later after we have done a reset and test, (which may hang), + * as we are about to initialize the Oncore and start it running. + * o We have one routine below for each case. + */ + +static void +oncore_msg_Cj( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + int mode; + char *cp; + + memcpy(instance->Cj, buf, len); + + instance->timeout = 0; + if (instance->o_state == ONCORE_CHECK_ID) { + oncore_msg_Cj_id(instance, buf, len); + oncore_chan_test(instance); + } else if (instance->o_state == ONCORE_HAVE_CHAN) { + mode = instance->init_type; + if (mode == 3 || mode == 4) { /* Cf will return here to check for TEST */ + instance->o_state = ONCORE_RESET_SENT; + cp = "state = ONCORE_RESET_SENT"; + record_clock_stats(&(instance->peer->srcadr), cp); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cf, sizeof(oncore_cmd_Cf)); + } else { + instance->o_state = ONCORE_TEST_SENT; + cp = "state = ONCORE_TEST_SENT"; + record_clock_stats(&(instance->peer->srcadr), cp); + } + } + + if (instance->o_state == ONCORE_TEST_SENT) { + if (instance->chan == 6) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ca, sizeof(oncore_cmd_Ca)); + else if (instance->chan == 8) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Fa, sizeof(oncore_cmd_Fa)); + else if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ia, sizeof(oncore_cmd_Ia)); + } else if (instance->o_state == ONCORE_INIT) + oncore_msg_Cj_init(instance, buf, len); +} + + + +/* The information on determining a Oncore 'Model', viz VP, UT, etc, from + * the Model Number comes from "Richard M. Hambly" <rick@cnssys.com> + * and from Motorola. Until recently Rick was the only source of + * this information as Motorola didn't give the information out. + * + * Determine the Type from the Model #, this determines #chan and if TRAIM is + * available. + * + * The Information from this routine is NO LONGER USED. + * The RESULTS are PRINTED, BUT NOT USED, and the routine COULD BE DELETED + */ + +static void +oncore_msg_Cj_id( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char *cp, *cp1, *cp2, Model[21], Msg[160]; + + /* Write Receiver ID message to clockstats file */ + + instance->Cj[294] = '\0'; + for (cp=(char *)instance->Cj; cp< (char *) &instance->Cj[294]; ) { + cp1 = strchr(cp, '\r'); + if (!cp1) + cp1 = (char *)&instance->Cj[294]; + *cp1 = '\0'; + record_clock_stats(&(instance->peer->srcadr), cp); + *cp1 = '\r'; + cp = cp1+2; + } + + /* next, the Firmware Version and Revision numbers */ + + instance->version = atoi(&instance->Cj[83]); + instance->revision = atoi(&instance->Cj[111]); + + /* from model number decide which Oncore this is, + and then the number of channels */ + + for (cp=&instance->Cj[160]; *cp == ' '; cp++) /* start right after 'Model #' */ + ; + cp1 = cp; + cp2 = Model; + for (; !isspace((int)*cp) && cp-cp1 < 20; cp++, cp2++) + *cp2 = *cp; + *cp2 = '\0'; + + cp = 0; + if (!strncmp(Model, "PVT6", (size_t) 4)) { + cp = "PVT6"; + instance->model = ONCORE_PVT6; + } else if (Model[0] == 'A') { + cp = "Basic"; + instance->model = ONCORE_BASIC; + } else if (Model[0] == 'B' || !strncmp(Model, "T8", (size_t) 2)) { + cp = "VP"; + instance->model = ONCORE_VP; + } else if (Model[0] == 'P') { + cp = "M12"; + instance->model = ONCORE_M12; + } else if (Model[0] == 'R' || Model[0] == 'D' || Model[0] == 'S') { + if (Model[5] == 'N') { + cp = "GT"; + instance->model = ONCORE_GT; + } else if ((Model[1] == '3' || Model[1] == '4') && Model[5] == 'G') { + cp = "GT+"; + instance->model = ONCORE_GTPLUS; + } else if ((Model[1] == '5' && Model[5] == 'U') || (Model[1] == '1' && Model[5] == 'A')) { + cp = "UT"; + instance->model = ONCORE_UT; + } else if (Model[1] == '5' && Model[5] == 'G') { + cp = "UT+"; + instance->model = ONCORE_UTPLUS; + } else if (Model[1] == '6' && Model[5] == 'G') { + cp = "SL"; + instance->model = ONCORE_SL; + } else { + cp = "Unknown"; + instance->model = ONCORE_UNKNOWN; + } + } else { + cp = "Unknown"; + instance->model = ONCORE_UNKNOWN; + } + + /* use MODEL to set CHAN and TRAIM and possibly zero SHMEM */ + + sprintf(Msg, "This looks like an Oncore %s with version %d.%d firmware.", cp, instance->version, instance->revision); + record_clock_stats(&(instance->peer->srcadr), Msg); + + instance->chan_id = 8; /* default */ + if (instance->model == ONCORE_BASIC || instance->model == ONCORE_PVT6) + instance->chan_id = 6; + else if (instance->model == ONCORE_VP || instance->model == ONCORE_UT || instance->model == ONCORE_UTPLUS) + instance->chan_id = 8; + else if (instance->model == ONCORE_M12) + instance->chan_id = 12; + + instance->traim_id = 0; /* default */ + if (instance->model == ONCORE_BASIC || instance->model == ONCORE_PVT6) + instance->traim_id = 0; + else if (instance->model == ONCORE_VP || instance->model == ONCORE_UT || instance->model == ONCORE_UTPLUS) + instance->traim_id = 1; + else if (instance->model == ONCORE_M12) + instance->traim_id = -1; + + sprintf(Msg, "Channels = %d, TRAIM = %s", instance->chan_id, + ((instance->traim_id < 0) ? "UNKNOWN" : ((instance->traim_id > 0) ? "ON" : "OFF"))); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* OK, know type of Oncore, have possibly reset it, and have tested it. + * We know the number of channels. + * We will determine whether we have TRAIM before we actually start. + * Now initialize. + */ + +static void +oncore_msg_Cj_init( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char *cp, Cmd[20], Msg[160]; + int mode; + + + /* The M12 with 1.3 or 2.0 Firmware, loses track of all Satellites and has to + * start again if we go from 0D -> 3D, then loses them again when we + * go from 3D -> 0D. We do this to get a @@Ea message for SHMEM. + * For NOW we will turn this aspect of filling SHMEM off for the M12 + */ + + if (instance->chan == 12) { + instance->shmem_bad_Ea = 1; + sprintf(Msg, "*** SHMEM partially enabled for ONCORE M12 s/w v%d.%d ***", instance->version, instance->revision); + record_clock_stats(&(instance->peer->srcadr), Msg); + } + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Cg, sizeof(oncore_cmd_Cg)); /* Return to Posn Fix mode */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bb, sizeof(oncore_cmd_Bb)); /* turn on for shmem (6/8/12) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ek, sizeof(oncore_cmd_Ek)); /* turn off (VP) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Aw, sizeof(oncore_cmd_Aw)); /* UTC time (6/8/12) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_AB, sizeof(oncore_cmd_AB)); /* Appl type static (VP) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Be, sizeof(oncore_cmd_Be)); /* Tell us the Almanac for shmem (6/8/12) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bd, sizeof(oncore_cmd_Bd)); /* Tell us when Almanac changes */ + + mode = instance->init_type; + + /* If there is Position input in the Config file + * and mode = (1,3) set it as posn hold posn, goto 0D mode. + * or mode = (2,4) set it as INITIAL position, and do Site Survey. + */ + + if (instance->posn_set) { + record_clock_stats(&(instance->peer->srcadr), "Setting Posn from input data"); + oncore_set_posn(instance); /* this should print posn indirectly thru the As cmd */ + } else /* must issue an @@At here to check on 6/8 Position Hold, set_posn would have */ + if (instance->chan != 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Atx, sizeof(oncore_cmd_Atx)); + + if (mode != 0) { + /* cable delay in ns */ + memcpy(Cmd, oncore_cmd_Az, (size_t) sizeof(oncore_cmd_Az)); + w32_buf(&Cmd[-2+4], instance->delay); + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Az)); /* 6,8,12 */ + + /* PPS offset in ns */ + if (instance->offset) { + memcpy(Cmd, oncore_cmd_Ay, (size_t) sizeof(oncore_cmd_Ay)); /* some have it, some don't */ + w32_buf(&Cmd[-2+4], instance->offset); /* will check for hw response */ + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ay)); + } + + /* Satellite mask angle */ + + if (instance->Ag != 0xff) { /* will have 0xff in it if not set by user */ + memcpy(Cmd, oncore_cmd_Ag, (size_t) sizeof(oncore_cmd_Ag)); + Cmd[-2+4] = instance->Ag; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ag)); + } + } + + /* 6, 8 12 chan - Position/Status/Data Output Message, 1/s + * now we're really running + * these were ALL started in the chan test, + * However, if we had mode=3,4 then commands got turned off, so we turn + * them on again here just in case + */ + + if (instance->chan == 6) { /* start 6chan, kill 8,12chan commands, possibly testing VP in 6chan mode */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ea0, sizeof(oncore_cmd_Ea0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_En0, sizeof(oncore_cmd_En0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ha0, sizeof(oncore_cmd_Ha0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Hn0, sizeof(oncore_cmd_Hn0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ba, sizeof(oncore_cmd_Ba )); + } else if (instance->chan == 8) { /* start 8chan, kill 6,12chan commands */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ba0, sizeof(oncore_cmd_Ba0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bn0, sizeof(oncore_cmd_Bn0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ha0, sizeof(oncore_cmd_Ha0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Hn0, sizeof(oncore_cmd_Hn0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ea, sizeof(oncore_cmd_Ea )); + } else if (instance->chan == 12){ /* start 12chan, kill 6,12chan commands */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ba0, sizeof(oncore_cmd_Ba0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bn0, sizeof(oncore_cmd_Bn0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ea0, sizeof(oncore_cmd_Ea0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_En0, sizeof(oncore_cmd_En0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ha, sizeof(oncore_cmd_Ha )); + } + + instance->count = 1; + instance->o_state = ONCORE_ALMANAC; + cp = "state = ONCORE_ALMANAC"; + record_clock_stats(&(instance->peer->srcadr), cp); +} + + + +/* 12chan position */ + +static void +oncore_msg_Ga( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char Msg[160]; + long lat, lon, ht; + double Lat, Lon, Ht; + + + lat = buf_w32(&buf[4]); + lon = buf_w32(&buf[8]); + ht = buf_w32(&buf[12]); /* GPS ellipsoid */ + + Lat = lat; + Lon = lon; + Ht = ht; + + Lat /= 3600000; + Lon /= 3600000; + Ht /= 100; + + + sprintf(Msg, "Ga Posn Lat = %.7f, Lon = %.7f, Ht = %.2f", Lat, Lon, Ht); + record_clock_stats(&(instance->peer->srcadr), Msg); + + instance->ss_lat = lat; + instance->ss_long = lon; + instance->ss_ht = ht; + + oncore_print_posn(instance); +} + + + +/* 12 chan time/date */ + +static void +oncore_msg_Gb( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char Msg[160], *gmts; + int mo, d, y, h, m, s, gmth, gmtm; + + mo = buf[4]; + d = buf[5]; + y = 256*buf[6]+buf[7]; + + h = buf[8]; + m = buf[9]; + s = buf[10]; + + gmts = ((buf[11] == 0) ? "+" : "-"); + gmth = buf[12]; + gmtm = buf[13]; + + sprintf(Msg, "Date/Time set to: %d%s%d %2d:%02d:%02d GMT (GMT offset is %s%02d:%02d)", + d, Month[mo+1], y, h, m, s, gmts, gmth, gmtm); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* + * Try to use Oncore M12+Timing Auto Survey Feature + * If its not there (M12), set flag to do it ourselves. + */ + +static void +oncore_msg_Gd( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + char *cp; + + if (instance->site_survey == ONCORE_SS_TESTING) { + if (buf[4] == 3) { + record_clock_stats(&(instance->peer->srcadr), + "Initiating hardware 3D site survey"); + + cp = "SSstate = ONCORE_SS_HW"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->site_survey = ONCORE_SS_HW; + } + } +} + + + +/* Leap Second for M12, gives all info from satellite message */ +/* also in UT v3.0 */ + +static void +oncore_msg_Gj( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + int dt; + char Msg[160], *cp; + + instance->saw_Gj = 1; /* flag, saw_Gj, dont need to try Bj in check_leap */ + + /* print the message to verify whats there */ + + dt = buf[5] - buf[4]; + +#if 1 + sprintf(Msg, "ONCORE[%d]: Leap Sec Msg: %d %d %d %d %d %d %d %d %d %d", + instance->unit, + buf[4], buf[5], 256*buf[6]+buf[7], buf[8], buf[9], buf[10], + (buf[14]+256*(buf[13]+256*(buf[12]+256*buf[11]))), + buf[15], buf[16], buf[17]); + record_clock_stats(&(instance->peer->srcadr), Msg); +#endif + if (dt) { + sprintf(Msg, "ONCORE[%d]: Leap second (%d) scheduled for %d%s%d at %d:%d:%d", + instance->unit, + dt, buf[9], Month[buf[8]], 256*buf[6]+buf[7], + buf[15], buf[16], buf[17]); + record_clock_stats(&(instance->peer->srcadr), Msg); + } + + /* Only raise warning within a month of the leap second */ + + instance->peer->leap = LEAP_NOWARNING; + cp = "Set peer.leap to LEAP_NOWARNING"; + + if (buf[6] == instance->BEHa[6] && buf[7] == instance->BEHa[7] && /* year */ + buf[8] == instance->BEHa[4]) { /* month */ + if (dt) { + if (dt < 0) { + instance->peer->leap = LEAP_DELSECOND; + cp = "Set peer.leap to LEAP_DELSECOND"; + } else { + instance->peer->leap = LEAP_ADDSECOND; + cp = "Set peer.leap to LEAP_ADDSECOND"; + } + } + } + record_clock_stats(&(instance->peer->srcadr), cp); +} + + + +/* Power on failure */ + +static void +oncore_msg_Sz( + struct instance *instance, + u_char *buf, + size_t len + ) +{ + const char *cp; + + cp = "Oncore: System Failure at Power On"; + if (instance && instance->peer) { + record_clock_stats(&(instance->peer->srcadr), cp); + oncore_shutdown(instance->unit, instance->peer); + } +} + +/************** Small Subroutines ***************/ + + +static void +oncore_antenna_report( + struct instance *instance, + enum antenna_state new_state) +{ + char *cp; + + if (instance->ant_state == new_state) + return; + + switch (new_state) { + case ONCORE_ANTENNA_OK: cp = "GPS antenna: OK"; break; + case ONCORE_ANTENNA_OC: cp = "GPS antenna: short (overcurrent)"; break; + case ONCORE_ANTENNA_UC: cp = "GPS antenna: open (not connected)"; break; + case ONCORE_ANTENNA_NV: cp = "GPS antenna: short (no voltage)"; break; + default: cp = "GPS antenna: ?"; break; + } + + instance->ant_state = new_state; + record_clock_stats(&instance->peer->srcadr, cp); +} + + + +static void +oncore_chan_test( + struct instance *instance + ) +{ + char *cp; + + /* subroutine oncore_Cj_id has determined the number of channels from the + * model number of the attached oncore. This is not always correct since + * the oncore could have non-standard firmware. Here we check (independently) by + * trying a 6, 8, and 12 chan command, and see which responds. + * Caution: more than one CAN respond. + * + * This #chan is used by the code rather than that calculated from the model number. + */ + + instance->o_state = ONCORE_CHECK_CHAN; + cp = "state = ONCORE_CHECK_CHAN"; + record_clock_stats(&(instance->peer->srcadr), cp); + + instance->count3 = 1; + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ba, sizeof(oncore_cmd_Ba)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ea, sizeof(oncore_cmd_Ea)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ha, sizeof(oncore_cmd_Ha)); +} + + + +/* check for a GOOD Almanac, have we got one yet? */ + +static void +oncore_check_almanac( + struct instance *instance + ) +{ + if (instance->chan == 6) { + instance->rsm.bad_almanac = instance->BEHa[64]&0x1; + instance->rsm.bad_fix = instance->BEHa[64]&0x52; + } else if (instance->chan == 8) { + instance->rsm.bad_almanac = instance->BEHa[72]&0x1; + instance->rsm.bad_fix = instance->BEHa[72]&0x52; + } else if (instance->chan == 12) { + int bits1, bits2; + + bits1 = (instance->BEHa[129]>>5) & 0x7; /* actually Ha */ + bits2 = instance->BEHa[130]; + instance->rsm.bad_almanac = (bits2 & 0x80); + instance->rsm.bad_fix = (bits2 & 0x8) || (bits1 == 0x2); + /* too few sat Bad Geom */ +#if 0 + fprintf(stderr, "ONCORE[%d]: DEBUG BITS: (%x %x), (%x %x), %x %x %x %x %x\n", + instance->unit, + instance->BEHa[129], instance->BEHa[130], bits1, bits2, instance->mode == MODE_0D, + instance->mode == MODE_2D, instance->mode == MODE_3D, + instance->rsm.bad_almanac, instance->rsm.bad_fix); +#endif + } +} + + + +/* check the antenna for changes (did it get unplugged?) */ + +static void +oncore_check_antenna( + struct instance *instance + ) +{ + enum antenna_state antenna; /* antenna state */ + + antenna = instance->ant_state; + if (instance->chan == 12) + antenna = (instance->BEHa[130] & 0x6 ) >> 1; + else + antenna = (instance->BEHa[37] & 0xc0) >> 6; /* prob unset 6, set GT, UT unset VP */ + + oncore_antenna_report (instance, antenna); +} + + + +/* + * Check the leap second status once per day. + * + * Note that the ONCORE firmware for the Bj command is wrong at + * least in the VP. + * It starts advertising a LEAP SECOND as soon as the GPS satellite + * data message (page 18, subframe 4) is updated to a date in the + * future, and does not wait for the month that it will occur. + * The event will usually be advertised several months in advance. + * Since there is a one bit flag, there is no way to tell if it is + * this month, or when... + * + * As such, we have the workaround below, of only checking for leap + * seconds with the Bj command in June/December. + * + * The Gj command gives more information, and we can tell in which + * month to apply the correction. + * + * Note that with the VP we COULD read the raw data message, and + * interpret it ourselves, but since this is specific to this receiver + * only, and the above workaround is adequate, we don't bother. + */ + +static void +oncore_check_leap_sec( + struct instance *instance + ) +{ + if (instance->Bj_day != instance->BEHa[5]) { /* do this 1/day */ + instance->Bj_day = instance->BEHa[5]; + + if (instance->saw_Gj < 0) { /* -1 DONT have Gj use Bj */ + if ((instance->BEHa[4] == 6) || (instance->BEHa[4] == 12)) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bj, sizeof(oncore_cmd_Bj)); + return; + } + + if (instance->saw_Gj == 0) /* 0 is dont know if we have Gj */ + instance->count4 = 1; + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gj, sizeof(oncore_cmd_Gj)); + return; + } + + /* Gj works for some 6/8 chan UT and the M12 */ + /* if no response from Gj in 5 sec, we try Bj */ + /* which isnt implemented in all the GT/UT either */ + + if (instance->count4) { /* delay, waiting for Gj response */ + if (instance->saw_Gj == 1) + instance->count4 = 0; + else if (instance->count4++ > 5) { /* delay, waiting for Gj response */ + instance->saw_Gj = -1; /* didnt see it, will use Bj */ + instance->count4 = 0; + if ((instance->BEHa[4] == 6) || (instance->BEHa[4] == 12)) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bj, sizeof(oncore_cmd_Bj)); + } + } +} + + + +/* check the message checksum, + * buf points to START of message ( @@ ) + * len is length WITH CR/LF. + */ + +static int +oncore_checksum_ok( + u_char *buf, + int len + ) +{ + int i, j; + + j = 0; + for (i = 2; i < len-3; i++) + j ^= buf[i]; + + return(j == buf[len-3]); +} + + + +static void +oncore_compute_dH( + struct instance *instance + ) +{ + int GPS, MSL; + char Msg[160]; + + /* Here calculate dH = GPS - MSL for output message */ + /* also set Altitude Hold mode if GT */ + + instance->have_dH = 1; + if (instance->chan == 12) { + GPS = buf_w32(&instance->BEHa[39]); + MSL = buf_w32(&instance->BEHa[43]); + } else { + GPS = buf_w32(&instance->BEHa[23]); + MSL = buf_w32(&instance->BEHa[27]); + } + instance->dH = GPS - MSL; + instance->dH /= 100.; + + /* if MSL is not set, the calculation is meaningless */ + + if (MSL) { /* not set ! */ + sprintf(Msg, "dH = (GPS - MSL) = %.2fm", instance->dH); + record_clock_stats(&(instance->peer->srcadr), Msg); + } +} + + + +/* + * try loading Almanac from shmem (where it was copied from shmem_old + */ + +static void +oncore_load_almanac( + struct instance *instance + ) +{ + u_char *cp, Cmd[20]; + int n; + struct timeval tv; + struct tm *tm; + + if (!instance->shmem) + return; + +#if 1 + for (cp=instance->shmem+4; (n = 256*(*(cp-3)) + *(cp-2)); cp+=(n+3)) { + if (!strncmp(cp, "@@Cb", 4) && + oncore_checksum_ok(cp, 33) && + (*(cp+4) == 4 || *(cp+4) == 5)) { + write(instance->ttyfd, cp, n); +#if 1 + oncore_print_Cb(instance, cp); +#endif + } + } +#else +/************DEBUG************/ + for (cp=instance->shmem+4; (n = 256*(*(cp-3)) + *(cp-2)); cp+=(n+3)) { + char Msg[160]; + + sprintf(Msg, "See %c%c%c%c %d", *(cp), *(cp+1), *(cp+2), *(cp+3), *(cp+4)); + record_clock_stats(&(instance->peer->srcadr), Msg); + + if (!strncmp(cp, "@@Cb", 4)) { + oncore_print_Cb(instance, cp); + if (oncore_checksum_ok(cp, 33)) { + if (*(cp+4) == 4 || *(cp+4) == 5) { + record_clock_stats(&(instance->peer->srcadr), "GOOD SF"); + write(instance->ttyfd, cp, n); + } else + record_clock_stats(&(instance->peer->srcadr), "BAD SF"); + } else + record_clock_stats(&(instance->peer->srcadr), "BAD CHECKSUM"); + } + } +/************DEBUG************/ +#endif + + /* Must load position and time or the Almanac doesn't do us any good */ + + if (!instance->posn_set) { /* if we input a posn use it, else from SHMEM */ + record_clock_stats(&(instance->peer->srcadr), "Loading Posn from SHMEM"); + for (cp=instance->shmem+4; (n = 256*(*(cp-3)) + *(cp-2)); cp+=(n+3)) { + if ((instance->chan == 6 && (!strncmp(cp, "@@Ba", 4) && oncore_checksum_ok(cp, 68))) || + (instance->chan == 8 && (!strncmp(cp, "@@Ea", 4) && oncore_checksum_ok(cp, 76))) || + (instance->chan == 12 && (!strncmp(cp, "@@Ha", 4) && oncore_checksum_ok(cp, 154)))) { + int ii, jj, kk; + + instance->posn_set = 1; + ii = buf_w32(cp + 15); + jj = buf_w32(cp + 19); + kk = buf_w32(cp + 23); +{ +char Msg[160]; +sprintf(Msg, "SHMEM posn = %d (%d, %d, %d)", cp-instance->shmem, ii, jj, kk); +record_clock_stats(&(instance->peer->srcadr), Msg); +} + if (ii != 0 || jj != 0 || kk != 0) { /* phk asked for this test */ + instance->ss_lat = ii; + instance->ss_long = jj; + instance->ss_ht = kk; + } + } + } + } + oncore_set_posn(instance); + + /* and set time to time from Computer clock */ + + gettimeofday(&tv, 0); + tm = gmtime((const time_t *) &tv.tv_sec); +#if 1 + { + char Msg[160]; + sprintf(Msg, "DATE %d %d %d, %d %d %d", 1900+tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + record_clock_stats(&(instance->peer->srcadr), Msg); + } +#endif + if (instance->chan == 12) { + memcpy(Cmd, oncore_cmd_Gb, (size_t) sizeof(oncore_cmd_Gb)); + Cmd[-2+4] = tm->tm_mon; + Cmd[-2+5] = tm->tm_mday; + Cmd[-2+6] = (1900+tm->tm_year)/256; + Cmd[-2+7] = (1900+tm->tm_year)%256; + Cmd[-2+8] = tm->tm_hour; + Cmd[-2+9] = tm->tm_min; + Cmd[-2+10] = tm->tm_sec; + Cmd[-2+11] = 0; + Cmd[-2+12] = 0; + Cmd[-2+13] = 0; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Gb)); + } else { + /* First set GMT offset to zero */ + + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ab, sizeof(oncore_cmd_Ab)); + + memcpy(Cmd, oncore_cmd_Ac, (size_t) sizeof(oncore_cmd_Ac)); + Cmd[-2+4] = tm->tm_mon; + Cmd[-2+5] = tm->tm_mday; + Cmd[-2+6] = (1900+tm->tm_year)/256; + Cmd[-2+7] = (1900+tm->tm_year)%256; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ac)); + + memcpy(Cmd, oncore_cmd_Aa, (size_t) sizeof(oncore_cmd_Aa)); + Cmd[-2+4] = tm->tm_hour; + Cmd[-2+5] = tm->tm_min; + Cmd[-2+6] = tm->tm_sec; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Aa)); + } + + record_clock_stats(&(instance->peer->srcadr), "Setting Posn and Time after Loading Almanac"); +} + + + +/* Almanac data input */ + +static void +oncore_print_Cb( + struct instance *instance, + u_char *cp + ) +{ +#if 0 + int ii; + char Msg[160]; + + printf("DEBUG: See: %c%c%c%c\n", *(cp), *(cp+1), *(cp+2), *(cp+3)); + printf("DEBUG: Cb: [%d,%d]", *(cp+4), *(cp+5)); + for(ii=0; ii<33; ii++) + printf(" %d", *(cp+ii)); + printf("\n"); + + sprintf(Msg, "Debug: Cb: [%d,%d]", *(cp+4), *(cp+5)); + record_clock_stats(&(instance->peer->srcadr), Msg); +#endif +} + + +#if 0 +static void +oncore_print_array( + u_char *cp, + int n + ) +{ + int jj, i, j, nn; + + nn = 0; + printf("\nTOP\n"); + jj = n/16; + for (j=0; j<jj; j++) { + printf("%4d: ", nn); + nn += 16; + for (i=0; i<16; i++) + printf(" %o", *cp++); + printf("\n"); + } +} +#endif + + +static void +oncore_print_posn( + struct instance *instance + ) +{ + char Msg[120], ew, ns; + double xd, xm, xs, yd, ym, ys, hm, hft; + int idx, idy, is, imx, imy; + long lat, lon; + + record_clock_stats(&(instance->peer->srcadr), "Posn:"); + ew = 'E'; + lon = instance->ss_long; + if (lon < 0) { + ew = 'W'; + lon = -lon; + } + + ns = 'N'; + lat = instance->ss_lat; + if (lat < 0) { + ns = 'S'; + lat = -lat; + } + + hm = instance->ss_ht/100.; + hft= hm/0.3048; + + xd = lat/3600000.; /* lat, lon in int msec arc, ht in cm. */ + yd = lon/3600000.; + sprintf(Msg, "Lat = %c %11.7fdeg, Long = %c %11.7fdeg, Alt = %5.2fm (%5.2fft) GPS", ns, xd, ew, yd, hm, hft); + record_clock_stats(&(instance->peer->srcadr), Msg); + + idx = xd; + idy = yd; + imx = lat%3600000; + imy = lon%3600000; + xm = imx/60000.; + ym = imy/60000.; + sprintf(Msg, + "Lat = %c %3ddeg %7.4fm, Long = %c %3ddeg %8.5fm, Alt = %7.2fm (%7.2fft) GPS", ns, idx, xm, ew, idy, ym, hm, hft); + record_clock_stats(&(instance->peer->srcadr), Msg); + + imx = xm; + imy = ym; + is = lat%60000; + xs = is/1000.; + is = lon%60000; + ys = is/1000.; + sprintf(Msg, + "Lat = %c %3ddeg %2dm %5.2fs, Long = %c %3ddeg %2dm %5.2fs, Alt = %7.2fm (%7.2fft) GPS", ns, idx, imx, xs, ew, idy, imy, ys, hm, hft); + record_clock_stats(&(instance->peer->srcadr), Msg); +} + + + +/* + * write message to Oncore. + */ + +static void +oncore_sendmsg( + int fd, + u_char *ptr, + size_t len + ) +{ + u_char cs = 0; + + if (debug > 4) + printf("ONCORE: Send @@%c%c %d\n", ptr[0], ptr[1], (int) len); + write(fd, "@@", (size_t) 2); + write(fd, ptr, len); + while (len--) + cs ^= *ptr++; + write(fd, &cs, (size_t) 1); + write(fd, "\r\n", (size_t) 2); +} + + + +static void +oncore_set_posn( + struct instance *instance + ) +{ + int mode; + char Cmd[20]; + + /* Turn OFF position hold, it needs to be off to set position (for some units), + will get set ON in @@Ea later */ + + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd0, sizeof(oncore_cmd_Gd0)); /* (12) */ + else { + oncore_sendmsg(instance->ttyfd, oncore_cmd_At0, sizeof(oncore_cmd_At0)); /* (6/8) */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av0, sizeof(oncore_cmd_Av0)); /* (6/8) */ + } + + mode = instance->init_type; + + if (mode != 0) { /* first set posn hold position */ + memcpy(Cmd, oncore_cmd_As, (size_t) sizeof(oncore_cmd_As)); /* don't modify static variables */ + w32_buf(&Cmd[-2+4], (int) instance->ss_lat); + w32_buf(&Cmd[-2+8], (int) instance->ss_long); + w32_buf(&Cmd[-2+12], (int) instance->ss_ht); + Cmd[-2+16] = 0; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_As)); /* posn hold 3D posn (6/8/12) */ + + memcpy(Cmd, oncore_cmd_Au, (size_t) sizeof(oncore_cmd_Au)); + w32_buf(&Cmd[-2+4], (int) instance->ss_ht); + Cmd[-2+8] = 0; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Au)); /* altitude hold (6/8/12 not UT, M12T) */ + + /* next set current position */ + + if (instance->chan == 12) { + memcpy(Cmd, oncore_cmd_Ga, (size_t) sizeof(oncore_cmd_Ga)); + w32_buf(&Cmd[-2+4], (int) instance->ss_lat); + w32_buf(&Cmd[-2+8], (int) instance->ss_long); + w32_buf(&Cmd[-2+12],(int) instance->ss_ht); + Cmd[-2+16] = 0; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ga)); /* 3d posn (12) */ + } else { + memcpy(Cmd, oncore_cmd_Ad, (size_t) sizeof(oncore_cmd_Ad)); + w32_buf(&Cmd[-2+4], (int) instance->ss_lat); + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ad)); /* lat (6/8) */ + + memcpy(Cmd, oncore_cmd_Ae, (size_t) sizeof(oncore_cmd_Ae)); + w32_buf(&Cmd[-2+4], (int) instance->ss_long); + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Ae)); /* long (6/8) */ + + memcpy(Cmd, oncore_cmd_Af, (size_t) sizeof(oncore_cmd_Af)); + w32_buf(&Cmd[-2+4], (int) instance->ss_ht); + Cmd[-2+8] = 0; + oncore_sendmsg(instance->ttyfd, Cmd, sizeof(oncore_cmd_Af)); /* ht (6/8) */ + } + + /* Finally, turn on position hold */ + + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd1, sizeof(oncore_cmd_Gd1)); + else + oncore_sendmsg(instance->ttyfd, oncore_cmd_At1, sizeof(oncore_cmd_At1)); + } +} + + + +static void +oncore_set_traim( + struct instance *instance + ) +{ + char Msg[160]; + + if (instance->traim_in != -1) /* set in Input */ + instance->traim = instance->traim_in; + else + instance->traim = instance->traim_ck; + + sprintf(Msg, "Input says TRAIM = %d", instance->traim_in); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Model # says TRAIM = %d", instance->traim_id); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Testing says TRAIM = %d", instance->traim_ck); + record_clock_stats(&(instance->peer->srcadr), Msg); + sprintf(Msg, "Using TRAIM = %d", instance->traim); + record_clock_stats(&(instance->peer->srcadr), Msg); + + if (instance->traim_ck == 1 && instance->traim == 0) { + /* if it should be off, and I turned it on during testing, + then turn it off again */ + if (instance->chan == 6) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bnx, sizeof(oncore_cmd_Bnx)); + else if (instance->chan == 8) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Enx, sizeof(oncore_cmd_Enx)); + else /* chan == 12 */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ge0, sizeof(oncore_cmd_Ge0)); + } +} + + + +/* + * if SHMEM active, every 15s, steal one 'tick' to get 2D or 3D posn. + */ + +static void +oncore_shmem_get_3D( + struct instance *instance + ) +{ + if (instance->pp->second%15 == 3) { /* start the sequence */ /* by changing mode */ + instance->shmem_reset = 1; + if (instance->chan == 12) { + if (instance->shmem_Posn == 2) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd2, sizeof(oncore_cmd_Gd2)); /* 2D */ + else + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd0, sizeof(oncore_cmd_Gd0)); /* 3D */ + } else { + if (instance->saw_At) { /* out of 0D -> 3D mode */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_At0, sizeof(oncore_cmd_At0)); + if (instance->shmem_Posn == 2) /* 3D -> 2D mode */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av1, sizeof(oncore_cmd_Av1)); + } else + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av0, sizeof(oncore_cmd_Av0)); + } + } else if (instance->shmem_reset || (instance->mode != MODE_0D)) { + instance->shmem_reset = 0; + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gd1, sizeof(oncore_cmd_Gd1)); /* 0D */ + else { + if (instance->saw_At) { + if (instance->mode == MODE_2D) /* 2D -> 3D or 0D mode */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av0, sizeof(oncore_cmd_Av0)); + oncore_sendmsg(instance->ttyfd, oncore_cmd_At1, sizeof(oncore_cmd_At1)); /* to 0D mode */ + } else + oncore_sendmsg(instance->ttyfd, oncore_cmd_Av1, sizeof(oncore_cmd_Av1)); + } + } +} + + + +/* + * Here we do the Software SiteSurvey. + * We have to average our own position for the Position Hold Mode + * We use Heights from the GPS ellipsoid. + * We check for the END of either HW or SW SiteSurvey. + */ + +static void +oncore_ss( + struct instance *instance + ) +{ + char *cp, Msg[160]; + double lat, lon, ht; + + + if (instance->site_survey == ONCORE_SS_HW) { + + /* + * Check to see if Hardware SiteSurvey has Finished. + */ + + if ((instance->chan == 8 && !(instance->BEHa[37] & 0x20)) || + (instance->chan == 12 && !(instance->BEHa[130] & 0x10))) { + record_clock_stats(&(instance->peer->srcadr), "Now in 0D mode"); + + if (instance->chan == 12) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gax, sizeof(oncore_cmd_Gax)); + else + oncore_sendmsg(instance->ttyfd, oncore_cmd_Asx, sizeof(oncore_cmd_Asx)); + + cp = "SSstate = ONCORE_SS_DONE"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->site_survey = ONCORE_SS_DONE; + } + } else { + /* + * Must be a Software Site Survey. + */ + + if (instance->rsm.bad_fix) /* Not if poor geometry or less than 3 sats */ + return; + + if (instance->mode != MODE_3D) /* Use only 3D Fixes */ + return; + + instance->ss_lat += buf_w32(&instance->BEHa[15]); + instance->ss_long += buf_w32(&instance->BEHa[19]); + instance->ss_ht += buf_w32(&instance->BEHa[23]); /* GPS ellipsoid */ + instance->ss_count++; + + if (instance->ss_count != POS_HOLD_AVERAGE) + return; + + instance->ss_lat /= POS_HOLD_AVERAGE; + instance->ss_long /= POS_HOLD_AVERAGE; + instance->ss_ht /= POS_HOLD_AVERAGE; + + sprintf(Msg, "Surveyed posn: lat %.3f (mas) long %.3f (mas) ht %.3f (cm)", + instance->ss_lat, instance->ss_long, instance->ss_ht); + record_clock_stats(&(instance->peer->srcadr), Msg); + lat = instance->ss_lat/3600000.; + lon = instance->ss_long/3600000.; + ht = instance->ss_ht/100; + sprintf(Msg, "Surveyed posn: lat %.7f (deg) long %.7f (deg) ht %.2f (m)", + lat, lon, ht); + record_clock_stats(&(instance->peer->srcadr), Msg); + + oncore_set_posn(instance); + + record_clock_stats(&(instance->peer->srcadr), "Now in 0D mode"); + + cp = "SSstate = ONCORE_SS_DONE"; + record_clock_stats(&(instance->peer->srcadr), cp); + instance->site_survey = ONCORE_SS_DONE; + } +} + + + +static int +oncore_wait_almanac( + struct instance *instance + ) +{ + if (instance->rsm.bad_almanac) { + if (debug) + printf("ONCORE[%d]: waiting for almanac\n", instance->unit); + + /* + * If we get here (first time) then we don't have an almanac in memory. + * Check if we have a SHMEM, and if so try to load whatever is there. + */ + + if (!instance->almanac_from_shmem) { + instance->almanac_from_shmem = 1; + oncore_load_almanac(instance); + } + return(1); + } else { /* Here we have the Almanac, we will be starting the @@Bn/@@En/@@Hn + commands, and can finally check for TRAIM. Again, we set a delay + (5sec) and wait for things to settle down */ + + if (instance->chan == 6) + oncore_sendmsg(instance->ttyfd, oncore_cmd_Bn, sizeof(oncore_cmd_Bn)); + else if (instance->chan == 8) + oncore_sendmsg(instance->ttyfd, oncore_cmd_En, sizeof(oncore_cmd_En)); + else if (instance->chan == 12) { + oncore_sendmsg(instance->ttyfd, oncore_cmd_Gc, sizeof(oncore_cmd_Gc)); /* 1PPS on, continuous */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Ge, sizeof(oncore_cmd_Ge)); /* TRAIM on */ + oncore_sendmsg(instance->ttyfd, oncore_cmd_Hn, sizeof(oncore_cmd_Hn)); /* TRAIM status 1/s */ + } + instance->traim_delay = 1; + + record_clock_stats(&(instance->peer->srcadr), "Have now loaded an ALMANAC"); + + instance->o_state = ONCORE_RUN; + record_clock_stats(&(instance->peer->srcadr), "state = ONCORE_RUN"); + } + return(0); +} + + + +#else +int refclock_oncore_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_palisade.c b/ntpd/refclock_palisade.c new file mode 100644 index 0000000..897221b --- /dev/null +++ b/ntpd/refclock_palisade.c @@ -0,0 +1,954 @@ +/* + * This software was developed by the Software and Component Technologies + * group of Trimble Navigation, Ltd. + * + * Copyright (c) 1997, 1998, 1999, 2000 Trimble Navigation Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Trimble Navigation, Ltd. + * 4. The name of Trimble Navigation Ltd. may not be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY TRIMBLE NAVIGATION LTD. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL TRIMBLE NAVIGATION LTD. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * refclock_palisade - clock driver for the Trimble Palisade GPS + * timing receiver + * + * For detailed information on this program, please refer to the html + * Refclock 29 page accompanying the NTP distribution. + * + * for questions / bugs / comments, contact: + * sven_dietrich@trimble.com + * + * Sven-Thorsten Dietrich + * 645 North Mary Avenue + * Post Office Box 3642 + * Sunnyvale, CA 94088-3642 + * + * Version 2.45; July 14, 1999 + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(SYS_WINNT) +#undef close +#define close closesocket +#endif + +#if defined(REFCLOCK) && (defined(PALISADE) || defined(CLOCK_PALISADE)) + +#include "refclock_palisade.h" +/* Table to get from month to day of the year */ +const int days_of_year [12] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 +}; + +#ifdef DEBUG +const char * Tracking_Status[15][15] = { + { "Doing Fixes\0" }, { "Good 1SV\0" }, { "Approx. 1SV\0" }, + {"Need Time\0" }, { "Need INIT\0" }, { "PDOP too High\0" }, + { "Bad 1SV\0" }, { "0SV Usable\0" }, { "1SV Usable\0" }, + { "2SV Usable\0" }, { "3SV Usable\0" }, { "No Integrity\0" }, + { "Diff Corr\0" }, { "Overdet Clock\0" }, { "Invalid\0" } }; +#endif + +/* + * Transfer vector + */ +struct refclock refclock_palisade = { + palisade_start, /* start up driver */ + palisade_shutdown, /* shut down driver */ + palisade_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + +int day_of_year P((char *dt)); + +/* Extract the clock type from the mode setting */ +#define CLK_TYPE(x) ((int)(((x)->ttl) & 0x7F)) + +/* Supported clock types */ +#define CLK_TRIMBLE 0 /* Trimble Palisade */ +#define CLK_PRAECIS 1 /* Endrun Technologies Praecis */ + +int praecis_msg; +static void praecis_parse(struct recvbuf *rbufp, struct peer *peer); + +/* + * palisade_start - open the devices and initialize data for processing + */ +static int +palisade_start ( +#ifdef PALISADE + unit, peer + ) + int unit; + struct peer *peer; +#else /* ANSI */ + int unit, + struct peer *peer + ) +#endif +{ + struct palisade_unit *up; + struct refclockproc *pp; + int fd; + char gpsdev[20]; + + struct termios tio; +#ifdef SYS_WINNT + (void) sprintf(gpsdev, "COM%d:", unit); +#else + (void) sprintf(gpsdev, DEVICE, unit); +#endif + /* + * Open serial port. + */ +#if defined PALISADE + fd = open(gpsdev, O_RDWR +#ifdef O_NONBLOCK + | O_NONBLOCK +#endif + ); +#else /* NTP 4.x */ + fd = refclock_open(gpsdev, SPEED232, LDISC_RAW); +#endif + if (fd <= 0) { +#ifdef DEBUG + printf("Palisade(%d) start: open %s failed\n", unit, gpsdev); +#endif + return 0; + } + + msyslog(LOG_NOTICE, "Palisade(%d) fd: %d dev: %s", unit, fd, + gpsdev); + +#if defined PALISADE + tio.c_cflag = (CS8|CLOCAL|CREAD|PARENB|PARODD); + tio.c_iflag = (IGNBRK); + tio.c_oflag = (0); + tio.c_lflag = (0); + + if (cfsetispeed(&tio, SPEED232) == -1) { + msyslog(LOG_ERR,"Palisade(%d) cfsetispeed(fd, &tio): %m",unit); +#ifdef DEBUG + printf("Palisade(%d) cfsetispeed(fd, &tio)\n",unit); +#endif + return 0; + } + if (cfsetospeed(&tio, SPEED232) == -1) { +#ifdef DEBUG + printf("Palisade(%d) cfsetospeed(fd, &tio)\n",unit); +#endif + msyslog(LOG_ERR,"Palisade(%d) cfsetospeed(fd, &tio): %m",unit); + return 0; + } +#else /* NTP 4.x */ + if (tcgetattr(fd, &tio) < 0) { + msyslog(LOG_ERR, + "Palisade(%d) tcgetattr(fd, &tio): %m",unit); +#ifdef DEBUG + printf("Palisade(%d) tcgetattr(fd, &tio)\n",unit); +#endif + return (0); + } + + tio.c_cflag |= (PARENB|PARODD); + tio.c_iflag &= ~ICRNL; +#endif /* NTP 4.x */ + + if (tcsetattr(fd, TCSANOW, &tio) == -1) { + msyslog(LOG_ERR, "Palisade(%d) tcsetattr(fd, &tio): %m",unit); +#ifdef DEBUG + printf("Palisade(%d) tcsetattr(fd, &tio)\n",unit); +#endif + return 0; + } + + /* + * Allocate and initialize unit structure + */ + up = (struct palisade_unit *) emalloc(sizeof(struct palisade_unit)); + + if (!(up)) { + msyslog(LOG_ERR, "Palisade(%d) emalloc: %m",unit); +#ifdef DEBUG + printf("Palisade(%d) emalloc\n",unit); +#endif + (void) close(fd); + return (0); + } + + memset((char *)up, 0, sizeof(struct palisade_unit)); + + up->type = CLK_TYPE(peer); + switch (up->type) { + case CLK_TRIMBLE: + /* Normal mode, do nothing */ + break; + case CLK_PRAECIS: + msyslog(LOG_NOTICE, "Palisade(%d) Praecis mode enabled\n",unit); + break; + default: + msyslog(LOG_NOTICE, "Palisade(%d) mode unknown\n",unit); + break; + } + + pp = peer->procptr; + pp->io.clock_recv = palisade_io; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { +#ifdef DEBUG + printf("Palisade(%d) io_addclock\n",unit); +#endif + (void) close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + pp->unitptr = (caddr_t)up; + pp->clockdesc = DESCRIPTION; + + peer->precision = PRECISION; + peer->sstclktype = CTL_SST_TS_UHF; + peer->minpoll = TRMB_MINPOLL; + peer->maxpoll = TRMB_MAXPOLL; + memcpy((char *)&pp->refid, REFID, 4); + + up->leap_status = 0; + up->unit = (short) unit; + up->rpt_status = TSIP_PARSED_EMPTY; + up->rpt_cnt = 0; + + return 1; +} + + +/* + * palisade_shutdown - shut down the clock + */ +static void +palisade_shutdown ( +#ifdef PALISADE + unit, peer + ) + int unit; + struct peer *peer; +#else /* ANSI */ + int unit, + struct peer *peer + ) +#endif +{ + struct palisade_unit *up; + struct refclockproc *pp; + pp = peer->procptr; + up = (struct palisade_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + + +/* + * unpack_date - get day and year from date + */ +int +day_of_year ( +#ifdef PALISADE + dt + ) + char * dt; +#else + char * dt + ) +#endif +{ + int day, mon, year; + + mon = dt[1]; + /* Check month is inside array bounds */ + if ((mon < 1) || (mon > 12)) + return -1; + + day = dt[0] + days_of_year[mon - 1]; + year = getint((u_char *) (dt + 2)); + + if ( !(year % 4) && ((year % 100) || + (!(year % 100) && !(year%400))) + &&(mon > 2)) + day ++; /* leap year and March or later */ + + return day; +} + + +/* + * TSIP_decode - decode the TSIP data packets + */ +int +TSIP_decode ( +#ifdef PALISADE + peer + ) + struct peer *peer; +#else + struct peer *peer + ) +#endif +{ + int st; + long secint; + double secs; + double secfrac; + unsigned short event = 0; + + struct palisade_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct palisade_unit *)pp->unitptr; + + /* + * Check the time packet, decode its contents. + * If the timecode has invalid length or is not in + * proper format, declare bad format and exit. + */ + + if ((up->rpt_buf[0] == (char) 0x41) || + (up->rpt_buf[0] == (char) 0x46) || + (up->rpt_buf[0] == (char) 0x54) || + (up->rpt_buf[0] == (char) 0x4B) || + (up->rpt_buf[0] == (char) 0x6D)) { + + /* standard time packet - GPS time and GPS week number */ +#ifdef DEBUG + printf("Palisade Port B packets detected. Connect to Port A\n"); +#endif + + return 0; + } + + /* + * We cast both to u_char to as 0x8f uses the sign bit on a char + */ + if ((u_char) up->rpt_buf[0] == (u_char) 0x8f) { + /* + * Superpackets + */ + event = (unsigned short) (getint((u_char *) &mb(1)) & 0xffff); + if (!((pp->sloppyclockflag & CLK_FLAG2) || event)) + /* Ignore Packet */ + return 0; + + switch (mb(0) & 0xff) { + int GPS_UTC_Offset; + case PACKET_8F0B: + + if (up->polled <= 0) + return 0; + + if (up->rpt_cnt != LENCODE_8F0B) /* check length */ + break; + +#ifdef DEBUG +if (debug > 1) { + int ts; + double lat, lon, alt; + lat = getdbl((u_char *) &mb(42)) * R2D; + lon = getdbl((u_char *) &mb(50)) * R2D; + alt = getdbl((u_char *) &mb(58)); + + printf("TSIP_decode: unit %d: Latitude: %03.4f Longitude: %03.4f Alt: %05.2f m\n", + up->unit, lat,lon,alt); + printf("TSIP_decode: unit %d: Sats:", up->unit); + for (st = 66, ts = 0; st <= 73; st++) if (mb(st)) { + if (mb(st) > 0) ts++; + printf(" %02d", mb(st)); + } + printf(" : Tracking %d\n", ts); + } +#endif + + GPS_UTC_Offset = getint((u_char *) &mb(16)); + if (GPS_UTC_Offset == 0) { /* Check UTC offset */ +#ifdef DEBUG + printf("TSIP_decode: UTC Offset Unknown\n"); +#endif + break; + } + + secs = getdbl((u_char *) &mb(3)); + secint = (long) secs; + secfrac = secs - secint; /* 0.0 <= secfrac < 1.0 */ + + pp->nsec = (long) (secfrac * 1000000000); + + secint %= 86400; /* Only care about today */ + pp->hour = secint / 3600; + secint %= 3600; + pp->minute = secint / 60; + secint %= 60; + pp->second = secint % 60; + + if ((pp->day = day_of_year(&mb(11))) < 0) break; + + pp->year = getint((u_char *) &mb(13)); + +#ifdef DEBUG + if (debug > 1) + printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%06ld %02d/%02d/%04d UTC %02d\n", + up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, + pp->second, pp->nsec, mb(12), mb(11), pp->year, GPS_UTC_Offset); +#endif + /* Only use this packet when no + * 8F-AD's are being received + */ + + if (up->leap_status) { + up->leap_status = 0; + return 0; + } + + return 2; + break; + + case PACKET_NTP: + /* Palisade-NTP Packet */ + + if (up->rpt_cnt != LENCODE_NTP) /* check length */ + break; + + up->leap_status = mb(19); + + if (up->polled <= 0) + return 0; + + /* Check Tracking Status */ + st = mb(18); + if (st < 0 || st > 14) st = 14; + if ((st >= 2 && st <= 7) || st == 11 || st == 12) { +#ifdef DEBUG + printf("TSIP_decode: Not Tracking Sats : %s\n", + *Tracking_Status[st]); +#endif + refclock_report(peer, CEVNT_BADTIME); + up->polled = -1; + return 0; + break; + } + + if (up->leap_status & PALISADE_LEAP_PENDING) { + if (up->leap_status & PALISADE_UTC_TIME) + pp->leap = LEAP_ADDSECOND; + else + pp->leap = LEAP_DELSECOND; + } + else if (up->leap_status) + pp->leap = LEAP_NOWARNING; + + else { /* UTC flag is not set: + * Receiver may have been reset, and lost + * its UTC almanac data */ + pp->leap = LEAP_NOTINSYNC; +#ifdef DEBUG + printf("TSIP_decode: UTC Almanac unavailable: %d\n", + mb(19)); +#endif + refclock_report(peer, CEVNT_BADTIME); + up->polled = -1; + return 0; + } + + pp->nsec = (long) (getdbl((u_char *) &mb(3)) * 1000000); + + if ((pp->day = day_of_year(&mb(14))) < 0) + break; + pp->year = getint((u_char *) &mb(16)); + pp->hour = mb(11); + pp->minute = mb(12); + pp->second = mb(13); + +#ifdef DEBUG + if (debug > 1) +printf("TSIP_decode: unit %d: %02X #%d %02d:%02d:%02d.%06ld %02d/%02d/%04d UTC %02x %s\n", + up->unit, mb(0) & 0xff, event, pp->hour, pp->minute, + pp->second, pp->nsec, mb(15), mb(14), pp->year, + mb(19), *Tracking_Status[st]); +#endif + return 1; + break; + + default: + /* Ignore Packet */ + return 0; + } /* switch */ + }/* if 8F packets */ + + refclock_report(peer, CEVNT_BADREPLY); + up->polled = -1; +#ifdef DEBUG + printf("TSIP_decode: unit %d: bad packet %02x-%02x event %d len %d\n", + up->unit, up->rpt_buf[0] & 0xff, mb(0) & 0xff, + event, up->rpt_cnt); +#endif + return 0; +} + +/* + * palisade__receive - receive data from the serial interface + */ + +static void +palisade_receive ( +#ifdef PALISADE + peer + ) + struct peer * peer; +#else /* ANSI */ + struct peer * peer + ) +#endif +{ + struct palisade_unit *up; + struct refclockproc *pp; + + /* + * Initialize pointers and read the timecode and timestamp. + */ + pp = peer->procptr; + up = (struct palisade_unit *)pp->unitptr; + + if (! TSIP_decode(peer)) return; + + if (up->polled <= 0) + return; /* no poll pending, already received or timeout */ + + up->polled = 0; /* Poll reply received */ + pp->lencode = 0; /* clear time code */ +#ifdef DEBUG + if (debug) + printf( + "palisade_receive: unit %d: %4d %03d %02d:%02d:%02d.%06ld\n", + up->unit, pp->year, pp->day, pp->hour, pp->minute, + pp->second, pp->nsec); +#endif + + /* + * Process the sample + * Generate timecode: YYYY DoY HH:MM:SS.microsec + * report and process + */ + + (void) sprintf(pp->a_lastcode,"%4d %03d %02d:%02d:%02d.%06ld", + pp->year,pp->day,pp->hour,pp->minute, pp->second,pp->nsec); + pp->lencode = 24; + +#ifdef PALISADE + pp->lasttime = current_time; +#endif + if (!refclock_process(pp +#ifdef PALISADE + , PALISADE_SAMPLES, PALISADE_SAMPLES * 3 / 5 +#endif + )) { + refclock_report(peer, CEVNT_BADTIME); + +#ifdef DEBUG + printf("palisade_receive: unit %d: refclock_process failed!\n", + up->unit); +#endif + return; + } + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + +#ifdef DEBUG + if (debug) + printf("palisade_receive: unit %d: %s\n", + up->unit, prettydate(&pp->lastrec)); +#endif + pp->lastref = pp->lastrec; + refclock_receive(peer +#ifdef PALISADE + , &pp->offset, 0, pp->dispersion, + &pp->lastrec, &pp->lastrec, pp->leap +#endif + ); +} + + +/* + * palisade_poll - called by the transmit procedure + * + */ +static void +palisade_poll ( +#ifdef PALISADE + unit, peer + ) + int unit; + struct peer *peer; +#else + int unit, + struct peer *peer + ) +#endif +{ + struct palisade_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct palisade_unit *)pp->unitptr; + + pp->polls++; + if (up->polled > 0) /* last reply never arrived or error */ + refclock_report(peer, CEVNT_TIMEOUT); + + up->polled = 2; /* synchronous packet + 1 event */ + +#ifdef DEBUG + if (debug) + printf("palisade_poll: unit %d: polling %s\n", unit, + (pp->sloppyclockflag & CLK_FLAG2) ? + "synchronous packet" : "event"); +#endif + + if (pp->sloppyclockflag & CLK_FLAG2) + return; /* using synchronous packet input */ + + if(up->type == CLK_PRAECIS) { + if(write(peer->procptr->io.fd,"SPSTAT\r\n",8) < 0) + msyslog(LOG_ERR, "Palisade(%d) write: %m:",unit); + else { + praecis_msg = 1; + return; + } + } + + if (HW_poll(pp) < 0) + refclock_report(peer, CEVNT_FAULT); +} + +static void +praecis_parse(struct recvbuf *rbufp, struct peer *peer) +{ + static char buf[100]; + static int p = 0; + struct refclockproc *pp; + + pp = peer->procptr; + + memcpy(buf+p,rbufp->recv_space.X_recv_buffer, rbufp->recv_length); + p += rbufp->recv_length; + + if(buf[p-2] == '\r' && buf[p-1] == '\n') { + buf[p-2] = '\0'; + record_clock_stats(&peer->srcadr, buf); + + p = 0; + praecis_msg = 0; + + if (HW_poll(pp) < 0) + refclock_report(peer, CEVNT_FAULT); + + } +} + +static void +palisade_io ( +#ifdef PALISADE + rbufp + ) + struct recvbuf *rbufp; +#else /* ANSI */ + struct recvbuf *rbufp + ) +#endif +{ + /* + * Initialize pointers and read the timecode and timestamp. + */ + struct palisade_unit *up; + struct refclockproc *pp; + struct peer *peer; + + char * c, * d; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct palisade_unit *)pp->unitptr; + + if(up->type == CLK_PRAECIS) { + if(praecis_msg) { + praecis_parse(rbufp,peer); + return; + } + } + + c = (char *) &rbufp->recv_space; + d = c + rbufp->recv_length; + + while (c != d) { + + /* Build time packet */ + switch (up->rpt_status) { + + case TSIP_PARSED_DLE_1: + switch (*c) + { + case 0: + case DLE: + case ETX: + up->rpt_status = TSIP_PARSED_EMPTY; + break; + + default: + up->rpt_status = TSIP_PARSED_DATA; + /* save packet ID */ + up->rpt_buf[0] = *c; + break; + } + break; + + case TSIP_PARSED_DATA: + if (*c == DLE) + up->rpt_status = TSIP_PARSED_DLE_2; + else + mb(up->rpt_cnt++) = *c; + break; + + case TSIP_PARSED_DLE_2: + if (*c == DLE) { + up->rpt_status = TSIP_PARSED_DATA; + mb(up->rpt_cnt++) = + *c; + } + else if (*c == ETX) + up->rpt_status = TSIP_PARSED_FULL; + else { + /* error: start new report packet */ + up->rpt_status = TSIP_PARSED_DLE_1; + up->rpt_buf[0] = *c; + } + break; + + case TSIP_PARSED_FULL: + case TSIP_PARSED_EMPTY: + default: + if ( *c != DLE) + up->rpt_status = TSIP_PARSED_EMPTY; + else + up->rpt_status = TSIP_PARSED_DLE_1; + break; + } + + c++; + + if (up->rpt_status == TSIP_PARSED_DLE_1) { + up->rpt_cnt = 0; + if (pp->sloppyclockflag & CLK_FLAG2) + /* stamp it */ + get_systime(&pp->lastrec); + } + else if (up->rpt_status == TSIP_PARSED_EMPTY) + up->rpt_cnt = 0; + + else if (up->rpt_cnt > BMAX) + up->rpt_status =TSIP_PARSED_EMPTY; + + if (up->rpt_status == TSIP_PARSED_FULL) + palisade_receive(peer); + + } /* while chars in buffer */ +} + + +/* + * Trigger the Palisade's event input, which is driven off the RTS + * + * Take a system time stamp to match the GPS time stamp. + * + */ +long +HW_poll ( +#ifdef PALISADE + pp /* pointer to unit structure */ + ) + struct refclockproc * pp; /* pointer to unit structure */ +#else + struct refclockproc * pp /* pointer to unit structure */ + ) +#endif +{ + int x; /* state before & after RTS set */ + struct palisade_unit *up; + + up = (struct palisade_unit *) pp->unitptr; + + /* read the current status, so we put things back right */ + if (ioctl(pp->io.fd, TIOCMGET, &x) < 0) { +#ifdef DEBUG + if (debug) + printf("Palisade HW_poll: unit %d: GET %s\n", up->unit, strerror(errno)); +#endif + msyslog(LOG_ERR, "Palisade(%d) HW_poll: ioctl(fd,GET): %m", + up->unit); + return -1; + } + + x |= TIOCM_RTS; /* turn on RTS */ + + /* Edge trigger */ + if (ioctl(pp->io.fd, TIOCMSET, &x) < 0) { +#ifdef DEBUG + if (debug) + printf("Palisade HW_poll: unit %d: SET \n", up->unit); +#endif + msyslog(LOG_ERR, + "Palisade(%d) HW_poll: ioctl(fd, SET, RTS_on): %m", + up->unit); + return -1; + } + + x &= ~TIOCM_RTS; /* turn off RTS */ + + /* poll timestamp */ + get_systime(&pp->lastrec); + + if (ioctl(pp->io.fd, TIOCMSET, &x) == -1) { +#ifdef DEBUG + if (debug) + printf("Palisade HW_poll: unit %d: UNSET \n", up->unit); +#endif + msyslog(LOG_ERR, + "Palisade(%d) HW_poll: ioctl(fd, UNSET, RTS_off): %m", + up->unit); + return -1; + } + + return 0; +} + +#if 0 /* unused */ +/* + * this 'casts' a character array into a float + */ +float +getfloat ( +#ifdef PALISADE + bp + ) + u_char *bp; +#else + u_char *bp + ) +#endif +{ + float sval; +#ifdef WORDS_BIGENDIAN + ((char *) &sval)[0] = *bp++; + ((char *) &sval)[1] = *bp++; + ((char *) &sval)[2] = *bp++; + ((char *) &sval)[3] = *bp++; +#else + ((char *) &sval)[3] = *bp++; + ((char *) &sval)[2] = *bp++; + ((char *) &sval)[1] = *bp++; + ((char *) &sval)[0] = *bp; +#endif /* ! XNTP_BIG_ENDIAN */ + return sval; +} +#endif + +/* + * this 'casts' a character array into a double + */ +double +getdbl ( +#ifdef PALISADE + bp + ) + u_char *bp; +#else + u_char *bp + ) +#endif +{ + double dval; +#ifdef WORDS_BIGENDIAN + ((char *) &dval)[0] = *bp++; + ((char *) &dval)[1] = *bp++; + ((char *) &dval)[2] = *bp++; + ((char *) &dval)[3] = *bp++; + ((char *) &dval)[4] = *bp++; + ((char *) &dval)[5] = *bp++; + ((char *) &dval)[6] = *bp++; + ((char *) &dval)[7] = *bp; +#else + ((char *) &dval)[7] = *bp++; + ((char *) &dval)[6] = *bp++; + ((char *) &dval)[5] = *bp++; + ((char *) &dval)[4] = *bp++; + ((char *) &dval)[3] = *bp++; + ((char *) &dval)[2] = *bp++; + ((char *) &dval)[1] = *bp++; + ((char *) &dval)[0] = *bp; +#endif /* ! XNTP_BIG_ENDIAN */ + return dval; +} + +/* + * cast a 16 bit character array into a short (16 bit) int + */ +short +getint ( +#ifdef PALISADE + bp + ) + u_char *bp; +#else + u_char *bp + ) +#endif +{ +return (short) (bp[1] + (bp[0] << 8)); +} + +#else +int refclock_palisade_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_palisade.h b/ntpd/refclock_palisade.h new file mode 100644 index 0000000..7e1ed49 --- /dev/null +++ b/ntpd/refclock_palisade.h @@ -0,0 +1,168 @@ +/* + * This software was developed by the Software and Component Technologies + * group of Trimble Navigation, Ltd. + * + * Copyright (c) 1997, 1998, 1999, 2000 Trimble Navigation Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Trimble Navigation, Ltd. + * 4. The name of Trimble Navigation Ltd. may not be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY TRIMBLE NAVIGATION LTD. ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL TRIMBLE NAVIGATION LTD. BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * refclock_palisade - clock driver for the Trimble Palisade GPS + * timing receiver + * + * For detailed information on this program, please refer to the html + * Refclock 29 page accompanying the NTP distribution. + * + * for questions / bugs / comments, contact: + * sven_dietrich@trimble.com + * + * Sven-Thorsten Dietrich + * 645 North Mary Avenue + * Post Office Box 3642 + * Sunnyvale, CA 94088-3642 + * + */ + +#ifndef _REFCLOCK_PALISADE_H +#define _REFCLOCK_PALISADE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined HAVE_SYS_MODEM_H +#include <sys/modem.h> +#define TIOCMSET MCSETA +#define TIOCMGET MCGETA +#define TIOCM_RTS MRTS +#endif + +#ifdef HAVE_TERMIOS_H +# ifdef TERMIOS_NEEDS__SVID3 +# define _SVID3 +# endif +# include <termios.h> +# ifdef TERMIOS_NEEDS__SVID3 +# undef _SVID3 +# endif +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_control.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +/* + * GPS Definitions + */ +#define DESCRIPTION "Trimble Palisade GPS" /* Long name */ +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference ID */ +#define TRMB_MINPOLL 4 /* 16 seconds */ +#define TRMB_MAXPOLL 5 /* 32 seconds */ + +/* + * I/O Definitions + */ +#define DEVICE "/dev/palisade%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ + +/* + * TSIP Report Definitions + */ +#define LENCODE_8F0B 74 /* Length of TSIP 8F-0B Packet & header */ +#define LENCODE_NTP 22 /* Length of Palisade NTP Packet */ + +/* Allowed Sub-Packet ID's */ +#define PACKET_8F0B 0x0B +#define PACKET_NTP 0xAD + +#define DLE 0x10 +#define ETX 0x03 + +/* parse states */ +#define TSIP_PARSED_EMPTY 0 +#define TSIP_PARSED_FULL 1 +#define TSIP_PARSED_DLE_1 2 +#define TSIP_PARSED_DATA 3 +#define TSIP_PARSED_DLE_2 4 + +/* + * Leap-Insert and Leap-Delete are encoded as follows: + * PALISADE_UTC_TIME set and PALISADE_LEAP_PENDING set: INSERT leap + */ + +#define PALISADE_LEAP_INPROGRESS 0x08 /* This is the leap flag */ +#define PALISADE_LEAP_WARNING 0x04 /* GPS Leap Warning (see ICD-200) */ +#define PALISADE_LEAP_PENDING 0x02 /* Leap Pending (24 hours) */ +#define PALISADE_UTC_TIME 0x01 /* UTC time available */ + +#define mb(_X_) (up->rpt_buf[(_X_ + 1)]) /* shortcut for buffer access */ + +/* Conversion Definitions */ +#define GPS_PI (3.1415926535898) +#define R2D (180.0/GPS_PI) + +/* + * Palisade unit control structure. + */ +struct palisade_unit { + short unit; /* NTP refclock unit number */ + int polled; /* flag to detect noreplies */ + char leap_status; /* leap second flag */ + char rpt_status; /* TSIP Parser State */ + short rpt_cnt; /* TSIP packet length so far */ + char rpt_buf[BMAX]; /* packet assembly buffer */ + int type; /* Clock mode type */ +}; + +/* + * Function prototypes + */ + +static int palisade_start P((int, struct peer *)); +static void palisade_shutdown P((int, struct peer *)); +static void palisade_receive P((struct peer *)); +static void palisade_poll P((int, struct peer *)); +static void palisade_io P((struct recvbuf *)); +int palisade_configure P((int, struct peer *)); +int TSIP_decode P((struct peer *)); +long HW_poll P((struct refclockproc *)); +float getfloat P((u_char *)); +double getdbl P((u_char *)); +short getint P((u_char *)); + +#endif /* PALISADE_H */ diff --git a/ntpd/refclock_parse.c b/ntpd/refclock_parse.c new file mode 100644 index 0000000..52fadaa --- /dev/null +++ b/ntpd/refclock_parse.c @@ -0,0 +1,5386 @@ +/* + * /src/NTP/ntp-4/ntpd/refclock_parse.c,v 4.36 1999/11/28 17:18:20 kardel RELEASE_19991128_A + * + * refclock_parse.c,v 4.36 1999/11/28 17:18:20 kardel RELEASE_19991128_A + * + * generic reference clock driver for receivers + * + * optionally make use of a STREAMS module for input processing where + * available and configured. Currently the STREAMS module + * is only available for Suns running SunOS 4.x and SunOS5.x + * + * the STREAMS module is not required for operation and may be omitted + * at the cost of reduced accuracy. As new kernel interfaces emerger this + * restriction may be lifted in future. + * + * Copyright (c) 1995-1999 by Frank Kardel <kardel@acm.org> + * Copyright (c) 1989-1994 by Frank Kardel, Friedrich-Alexander Universität Erlangen-Nürnberg, Germany + * + * This software may not be sold for profit without a written consent + * from the author. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_PARSE) + +/* + * This driver currently provides the support for + * - Meinberg receiver DCF77 PZF 535 (TCXO version) (DCF) + * - Meinberg receiver DCF77 PZF 535 (OCXO version) (DCF) + * - Meinberg receiver DCF77 PZF 509 (DCF) + * - Meinberg receiver DCF77 AM receivers (e.g. C51) (DCF) + * - IGEL CLOCK (DCF) + * - ELV DCF7000 (DCF) + * - Schmid clock (DCF) + * - Conrad DCF77 receiver module (DCF) + * - FAU DCF77 NTP receiver (TimeBrick) (DCF) + * + * - Meinberg GPS166/GPS167 (GPS) + * - Trimble (TSIP and TAIP protocol) (GPS) + * + * - RCC8000 MSF Receiver (MSF) + * - WHARTON 400A Series clock (DCF) + * - VARITEXT clock (MSF) + */ + +/* + * Meinberg receivers are usually connected via a + * 9600 baud serial line + * + * The Meinberg GPS receivers also have a special NTP time stamp + * format. The firmware release is Uni-Erlangen. + * + * Meinberg generic receiver setup: + * output time code every second + * Baud rate 9600 7E2S + * + * Meinberg GPS16x setup: + * output time code every second + * Baudrate 19200 8N1 + * + * This software supports the standard data formats used + * in Meinberg receivers. + * + * Special software versions are only sensible for the + * GPS 16x family of receivers. + * + * Meinberg can be reached via: http://www.meinberg.de/ + */ + +#include "ntpd.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" /* includes <sys/time.h> */ +#include "ntp_control.h" + +#include <stdio.h> +#include <ctype.h> +#ifndef TM_IN_SYS_TIME +# include <time.h> +#endif + +#if !defined(STREAM) && !defined(HAVE_SYSV_TTYS) && !defined(HAVE_BSD_TTYS) && !defined(HAVE_TERMIOS) +# include "Bletch: Define one of {STREAM,HAVE_SYSV_TTYS,HAVE_TERMIOS}" +#endif + +#ifdef STREAM +# include <sys/stream.h> +# include <sys/stropts.h> +#endif + +#ifdef HAVE_TERMIOS +# define TTY_GETATTR(_FD_, _ARG_) tcgetattr((_FD_), (_ARG_)) +# define TTY_SETATTR(_FD_, _ARG_) tcsetattr((_FD_), TCSANOW, (_ARG_)) +# undef HAVE_SYSV_TTYS +#endif + +#ifdef HAVE_SYSV_TTYS +# define TTY_GETATTR(_FD_, _ARG_) ioctl((_FD_), TCGETA, (_ARG_)) +# define TTY_SETATTR(_FD_, _ARG_) ioctl((_FD_), TCSETAW, (_ARG_)) +#endif + +#ifdef HAVE_BSD_TTYS +/* #error CURRENTLY NO BSD TTY SUPPORT */ +# include "Bletch: BSD TTY not currently supported" +#endif + +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +#ifdef PPS +#ifdef HAVE_SYS_PPSCLOCK_H +#include <sys/ppsclock.h> +#endif +#ifdef HAVE_TIO_SERIAL_STUFF +#include <linux/serial.h> +#endif +#endif + +#include "ntp_io.h" +#include "ntp_stdlib.h" + +#include "parse.h" +#include "mbg_gps166.h" +#include "trimble.h" +#include "binio.h" +#include "ascii.h" +#include "ieee754io.h" + +static char rcsid[]="refclock_parse.c,v 4.36 1999/11/28 17:18:20 kardel RELEASE_19991128_A"; + +/**=========================================================================== + ** external interface to ntp mechanism + **/ + +static void parse_init P((void)); +static int parse_start P((int, struct peer *)); +static void parse_shutdown P((int, struct peer *)); +static void parse_poll P((int, struct peer *)); +static void parse_control P((int, struct refclockstat *, struct refclockstat *, struct peer *)); + +#define parse_buginfo noentry + +struct refclock refclock_parse = { + parse_start, + parse_shutdown, + parse_poll, + parse_control, + parse_init, + parse_buginfo, + NOFLAGS +}; + +/* + * Definitions + */ +#define MAXUNITS 4 /* maximum number of "PARSE" units permitted */ +#define PARSEDEVICE "/dev/refclock-%d" /* device to open %d is unit number */ + +#undef ABS +#define ABS(_X_) (((_X_) < 0) ? -(_X_) : (_X_)) + +/**=========================================================================== + ** function vector for dynamically binding io handling mechanism + **/ + +struct parseunit; /* to keep inquiring minds happy */ + +typedef struct bind +{ + const char *bd_description; /* name of type of binding */ + int (*bd_init) P((struct parseunit *)); /* initialize */ + void (*bd_end) P((struct parseunit *)); /* end */ + int (*bd_setcs) P((struct parseunit *, parsectl_t *)); /* set character size */ + int (*bd_disable) P((struct parseunit *)); /* disable */ + int (*bd_enable) P((struct parseunit *)); /* enable */ + int (*bd_getfmt) P((struct parseunit *, parsectl_t *)); /* get format */ + int (*bd_setfmt) P((struct parseunit *, parsectl_t *)); /* setfmt */ + int (*bd_timecode) P((struct parseunit *, parsectl_t *)); /* get time code */ + void (*bd_receive) P((struct recvbuf *)); /* receive operation */ + int (*bd_io_input) P((struct recvbuf *)); /* input operation */ +} bind_t; + +#define PARSE_END(_X_) (*(_X_)->binding->bd_end)(_X_) +#define PARSE_SETCS(_X_, _CS_) (*(_X_)->binding->bd_setcs)(_X_, _CS_) +#define PARSE_ENABLE(_X_) (*(_X_)->binding->bd_enable)(_X_) +#define PARSE_DISABLE(_X_) (*(_X_)->binding->bd_disable)(_X_) +#define PARSE_GETFMT(_X_, _DCT_) (*(_X_)->binding->bd_getfmt)(_X_, _DCT_) +#define PARSE_SETFMT(_X_, _DCT_) (*(_X_)->binding->bd_setfmt)(_X_, _DCT_) +#define PARSE_GETTIMECODE(_X_, _DCT_) (*(_X_)->binding->bd_timecode)(_X_, _DCT_) + +/* + * io modes + */ +#define PARSE_F_PPSPPS 0x0001 /* use loopfilter PPS code (CIOGETEV) */ +#define PARSE_F_PPSONSECOND 0x0002 /* PPS pulses are on second */ + + +/**=========================================================================== + ** error message regression handling + ** + ** there are quite a few errors that can occur in rapid succession such as + ** noisy input data or no data at all. in order to reduce the amount of + ** syslog messages in such case, we are using a backoff algorithm. We limit + ** the number of error messages of a certain class to 1 per time unit. if a + ** configurable number of messages is displayed that way, we move on to the + ** next time unit / count for that class. a count of messages that have been + ** suppressed is held and displayed whenever a corresponding message is + ** displayed. the time units for a message class will also be displayed. + ** whenever an error condition clears we reset the error message state, + ** thus we would still generate much output on pathological conditions + ** where the system oscillates between OK and NOT OK states. coping + ** with that condition is currently considered too complicated. + **/ + +#define ERR_ALL (unsigned)~0 /* "all" errors */ +#define ERR_BADDATA (unsigned)0 /* unusable input data/conversion errors */ +#define ERR_NODATA (unsigned)1 /* no input data */ +#define ERR_BADIO (unsigned)2 /* read/write/select errors */ +#define ERR_BADSTATUS (unsigned)3 /* unsync states */ +#define ERR_BADEVENT (unsigned)4 /* non nominal events */ +#define ERR_INTERNAL (unsigned)5 /* internal error */ +#define ERR_CNT (unsigned)(ERR_INTERNAL+1) + +#define ERR(_X_) if (list_err(parse, (_X_))) + +struct errorregression +{ + u_long err_count; /* number of repititions per class */ + u_long err_delay; /* minimum delay between messages */ +}; + +static struct errorregression +err_baddata[] = /* error messages for bad input data */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_nodata[] = /* error messages for missing input data */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badstatus[] = /* unsynchronized state messages */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 3, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badio[] = /* io failures (bad reads, selects, ...) */ +{ + { 1, 0 }, /* output first message immediately */ + { 5, 60 }, /* output next five messages in 60 second intervals */ + { 5, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_badevent[] = /* non nominal events */ +{ + { 20, 0 }, /* output first message immediately */ + { 6, 60 }, /* output next five messages in 60 second intervals */ + { 5, 3600 }, /* output next 3 messages in hour intervals */ + { 0, 12*3600 } /* repeat messages only every 12 hours */ +}; + +static struct errorregression +err_internal[] = /* really bad things - basically coding/OS errors */ +{ + { 0, 0 }, /* output all messages immediately */ +}; + +static struct errorregression * +err_tbl[] = +{ + err_baddata, + err_nodata, + err_badio, + err_badstatus, + err_badevent, + err_internal +}; + +struct errorinfo +{ + u_long err_started; /* begin time (ntp) of error condition */ + u_long err_last; /* last time (ntp) error occurred */ + u_long err_cnt; /* number of error repititions */ + u_long err_suppressed; /* number of suppressed messages */ + struct errorregression *err_stage; /* current error stage */ +}; + +/**=========================================================================== + ** refclock instance data + **/ + +struct parseunit +{ + /* + * NTP management + */ + struct peer *peer; /* backlink to peer structure - refclock inactive if 0 */ + struct refclockproc *generic; /* backlink to refclockproc structure */ + + /* + * PARSE io + */ + bind_t *binding; /* io handling binding */ + + /* + * parse state + */ + parse_t parseio; /* io handling structure (user level parsing) */ + + /* + * type specific parameters + */ + struct parse_clockinfo *parse_type; /* link to clock description */ + + /* + * clock state handling/reporting + */ + u_char flags; /* flags (leap_control) */ + u_long lastchange; /* time (ntp) when last state change accured */ + u_long statetime[CEVNT_MAX+1]; /* accumulated time of clock states */ + u_long pollneeddata; /* current_time(!=0) for receive sample expected in PPS mode */ + u_short lastformat; /* last format used */ + u_long lastsync; /* time (ntp) when clock was last seen fully synchronized */ + u_long lastmissed; /* time (ntp) when poll didn't get data (powerup heuristic) */ + u_long ppsserial; /* magic cookie for ppsclock serials (avoids stale ppsclock data) */ + parsetime_t time; /* last (parse module) data */ + void *localdata; /* optional local, receiver-specific data */ + unsigned long localstate; /* private local state */ + struct errorinfo errors[ERR_CNT]; /* error state table for suppressing excessive error messages */ + struct ctl_var *kv; /* additional pseudo variables */ + u_long laststatistic; /* time when staticstics where output */ +}; + + +/**=========================================================================== + ** Clockinfo section all parameter for specific clock types + ** includes NTP parameters, TTY parameters and IO handling parameters + **/ + +static void poll_dpoll P((struct parseunit *)); +static void poll_poll P((struct peer *)); +static int poll_init P((struct parseunit *)); + +typedef struct poll_info +{ + u_long rate; /* poll rate - once every "rate" seconds - 0 off */ + const char *string; /* string to send for polling */ + u_long count; /* number of characters in string */ +} poll_info_t; + +#define NO_CL_FLAGS 0 +#define NO_POLL 0 +#define NO_INIT 0 +#define NO_END 0 +#define NO_EVENT 0 +#define NO_DATA 0 +#define NO_MESSAGE 0 +#define NO_PPSDELAY 0 + +#define DCF_ID "DCF" /* generic DCF */ +#define DCF_A_ID "DCFa" /* AM demodulation */ +#define DCF_P_ID "DCFp" /* psuedo random phase shift */ +#define GPS_ID "GPS" /* GPS receiver */ + +#define NOCLOCK_ROOTDELAY 0.0 +#define NOCLOCK_BASEDELAY 0.0 +#define NOCLOCK_DESCRIPTION 0 +#define NOCLOCK_MAXUNSYNC 0 +#define NOCLOCK_CFLAG 0 +#define NOCLOCK_IFLAG 0 +#define NOCLOCK_OFLAG 0 +#define NOCLOCK_LFLAG 0 +#define NOCLOCK_ID "TILT" +#define NOCLOCK_POLL NO_POLL +#define NOCLOCK_INIT NO_INIT +#define NOCLOCK_END NO_END +#define NOCLOCK_DATA NO_DATA +#define NOCLOCK_FORMAT "" +#define NOCLOCK_TYPE CTL_SST_TS_UNSPEC +#define NOCLOCK_SAMPLES 0 +#define NOCLOCK_KEEP 0 + +#define DCF_TYPE CTL_SST_TS_LF +#define GPS_TYPE CTL_SST_TS_UHF + +/* + * receiver specific constants + */ +#define MBG_SPEED (B9600) +#define MBG_CFLAG (CS7|PARENB|CREAD|CLOCAL|HUPCL) +#define MBG_IFLAG (IGNBRK|IGNPAR|ISTRIP) +#define MBG_OFLAG 0 +#define MBG_LFLAG 0 +#define MBG_FLAGS PARSE_F_PPSONSECOND + +/* + * Meinberg DCF77 receivers + */ +#define DCFUA31_ROOTDELAY 0.0 /* 0 */ +#define DCFUA31_BASEDELAY 0.010 /* 10.7421875ms: 10 ms (+/- 3 ms) */ +#define DCFUA31_DESCRIPTION "Meinberg DCF77 C51 or compatible" +#define DCFUA31_MAXUNSYNC 60*30 /* only trust clock for 1/2 hour */ +#define DCFUA31_SPEED MBG_SPEED +#define DCFUA31_CFLAG MBG_CFLAG +#define DCFUA31_IFLAG MBG_IFLAG +#define DCFUA31_OFLAG MBG_OFLAG +#define DCFUA31_LFLAG MBG_LFLAG +#define DCFUA31_SAMPLES 5 +#define DCFUA31_KEEP 3 +#define DCFUA31_FORMAT "Meinberg Standard" + +/* + * Meinberg DCF PZF535/TCXO (FM/PZF) receiver + */ +#define DCFPZF535_ROOTDELAY 0.0 +#define DCFPZF535_BASEDELAY 0.001968 /* 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define DCFPZF535_DESCRIPTION "Meinberg DCF PZF 535/509 / TCXO" +#define DCFPZF535_MAXUNSYNC 60*60*12 /* only trust clock for 12 hours + * @ 5e-8df/f we have accumulated + * at most 2.16 ms (thus we move to + * NTP synchronisation */ +#define DCFPZF535_SPEED MBG_SPEED +#define DCFPZF535_CFLAG MBG_CFLAG +#define DCFPZF535_IFLAG MBG_IFLAG +#define DCFPZF535_OFLAG MBG_OFLAG +#define DCFPZF535_LFLAG MBG_LFLAG +#define DCFPZF535_SAMPLES 5 +#define DCFPZF535_KEEP 3 +#define DCFPZF535_FORMAT "Meinberg Standard" + +/* + * Meinberg DCF PZF535/OCXO receiver + */ +#define DCFPZF535OCXO_ROOTDELAY 0.0 +#define DCFPZF535OCXO_BASEDELAY 0.001968 /* 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define DCFPZF535OCXO_DESCRIPTION "Meinberg DCF PZF 535/509 / OCXO" +#define DCFPZF535OCXO_MAXUNSYNC 60*60*96 /* only trust clock for 4 days + * @ 5e-9df/f we have accumulated + * at most an error of 1.73 ms + * (thus we move to NTP synchronisation) */ +#define DCFPZF535OCXO_SPEED MBG_SPEED +#define DCFPZF535OCXO_CFLAG MBG_CFLAG +#define DCFPZF535OCXO_IFLAG MBG_IFLAG +#define DCFPZF535OCXO_OFLAG MBG_OFLAG +#define DCFPZF535OCXO_LFLAG MBG_LFLAG +#define DCFPZF535OCXO_SAMPLES 5 +#define DCFPZF535OCXO_KEEP 3 +#define DCFPZF535OCXO_FORMAT "Meinberg Standard" + +/* + * Meinberg GPS16X receiver + */ +static void gps16x_message P((struct parseunit *, parsetime_t *)); +static int gps16x_poll_init P((struct parseunit *)); + +#define GPS16X_ROOTDELAY 0.0 /* nothing here */ +#define GPS16X_BASEDELAY 0.001968 /* XXX to be fixed ! 1.968ms +- 104us (oscilloscope) - relative to start (end of STX) */ +#define GPS16X_DESCRIPTION "Meinberg GPS16x receiver" +#define GPS16X_MAXUNSYNC 60*60*96 /* only trust clock for 4 days + * @ 5e-9df/f we have accumulated + * at most an error of 1.73 ms + * (thus we move to NTP synchronisation) */ +#define GPS16X_SPEED B19200 +#define GPS16X_CFLAG (CS8|CREAD|CLOCAL|HUPCL) +#define GPS16X_IFLAG (IGNBRK|IGNPAR) +#define GPS16X_OFLAG MBG_OFLAG +#define GPS16X_LFLAG MBG_LFLAG +#define GPS16X_POLLRATE 6 +#define GPS16X_POLLCMD "" +#define GPS16X_CMDSIZE 0 + +static poll_info_t gps16x_pollinfo = { GPS16X_POLLRATE, GPS16X_POLLCMD, GPS16X_CMDSIZE }; + +#define GPS16X_INIT gps16x_poll_init +#define GPS16X_POLL 0 +#define GPS16X_END 0 +#define GPS16X_DATA ((void *)(&gps16x_pollinfo)) +#define GPS16X_MESSAGE gps16x_message +#define GPS16X_ID GPS_ID +#define GPS16X_FORMAT "Meinberg GPS Extended" +#define GPS16X_SAMPLES 5 +#define GPS16X_KEEP 3 + +/* + * ELV DCF7000 Wallclock-Receiver/Switching Clock (Kit) + * + * This is really not the hottest clock - but before you have nothing ... + */ +#define DCF7000_ROOTDELAY 0.0 /* 0 */ +#define DCF7000_BASEDELAY 0.405 /* slow blow */ +#define DCF7000_DESCRIPTION "ELV DCF7000" +#define DCF7000_MAXUNSYNC (60*5) /* sorry - but it just was not build as a clock */ +#define DCF7000_SPEED (B9600) +#define DCF7000_CFLAG (CS8|CREAD|PARENB|PARODD|CLOCAL|HUPCL) +#define DCF7000_IFLAG (IGNBRK) +#define DCF7000_OFLAG 0 +#define DCF7000_LFLAG 0 +#define DCF7000_SAMPLES 5 +#define DCF7000_KEEP 3 +#define DCF7000_FORMAT "ELV DCF7000" + +/* + * Schmid DCF Receiver Kit + * + * When the WSDCF clock is operating optimally we want the primary clock + * distance to come out at 300 ms. Thus, peer.distance in the WSDCF peer + * structure is set to 290 ms and we compute delays which are at least + * 10 ms long. The following are 290 ms and 10 ms expressed in u_fp format + */ +#define WS_POLLRATE 1 /* every second - watch interdependency with poll routine */ +#define WS_POLLCMD "\163" +#define WS_CMDSIZE 1 + +static poll_info_t wsdcf_pollinfo = { WS_POLLRATE, WS_POLLCMD, WS_CMDSIZE }; + +#define WSDCF_INIT poll_init +#define WSDCF_POLL poll_dpoll +#define WSDCF_END 0 +#define WSDCF_DATA ((void *)(&wsdcf_pollinfo)) +#define WSDCF_ROOTDELAY 0.0 /* 0 */ +#define WSDCF_BASEDELAY 0.010 /* ~ 10ms */ +#define WSDCF_DESCRIPTION "WS/DCF Receiver" +#define WSDCF_FORMAT "Schmid" +#define WSDCF_MAXUNSYNC (60*60) /* assume this beast hold at 1 h better than 2 ms XXX-must verify */ +#define WSDCF_SPEED (B1200) +#define WSDCF_CFLAG (CS8|CREAD|CLOCAL) +#define WSDCF_IFLAG 0 +#define WSDCF_OFLAG 0 +#define WSDCF_LFLAG 0 +#define WSDCF_SAMPLES 5 +#define WSDCF_KEEP 3 + +/* + * RAW DCF77 - input of DCF marks via RS232 - many variants + */ +#define RAWDCF_FLAGS 0 +#define RAWDCF_ROOTDELAY 0.0 /* 0 */ +#define RAWDCF_BASEDELAY 0.258 +#define RAWDCF_FORMAT "RAW DCF77 Timecode" +#define RAWDCF_MAXUNSYNC (0) /* sorry - its a true receiver - no signal - no time */ +#define RAWDCF_SPEED (B50) +#ifdef NO_PARENB_IGNPAR /* Was: defined(SYS_IRIX4) || defined(SYS_IRIX5) */ +/* somehow doesn't grok PARENB & IGNPAR (mj) */ +# define RAWDCF_CFLAG (CS8|CREAD|CLOCAL) +#else +# define RAWDCF_CFLAG (CS8|CREAD|CLOCAL|PARENB) +#endif +#ifdef RAWDCF_NO_IGNPAR /* Was: defined(SYS_LINUX) && defined(CLOCK_RAWDCF) */ +# define RAWDCF_IFLAG 0 +#else +# define RAWDCF_IFLAG (IGNPAR) +#endif +#define RAWDCF_OFLAG 0 +#define RAWDCF_LFLAG 0 +#define RAWDCF_SAMPLES 20 +#define RAWDCF_KEEP 12 +#define RAWDCF_INIT 0 + +/* + * RAW DCF variants + */ +/* + * Conrad receiver + * + * simplest (cheapest) DCF clock - e. g. DCF77 receiver by Conrad + * (~40DM - roughly $30 ) followed by a level converter for RS232 + */ +#define CONRAD_BASEDELAY 0.292 /* Conrad receiver @ 50 Baud on a Sun */ +#define CONRAD_DESCRIPTION "RAW DCF77 CODE (Conrad DCF77 receiver module)" + +/* + * TimeBrick receiver + */ +#define TIMEBRICK_BASEDELAY 0.210 /* TimeBrick @ 50 Baud on a Sun */ +#define TIMEBRICK_DESCRIPTION "RAW DCF77 CODE (TimeBrick)" + +/* + * IGEL:clock receiver + */ +#define IGELCLOCK_BASEDELAY 0.258 /* IGEL:clock receiver */ +#define IGELCLOCK_DESCRIPTION "RAW DCF77 CODE (IGEL:clock)" +#define IGELCLOCK_SPEED (B1200) +#define IGELCLOCK_CFLAG (CS8|CREAD|HUPCL|CLOCAL) + +/* + * RAWDCF receivers that need to be powered from DTR + * (like Expert mouse clock) + */ +static int rawdcf_init_1 P((struct parseunit *)); +#define RAWDCFDTRSET_DESCRIPTION "RAW DCF77 CODE (DTR SET/RTS CLR)" +#define RAWDCFDTRSET_INIT rawdcf_init_1 + +/* + * RAWDCF receivers that need to be powered from + * DTR CLR and RTS SET + */ +static int rawdcf_init_2 P((struct parseunit *)); +#define RAWDCFDTRCLRRTSSET_DESCRIPTION "RAW DCF77 CODE (DTR CLR/RTS SET)" +#define RAWDCFDTRCLRRTSSET_INIT rawdcf_init_2 + +/* + * Trimble GPS receivers (TAIP and TSIP protocols) + */ +#ifndef TRIM_POLLRATE +#define TRIM_POLLRATE 0 /* only true direct polling */ +#endif + +#define TRIM_TAIPPOLLCMD ">SRM;FR_FLAG=F;EC_FLAG=F<>QTM<" +#define TRIM_TAIPCMDSIZE (sizeof(TRIM_TAIPPOLLCMD)-1) + +static poll_info_t trimbletaip_pollinfo = { TRIM_POLLRATE, TRIM_TAIPPOLLCMD, TRIM_TAIPCMDSIZE }; +static int trimbletaip_init P((struct parseunit *)); +static void trimbletaip_event P((struct parseunit *, int)); + +/* query time & UTC correction data */ +static char tsipquery[] = { DLE, 0x21, DLE, ETX, DLE, 0x2F, DLE, ETX }; + +static poll_info_t trimbletsip_pollinfo = { TRIM_POLLRATE, tsipquery, sizeof(tsipquery) }; +static int trimbletsip_init P((struct parseunit *)); +static void trimbletsip_end P((struct parseunit *)); +static void trimbletsip_message P((struct parseunit *, parsetime_t *)); +static void trimbletsip_event P((struct parseunit *, int)); + +#define TRIMBLETSIP_IDLE_TIME (300) /* 5 minutes silence at most */ + +#define TRIMBLETAIP_SPEED (B4800) +#define TRIMBLETAIP_CFLAG (CS8|CREAD|CLOCAL) +#define TRIMBLETAIP_IFLAG (BRKINT|IGNPAR|ISTRIP|ICRNL|IXON) +#define TRIMBLETAIP_OFLAG (OPOST|ONLCR) +#define TRIMBLETAIP_LFLAG (0) + +#define TRIMBLETSIP_SPEED (B9600) +#define TRIMBLETSIP_CFLAG (CS8|CLOCAL|CREAD|PARENB|PARODD) +#define TRIMBLETSIP_IFLAG (IGNBRK) +#define TRIMBLETSIP_OFLAG (0) +#define TRIMBLETSIP_LFLAG (ICANON) + +#define TRIMBLETSIP_SAMPLES 5 +#define TRIMBLETSIP_KEEP 3 +#define TRIMBLETAIP_SAMPLES 5 +#define TRIMBLETAIP_KEEP 3 + +#define TRIMBLETAIP_FLAGS (PARSE_F_PPSONSECOND) +#define TRIMBLETSIP_FLAGS (TRIMBLETAIP_FLAGS) + +#define TRIMBLETAIP_POLL poll_dpoll +#define TRIMBLETSIP_POLL poll_dpoll + +#define TRIMBLETAIP_INIT trimbletaip_init +#define TRIMBLETSIP_INIT trimbletsip_init + +#define TRIMBLETAIP_EVENT trimbletaip_event + +#define TRIMBLETSIP_EVENT trimbletsip_event +#define TRIMBLETSIP_MESSAGE trimbletsip_message + +#define TRIMBLETAIP_END 0 +#define TRIMBLETSIP_END trimbletsip_end + +#define TRIMBLETAIP_DATA ((void *)(&trimbletaip_pollinfo)) +#define TRIMBLETSIP_DATA ((void *)(&trimbletsip_pollinfo)) + +#define TRIMBLETAIP_ID GPS_ID +#define TRIMBLETSIP_ID GPS_ID + +#define TRIMBLETAIP_FORMAT "Trimble TAIP" +#define TRIMBLETSIP_FORMAT "Trimble TSIP" + +#define TRIMBLETAIP_ROOTDELAY 0x0 +#define TRIMBLETSIP_ROOTDELAY 0x0 + +#define TRIMBLETAIP_BASEDELAY 0.0 +#define TRIMBLETSIP_BASEDELAY 0.020 /* GPS time message latency */ + +#define TRIMBLETAIP_DESCRIPTION "Trimble GPS (TAIP) receiver" +#define TRIMBLETSIP_DESCRIPTION "Trimble GPS (TSIP) receiver" + +#define TRIMBLETAIP_MAXUNSYNC 0 +#define TRIMBLETSIP_MAXUNSYNC 0 + +#define TRIMBLETAIP_EOL '<' + +/* + * RadioCode Clocks RCC 800 receiver + */ +#define RCC_POLLRATE 0 /* only true direct polling */ +#define RCC_POLLCMD "\r" +#define RCC_CMDSIZE 1 + +static poll_info_t rcc8000_pollinfo = { RCC_POLLRATE, RCC_POLLCMD, RCC_CMDSIZE }; +#define RCC8000_FLAGS 0 +#define RCC8000_POLL poll_dpoll +#define RCC8000_INIT poll_init +#define RCC8000_END 0 +#define RCC8000_DATA ((void *)(&rcc8000_pollinfo)) +#define RCC8000_ROOTDELAY 0.0 +#define RCC8000_BASEDELAY 0.0 +#define RCC8000_ID "MSF" +#define RCC8000_DESCRIPTION "RCC 8000 MSF Receiver" +#define RCC8000_FORMAT "Radiocode RCC8000" +#define RCC8000_MAXUNSYNC (60*60) /* should be ok for an hour */ +#define RCC8000_SPEED (B2400) +#define RCC8000_CFLAG (CS8|CREAD|CLOCAL) +#define RCC8000_IFLAG (IGNBRK|IGNPAR) +#define RCC8000_OFLAG 0 +#define RCC8000_LFLAG 0 +#define RCC8000_SAMPLES 5 +#define RCC8000_KEEP 3 + +/* + * Hopf Radio clock 6021 Format + * + */ +#define HOPF6021_ROOTDELAY 0.0 +#define HOPF6021_BASEDELAY 0.0 +#define HOPF6021_DESCRIPTION "HOPF 6021" +#define HOPF6021_FORMAT "hopf Funkuhr 6021" +#define HOPF6021_MAXUNSYNC (60*60) /* should be ok for an hour */ +#define HOPF6021_SPEED (B9600) +#define HOPF6021_CFLAG (CS8|CREAD|CLOCAL) +#define HOPF6021_IFLAG (IGNBRK|ISTRIP) +#define HOPF6021_OFLAG 0 +#define HOPF6021_LFLAG 0 +#define HOPF6021_FLAGS 0 +#define HOPF6021_SAMPLES 5 +#define HOPF6021_KEEP 3 + +/* + * Diem's Computime Radio Clock Receiver + */ +#define COMPUTIME_FLAGS 0 +#define COMPUTIME_ROOTDELAY 0.0 +#define COMPUTIME_BASEDELAY 0.0 +#define COMPUTIME_ID DCF_ID +#define COMPUTIME_DESCRIPTION "Diem's Computime receiver" +#define COMPUTIME_FORMAT "Diem's Computime Radio Clock" +#define COMPUTIME_TYPE DCF_TYPE +#define COMPUTIME_MAXUNSYNC (60*60) /* only trust clock for 1 hour */ +#define COMPUTIME_SPEED (B9600) +#define COMPUTIME_CFLAG (CSTOPB|CS7|CREAD|CLOCAL) +#define COMPUTIME_IFLAG (IGNBRK|IGNPAR|ISTRIP) +#define COMPUTIME_OFLAG 0 +#define COMPUTIME_LFLAG 0 +#define COMPUTIME_SAMPLES 5 +#define COMPUTIME_KEEP 3 + +/* + * Varitext Radio Clock Receiver + */ +#define VARITEXT_FLAGS 0 +#define VARITEXT_ROOTDELAY 0.0 +#define VARITEXT_BASEDELAY 0.0 +#define VARITEXT_ID "MSF" +#define VARITEXT_DESCRIPTION "Varitext receiver" +#define VARITEXT_FORMAT "Varitext Radio Clock" +#define VARITEXT_TYPE DCF_TYPE +#define VARITEXT_MAXUNSYNC (60*60) /* only trust clock for 1 hour */ +#define VARITEXT_SPEED (B9600) +#define VARITEXT_CFLAG (CS7|CREAD|CLOCAL|PARENB|PARODD) +#define VARITEXT_IFLAG (IGNPAR|IGNBRK|INPCK) /*|ISTRIP)*/ +#define VARITEXT_OFLAG 0 +#define VARITEXT_LFLAG 0 +#define VARITEXT_SAMPLES 32 +#define VARITEXT_KEEP 20 + +static struct parse_clockinfo +{ + u_long cl_flags; /* operation flags (io modes) */ + void (*cl_poll) P((struct parseunit *)); /* active poll routine */ + int (*cl_init) P((struct parseunit *)); /* active poll init routine */ + void (*cl_event) P((struct parseunit *, int)); /* special event handling (e.g. reset clock) */ + void (*cl_end) P((struct parseunit *)); /* active poll end routine */ + void (*cl_message) P((struct parseunit *, parsetime_t *)); /* process a lower layer message */ + void *cl_data; /* local data area for "poll" mechanism */ + double cl_rootdelay; /* rootdelay */ + double cl_basedelay; /* current offset by which the RS232 + time code is delayed from the actual time */ + const char *cl_id; /* ID code */ + const char *cl_description; /* device name */ + const char *cl_format; /* fixed format */ + u_char cl_type; /* clock type (ntp control) */ + u_long cl_maxunsync; /* time to trust oscillator after losing synch */ + u_long cl_speed; /* terminal input & output baudrate */ + u_long cl_cflag; /* terminal control flags */ + u_long cl_iflag; /* terminal input flags */ + u_long cl_oflag; /* terminal output flags */ + u_long cl_lflag; /* terminal local flags */ + u_long cl_samples; /* samples for median filter */ + u_long cl_keep; /* samples for median filter to keep */ +} parse_clockinfo[] = +{ + { /* mode 0 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + DCFPZF535_ROOTDELAY, + DCFPZF535_BASEDELAY, + DCF_P_ID, + DCFPZF535_DESCRIPTION, + DCFPZF535_FORMAT, + DCF_TYPE, + DCFPZF535_MAXUNSYNC, + DCFPZF535_SPEED, + DCFPZF535_CFLAG, + DCFPZF535_IFLAG, + DCFPZF535_OFLAG, + DCFPZF535_LFLAG, + DCFPZF535_SAMPLES, + DCFPZF535_KEEP + }, + { /* mode 1 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + DCFPZF535OCXO_ROOTDELAY, + DCFPZF535OCXO_BASEDELAY, + DCF_P_ID, + DCFPZF535OCXO_DESCRIPTION, + DCFPZF535OCXO_FORMAT, + DCF_TYPE, + DCFPZF535OCXO_MAXUNSYNC, + DCFPZF535OCXO_SPEED, + DCFPZF535OCXO_CFLAG, + DCFPZF535OCXO_IFLAG, + DCFPZF535OCXO_OFLAG, + DCFPZF535OCXO_LFLAG, + DCFPZF535OCXO_SAMPLES, + DCFPZF535OCXO_KEEP + }, + { /* mode 2 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + DCFUA31_ROOTDELAY, + DCFUA31_BASEDELAY, + DCF_A_ID, + DCFUA31_DESCRIPTION, + DCFUA31_FORMAT, + DCF_TYPE, + DCFUA31_MAXUNSYNC, + DCFUA31_SPEED, + DCFUA31_CFLAG, + DCFUA31_IFLAG, + DCFUA31_OFLAG, + DCFUA31_LFLAG, + DCFUA31_SAMPLES, + DCFUA31_KEEP + }, + { /* mode 3 */ + MBG_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + DCF7000_ROOTDELAY, + DCF7000_BASEDELAY, + DCF_A_ID, + DCF7000_DESCRIPTION, + DCF7000_FORMAT, + DCF_TYPE, + DCF7000_MAXUNSYNC, + DCF7000_SPEED, + DCF7000_CFLAG, + DCF7000_IFLAG, + DCF7000_OFLAG, + DCF7000_LFLAG, + DCF7000_SAMPLES, + DCF7000_KEEP + }, + { /* mode 4 */ + NO_CL_FLAGS, + WSDCF_POLL, + WSDCF_INIT, + NO_EVENT, + WSDCF_END, + NO_MESSAGE, + WSDCF_DATA, + WSDCF_ROOTDELAY, + WSDCF_BASEDELAY, + DCF_A_ID, + WSDCF_DESCRIPTION, + WSDCF_FORMAT, + DCF_TYPE, + WSDCF_MAXUNSYNC, + WSDCF_SPEED, + WSDCF_CFLAG, + WSDCF_IFLAG, + WSDCF_OFLAG, + WSDCF_LFLAG, + WSDCF_SAMPLES, + WSDCF_KEEP + }, + { /* mode 5 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCF_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + RAWDCF_ROOTDELAY, + CONRAD_BASEDELAY, + DCF_A_ID, + CONRAD_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 6 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCF_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + RAWDCF_ROOTDELAY, + TIMEBRICK_BASEDELAY, + DCF_A_ID, + TIMEBRICK_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 7 */ + MBG_FLAGS, + GPS16X_POLL, + GPS16X_INIT, + NO_EVENT, + GPS16X_END, + GPS16X_MESSAGE, + GPS16X_DATA, + GPS16X_ROOTDELAY, + GPS16X_BASEDELAY, + GPS16X_ID, + GPS16X_DESCRIPTION, + GPS16X_FORMAT, + GPS_TYPE, + GPS16X_MAXUNSYNC, + GPS16X_SPEED, + GPS16X_CFLAG, + GPS16X_IFLAG, + GPS16X_OFLAG, + GPS16X_LFLAG, + GPS16X_SAMPLES, + GPS16X_KEEP + }, + { /* mode 8 */ + RAWDCF_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + RAWDCF_ROOTDELAY, + IGELCLOCK_BASEDELAY, + DCF_A_ID, + IGELCLOCK_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + IGELCLOCK_SPEED, + IGELCLOCK_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 9 */ + TRIMBLETAIP_FLAGS, +#if TRIM_POLLRATE /* DHD940515: Allow user config */ + NO_POLL, +#else + TRIMBLETAIP_POLL, +#endif + TRIMBLETAIP_INIT, + TRIMBLETAIP_EVENT, + TRIMBLETAIP_END, + NO_MESSAGE, + TRIMBLETAIP_DATA, + TRIMBLETAIP_ROOTDELAY, + TRIMBLETAIP_BASEDELAY, + TRIMBLETAIP_ID, + TRIMBLETAIP_DESCRIPTION, + TRIMBLETAIP_FORMAT, + GPS_TYPE, + TRIMBLETAIP_MAXUNSYNC, + TRIMBLETAIP_SPEED, + TRIMBLETAIP_CFLAG, + TRIMBLETAIP_IFLAG, + TRIMBLETAIP_OFLAG, + TRIMBLETAIP_LFLAG, + TRIMBLETAIP_SAMPLES, + TRIMBLETAIP_KEEP + }, + { /* mode 10 */ + TRIMBLETSIP_FLAGS, +#if TRIM_POLLRATE /* DHD940515: Allow user config */ + NO_POLL, +#else + TRIMBLETSIP_POLL, +#endif + TRIMBLETSIP_INIT, + TRIMBLETSIP_EVENT, + TRIMBLETSIP_END, + TRIMBLETSIP_MESSAGE, + TRIMBLETSIP_DATA, + TRIMBLETSIP_ROOTDELAY, + TRIMBLETSIP_BASEDELAY, + TRIMBLETSIP_ID, + TRIMBLETSIP_DESCRIPTION, + TRIMBLETSIP_FORMAT, + GPS_TYPE, + TRIMBLETSIP_MAXUNSYNC, + TRIMBLETSIP_SPEED, + TRIMBLETSIP_CFLAG, + TRIMBLETSIP_IFLAG, + TRIMBLETSIP_OFLAG, + TRIMBLETSIP_LFLAG, + TRIMBLETSIP_SAMPLES, + TRIMBLETSIP_KEEP + }, + { /* mode 11 */ + NO_CL_FLAGS, + RCC8000_POLL, + RCC8000_INIT, + NO_EVENT, + RCC8000_END, + NO_MESSAGE, + RCC8000_DATA, + RCC8000_ROOTDELAY, + RCC8000_BASEDELAY, + RCC8000_ID, + RCC8000_DESCRIPTION, + RCC8000_FORMAT, + DCF_TYPE, + RCC8000_MAXUNSYNC, + RCC8000_SPEED, + RCC8000_CFLAG, + RCC8000_IFLAG, + RCC8000_OFLAG, + RCC8000_LFLAG, + RCC8000_SAMPLES, + RCC8000_KEEP + }, + { /* mode 12 */ + HOPF6021_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + HOPF6021_ROOTDELAY, + HOPF6021_BASEDELAY, + DCF_ID, + HOPF6021_DESCRIPTION, + HOPF6021_FORMAT, + DCF_TYPE, + HOPF6021_MAXUNSYNC, + HOPF6021_SPEED, + HOPF6021_CFLAG, + HOPF6021_IFLAG, + HOPF6021_OFLAG, + HOPF6021_LFLAG, + HOPF6021_SAMPLES, + HOPF6021_KEEP + }, + { /* mode 13 */ + COMPUTIME_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + COMPUTIME_ROOTDELAY, + COMPUTIME_BASEDELAY, + COMPUTIME_ID, + COMPUTIME_DESCRIPTION, + COMPUTIME_FORMAT, + COMPUTIME_TYPE, + COMPUTIME_MAXUNSYNC, + COMPUTIME_SPEED, + COMPUTIME_CFLAG, + COMPUTIME_IFLAG, + COMPUTIME_OFLAG, + COMPUTIME_LFLAG, + COMPUTIME_SAMPLES, + COMPUTIME_KEEP + }, + { /* mode 14 */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRSET_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 15 */ + 0, /* operation flags (io modes) */ + NO_POLL, /* active poll routine */ + NO_INIT, /* active poll init routine */ + NO_EVENT, /* special event handling (e.g. reset clock) */ + NO_END, /* active poll end routine */ + NO_MESSAGE, /* process a lower layer message */ + NO_DATA, /* local data area for "poll" mechanism */ + 0, /* rootdelay */ + 11.0 /* bits */ / 9600, /* current offset by which the RS232 + time code is delayed from the actual time */ + DCF_ID, /* ID code */ + "WHARTON 400A Series clock", /* device name */ + "WHARTON 400A Series clock Output Format 1", /* fixed format */ + /* Must match a format-name in a libparse/clk_xxx.c file */ + DCF_TYPE, /* clock type (ntp control) */ + (1*60*60), /* time to trust oscillator after losing synch */ + B9600, /* terminal input & output baudrate */ + (CS8|CREAD|PARENB|CLOCAL|HUPCL),/* terminal control flags */ + 0, /* terminal input flags */ + 0, /* terminal output flags */ + 0, /* terminal local flags */ + 5, /* samples for median filter */ + 3, /* samples for median filter to keep */ + }, + { /* mode 16 - RAWDCF RTS set, DTR clr */ + RAWDCF_FLAGS, + NO_POLL, + RAWDCFDTRCLRRTSSET_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + RAWDCF_ROOTDELAY, + RAWDCF_BASEDELAY, + DCF_A_ID, + RAWDCFDTRCLRRTSSET_DESCRIPTION, + RAWDCF_FORMAT, + DCF_TYPE, + RAWDCF_MAXUNSYNC, + RAWDCF_SPEED, + RAWDCF_CFLAG, + RAWDCF_IFLAG, + RAWDCF_OFLAG, + RAWDCF_LFLAG, + RAWDCF_SAMPLES, + RAWDCF_KEEP + }, + { /* mode 17 */ + VARITEXT_FLAGS, + NO_POLL, + NO_INIT, + NO_EVENT, + NO_END, + NO_MESSAGE, + NO_DATA, + VARITEXT_ROOTDELAY, + VARITEXT_BASEDELAY, + VARITEXT_ID, + VARITEXT_DESCRIPTION, + VARITEXT_FORMAT, + VARITEXT_TYPE, + VARITEXT_MAXUNSYNC, + VARITEXT_SPEED, + VARITEXT_CFLAG, + VARITEXT_IFLAG, + VARITEXT_OFLAG, + VARITEXT_LFLAG, + VARITEXT_SAMPLES, + VARITEXT_KEEP + } +}; + +static int ncltypes = sizeof(parse_clockinfo) / sizeof(struct parse_clockinfo); + +#define CLK_REALTYPE(x) ((int)(((x)->ttl) & 0x7F)) +#define CLK_TYPE(x) ((CLK_REALTYPE(x) >= ncltypes) ? ~0 : CLK_REALTYPE(x)) +#define CLK_UNIT(x) ((int)REFCLOCKUNIT(&(x)->srcadr)) +#define CLK_PPS(x) (((x)->ttl) & 0x80) + +/* + * Other constant stuff + */ +#define PARSEHSREFID 0x7f7f08ff /* 127.127.8.255 refid for hi strata */ + +#define PARSESTATISTICS (60*60) /* output state statistics every hour */ + +static struct parseunit *parseunits[MAXUNITS]; + +static int notice = 0; + +#define PARSE_STATETIME(parse, i) ((parse->generic->currentstatus == i) ? parse->statetime[i] + current_time - parse->lastchange : parse->statetime[i]) + +static void parse_event P((struct parseunit *, int)); +static void parse_process P((struct parseunit *, parsetime_t *)); +static void clear_err P((struct parseunit *, u_long)); +static int list_err P((struct parseunit *, u_long)); +static char * l_mktime P((u_long)); + +/**=========================================================================== + ** implementation error message regression module + **/ +static void +clear_err( + struct parseunit *parse, + u_long lstate + ) +{ + if (lstate == ERR_ALL) + { + int i; + + for (i = 0; i < ERR_CNT; i++) + { + parse->errors[i].err_stage = err_tbl[i]; + parse->errors[i].err_cnt = 0; + parse->errors[i].err_last = 0; + parse->errors[i].err_started = 0; + parse->errors[i].err_suppressed = 0; + } + } + else + { + parse->errors[lstate].err_stage = err_tbl[lstate]; + parse->errors[lstate].err_cnt = 0; + parse->errors[lstate].err_last = 0; + parse->errors[lstate].err_started = 0; + parse->errors[lstate].err_suppressed = 0; + } +} + +static int +list_err( + struct parseunit *parse, + u_long lstate + ) +{ + int do_it; + struct errorinfo *err = &parse->errors[lstate]; + + if (err->err_started == 0) + { + err->err_started = current_time; + } + + do_it = (current_time - err->err_last) >= err->err_stage->err_delay; + + if (do_it) + err->err_cnt++; + + if (err->err_stage->err_count && + (err->err_cnt >= err->err_stage->err_count)) + { + err->err_stage++; + err->err_cnt = 0; + } + + if (!err->err_cnt && do_it) + msyslog(LOG_INFO, "PARSE receiver #%d: interval for following error message class is at least %s", + CLK_UNIT(parse->peer), l_mktime(err->err_stage->err_delay)); + + if (!do_it) + err->err_suppressed++; + else + err->err_last = current_time; + + if (do_it && err->err_suppressed) + { + msyslog(LOG_INFO, "PARSE receiver #%d: %ld message%s suppressed, error condition class persists for %s", + CLK_UNIT(parse->peer), err->err_suppressed, (err->err_suppressed == 1) ? " was" : "s where", + l_mktime(current_time - err->err_started)); + err->err_suppressed = 0; + } + + return do_it; +} + +/*-------------------------------------------------- + * mkreadable - make a printable ascii string (without + * embedded quotes so that the ntpq protocol isn't + * fooled + */ +#ifndef isprint +#define isprint(_X_) (((_X_) > 0x1F) && ((_X_) < 0x7F)) +#endif + +static char * +mkreadable( + char *buffer, + long blen, + const char *src, + u_long srclen, + int hex + ) +{ + char *b = buffer; + char *endb = (char *)0; + + if (blen < 4) + return (char *)0; /* don't bother with mini buffers */ + + endb = buffer + blen - 4; + + blen--; /* account for '\0' */ + + while (blen && srclen--) + { + if (!hex && /* no binary only */ + (*src != '\\') && /* no plain \ */ + (*src != '"') && /* no " */ + isprint((int)*src)) /* only printables */ + { /* they are easy... */ + *buffer++ = *src++; + blen--; + } + else + { + if (blen < 4) + { + while (blen--) + { + *buffer++ = '.'; + } + *buffer = '\0'; + return b; + } + else + { + if (*src == '\\') + { + strcpy(buffer,"\\\\"); + buffer += 2; + blen -= 2; + src++; + } + else + { + sprintf(buffer, "\\x%02x", *src++); + blen -= 4; + buffer += 4; + } + } + } + if (srclen && !blen && endb) /* overflow - set last chars to ... */ + strcpy(endb, "..."); + } + + *buffer = '\0'; + return b; +} + + +/*-------------------------------------------------- + * mkascii - make a printable ascii string + * assumes (unless defined better) 7-bit ASCII + */ +static char * +mkascii( + char *buffer, + long blen, + const char *src, + u_long srclen + ) +{ + return mkreadable(buffer, blen, src, srclen, 0); +} + +/**=========================================================================== + ** implementation of i/o handling methods + ** (all STREAM, partial STREAM, user level) + **/ + +/* + * define possible io handling methods + */ +#ifdef STREAM +static int ppsclock_init P((struct parseunit *)); +static int stream_init P((struct parseunit *)); +static void stream_end P((struct parseunit *)); +static int stream_enable P((struct parseunit *)); +static int stream_disable P((struct parseunit *)); +static int stream_setcs P((struct parseunit *, parsectl_t *)); +static int stream_getfmt P((struct parseunit *, parsectl_t *)); +static int stream_setfmt P((struct parseunit *, parsectl_t *)); +static int stream_timecode P((struct parseunit *, parsectl_t *)); +static void stream_receive P((struct recvbuf *)); +#endif + +static int local_init P((struct parseunit *)); +static void local_end P((struct parseunit *)); +static int local_nop P((struct parseunit *)); +static int local_setcs P((struct parseunit *, parsectl_t *)); +static int local_getfmt P((struct parseunit *, parsectl_t *)); +static int local_setfmt P((struct parseunit *, parsectl_t *)); +static int local_timecode P((struct parseunit *, parsectl_t *)); +static void local_receive P((struct recvbuf *)); +static int local_input P((struct recvbuf *)); + +static bind_t io_bindings[] = +{ +#ifdef STREAM + { + "parse STREAM", + stream_init, + stream_end, + stream_setcs, + stream_disable, + stream_enable, + stream_getfmt, + stream_setfmt, + stream_timecode, + stream_receive, + 0, + }, + { + "ppsclock STREAM", + ppsclock_init, + local_end, + local_setcs, + local_nop, + local_nop, + local_getfmt, + local_setfmt, + local_timecode, + local_receive, + local_input, + }, +#endif + { + "normal", + local_init, + local_end, + local_setcs, + local_nop, + local_nop, + local_getfmt, + local_setfmt, + local_timecode, + local_receive, + local_input, + }, + { + (char *)0, + } +}; + +#ifdef STREAM + +#define fix_ts(_X_) \ + if ((&(_X_))->tv.tv_usec >= 1000000) \ + { \ + (&(_X_))->tv.tv_usec -= 1000000; \ + (&(_X_))->tv.tv_sec += 1; \ + } + +#define cvt_ts(_X_, _Y_) \ + { \ + l_fp ts; \ + fix_ts((_X_)); \ + if (!buftvtots((const char *)&(&(_X_))->tv, &ts)) \ + { \ + ERR(ERR_BADDATA) \ + msyslog(LOG_ERR,"parse: stream_receive: timestamp conversion error (buftvtots) (%s) (%ld.%06ld) ", (_Y_), (long)(&(_X_))->tv.tv_sec, (long)(&(_X_))->tv.tv_usec);\ + return; \ + } \ + else \ + { \ + (&(_X_))->fp = ts; \ + } \ + } + +/*-------------------------------------------------- + * ppsclock STREAM init + */ +static int +ppsclock_init( + struct parseunit *parse + ) +{ + static char m1[] = "ppsclocd"; + static char m2[] = "ppsclock"; + + /* + * now push the parse streams module + * it will ensure exclusive access to the device + */ + if (ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m1) == -1 && + ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m2) == -1) + { + if (errno != EINVAL) + { + msyslog(LOG_ERR, "PARSE receiver #%d: ppsclock_init: ioctl(fd, I_PUSH, \"ppsclock\"): %m", + CLK_UNIT(parse->peer)); + } + return 0; + } + if (!local_init(parse)) + { + (void)ioctl(parse->generic->io.fd, I_POP, (caddr_t)0); + return 0; + } + + parse->flags |= PARSE_PPSCLOCK; + return 1; +} + +/*-------------------------------------------------- + * parse STREAM init + */ +static int +stream_init( + struct parseunit *parse + ) +{ + static char m1[] = "parse"; + /* + * now push the parse streams module + * to test whether it is there (neat interface 8-( ) + */ + if (ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m1) == -1) + { + if (errno != EINVAL) /* accept non-existence */ + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_init: ioctl(fd, I_PUSH, \"parse\"): %m", CLK_UNIT(parse->peer)); + } + return 0; + } + else + { + while(ioctl(parse->generic->io.fd, I_POP, (caddr_t)0) == 0) + /* empty loop */; + + /* + * now push it a second time after we have removed all + * module garbage + */ + if (ioctl(parse->generic->io.fd, I_PUSH, (caddr_t)m1) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_init: ioctl(fd, I_PUSH, \"parse\"): %m", CLK_UNIT(parse->peer)); + return 0; + } + else + { + return 1; + } + } +} + +/*-------------------------------------------------- + * parse STREAM end + */ +static void +stream_end( + struct parseunit *parse + ) +{ + while(ioctl(parse->generic->io.fd, I_POP, (caddr_t)0) == 0) + /* empty loop */; +} + +/*-------------------------------------------------- + * STREAM setcs + */ +static int +stream_setcs( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_SETCS; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_setcs: ioctl(fd, I_STR, PARSEIOC_SETCS): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + +/*-------------------------------------------------- + * STREAM enable + */ +static int +stream_enable( + struct parseunit *parse + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_ENABLE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)0; + strioc.ic_len = 0; + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_enable: ioctl(fd, I_STR, PARSEIOC_ENABLE): %m", CLK_UNIT(parse->peer)); + return 0; + } + parse->generic->io.clock_recv = stream_receive; /* ok - parse input in kernel */ + return 1; +} + +/*-------------------------------------------------- + * STREAM disable + */ +static int +stream_disable( + struct parseunit *parse + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_DISABLE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)0; + strioc.ic_len = 0; + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_disable: ioctl(fd, I_STR, PARSEIOC_DISABLE): %m", CLK_UNIT(parse->peer)); + return 0; + } + parse->generic->io.clock_recv = local_receive; /* ok - parse input in daemon */ + return 1; +} + +/*-------------------------------------------------- + * STREAM getfmt + */ +static int +stream_getfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_GETFMT; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: ioctl(fd, I_STR, PARSEIOC_GETFMT): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + +/*-------------------------------------------------- + * STREAM setfmt + */ +static int +stream_setfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_SETFMT; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: stream_setfmt: ioctl(fd, I_STR, PARSEIOC_SETFMT): %m", CLK_UNIT(parse->peer)); + return 0; + } + return 1; +} + + +/*-------------------------------------------------- + * STREAM timecode + */ +static int +stream_timecode( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + struct strioctl strioc; + + strioc.ic_cmd = PARSEIOC_TIMECODE; + strioc.ic_timout = 0; + strioc.ic_dp = (char *)tcl; + strioc.ic_len = sizeof (*tcl); + + if (ioctl(parse->generic->io.fd, I_STR, (caddr_t)&strioc) == -1) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: stream_timecode: ioctl(fd, I_STR, PARSEIOC_TIMECODE): %m", CLK_UNIT(parse->peer)); + return 0; + } + clear_err(parse, ERR_INTERNAL); + return 1; +} + +/*-------------------------------------------------- + * STREAM receive + */ +static void +stream_receive( + struct recvbuf *rbufp + ) +{ + struct parseunit *parse = (struct parseunit *)((void *)rbufp->recv_srcclock); + parsetime_t parsetime; + + if (!parse->peer) + return; + + if (rbufp->recv_length != sizeof(parsetime_t)) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR,"PARSE receiver #%d: stream_receive: bad size (got %d expected %d)", + CLK_UNIT(parse->peer), rbufp->recv_length, (int)sizeof(parsetime_t)); + parse->generic->baddata++; + parse_event(parse, CEVNT_BADREPLY); + return; + } + clear_err(parse, ERR_BADIO); + + memmove((caddr_t)&parsetime, + (caddr_t)rbufp->recv_buffer, + sizeof(parsetime_t)); + +#ifdef DEBUG + if (debug > 3) + { + printf("PARSE receiver #%d: status %06x, state %08x, time %lx.%08lx, stime %lx.%08lx, ptime %lx.%08lx\n", + CLK_UNIT(parse->peer), + (unsigned int)parsetime.parse_status, + (unsigned int)parsetime.parse_state, + (long)parsetime.parse_time.tv.tv_sec, + (long)parsetime.parse_time.tv.tv_usec, + (long)parsetime.parse_stime.tv.tv_sec, + (long)parsetime.parse_stime.tv.tv_usec, + (long)parsetime.parse_ptime.tv.tv_sec, + (long)parsetime.parse_ptime.tv.tv_usec); + } +#endif + + /* + * switch time stamp world - be sure to normalize small usec field + * errors. + */ + + cvt_ts(parsetime.parse_stime, "parse_stime"); + + if (PARSE_TIMECODE(parsetime.parse_state)) + { + cvt_ts(parsetime.parse_time, "parse_time"); + } + + if (PARSE_PPS(parsetime.parse_state)) + cvt_ts(parsetime.parse_ptime, "parse_ptime"); + + parse_process(parse, &parsetime); +} +#endif + +/*-------------------------------------------------- + * local init + */ +static int +local_init( + struct parseunit *parse + ) +{ + return parse_ioinit(&parse->parseio); +} + +/*-------------------------------------------------- + * local end + */ +static void +local_end( + struct parseunit *parse + ) +{ + parse_ioend(&parse->parseio); +} + + +/*-------------------------------------------------- + * local nop + */ +static int +local_nop( + struct parseunit *parse + ) +{ + return 1; +} + +/*-------------------------------------------------- + * local setcs + */ +static int +local_setcs( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_setcs(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local getfmt + */ +static int +local_getfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_getfmt(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local setfmt + */ +static int +local_setfmt( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_setfmt(tcl, &parse->parseio); +} + +/*-------------------------------------------------- + * local timecode + */ +static int +local_timecode( + struct parseunit *parse, + parsectl_t *tcl + ) +{ + return parse_timecode(tcl, &parse->parseio); +} + + +/*-------------------------------------------------- + * local input + */ +static int +local_input( + struct recvbuf *rbufp + ) +{ + struct parseunit *parse = (struct parseunit *)((void *)rbufp->recv_srcclock); + int count; + unsigned char *s; + timestamp_t ts; + + if (!parse->peer) + return 0; + + /* + * eat all characters, parsing then and feeding complete samples + */ + count = rbufp->recv_length; + s = (unsigned char *)rbufp->recv_buffer; + ts.fp = rbufp->recv_time; + + while (count--) + { + if (parse_ioread(&parse->parseio, (unsigned int)(*s++), &ts)) + { + struct recvbuf buf; + + /* + * got something good to eat + */ + if (!PARSE_PPS(parse->parseio.parse_dtime.parse_state)) + { +#ifdef TIOCDCDTIMESTAMP + struct timeval dcd_time; + + if (ioctl(rbufp->fd, TIOCDCDTIMESTAMP, &dcd_time) != -1) + { + l_fp tstmp; + + TVTOTS(&dcd_time, &tstmp); + tstmp.l_ui += JAN_1970; + L_SUB(&ts.fp, &tstmp); + if (ts.fp.l_ui == 0) + { +#ifdef DEBUG + if (debug) + { + printf( + "parse: local_receive: fd %d DCDTIMESTAMP %s\n", + rbufp->fd, + lfptoa(&tstmp, 6)); + printf(" sigio %s\n", + lfptoa(&ts.fp, 6)); + } +#endif + parse->parseio.parse_dtime.parse_ptime.fp = tstmp; + parse->parseio.parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS; + } + } +#else /* TIOCDCDTIMESTAMP */ +#if defined(HAVE_STRUCT_PPSCLOCKEV) && (defined(HAVE_CIOGETEV) || defined(HAVE_TIOCGPPSEV)) + if (parse->flags & PARSE_PPSCLOCK) + { + l_fp tts; + struct ppsclockev ev; + +#ifdef HAVE_CIOGETEV + if (ioctl(parse->generic->io.fd, CIOGETEV, (caddr_t)&ev) == 0) +#endif +#ifdef HAVE_TIOCGPPSEV + if (ioctl(parse->generic->io.fd, TIOCGPPSEV, (caddr_t)&ev) == 0) +#endif + { + if (ev.serial != parse->ppsserial) + { + /* + * add PPS time stamp if available via ppsclock module + * and not supplied already. + */ + if (!buftvtots((const char *)&ev.tv, &tts)) + { + ERR(ERR_BADDATA) + msyslog(LOG_ERR,"parse: local_receive: timestamp conversion error (buftvtots) (ppsclockev.tv)"); + } + else + { + parse->parseio.parse_dtime.parse_ptime.fp = tts; + parse->parseio.parse_dtime.parse_state |= PARSEB_PPS|PARSEB_S_PPS; + } + } + parse->ppsserial = ev.serial; + } + } +#endif +#endif /* TIOCDCDTIMESTAMP */ + } + if (count) + { /* simulate receive */ + memmove((caddr_t)buf.recv_buffer, + (caddr_t)&parse->parseio.parse_dtime, + sizeof(parsetime_t)); + parse_iodone(&parse->parseio); + buf.recv_length = sizeof(parsetime_t); + buf.recv_time = rbufp->recv_time; + buf.srcadr = rbufp->srcadr; + buf.dstadr = rbufp->dstadr; + buf.fd = rbufp->fd; + buf.next = 0; + buf.X_from_where = rbufp->X_from_where; + rbufp->receiver(&buf); + } + else + { + memmove((caddr_t)rbufp->recv_buffer, + (caddr_t)&parse->parseio.parse_dtime, + sizeof(parsetime_t)); + parse_iodone(&parse->parseio); + rbufp->recv_length = sizeof(parsetime_t); + return 1; /* got something & in place return */ + } + } + } + return 0; /* nothing to pass up */ +} + +/*-------------------------------------------------- + * local receive + */ +static void +local_receive( + struct recvbuf *rbufp + ) +{ + struct parseunit *parse = (struct parseunit *)((void *)rbufp->recv_srcclock); + parsetime_t parsetime; + + if (!parse->peer) + return; + + if (rbufp->recv_length != sizeof(parsetime_t)) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR,"PARSE receiver #%d: local_receive: bad size (got %d expected %d)", + CLK_UNIT(parse->peer), rbufp->recv_length, (int)sizeof(parsetime_t)); + parse->generic->baddata++; + parse_event(parse, CEVNT_BADREPLY); + return; + } + clear_err(parse, ERR_BADIO); + + memmove((caddr_t)&parsetime, + (caddr_t)rbufp->recv_buffer, + sizeof(parsetime_t)); + +#ifdef DEBUG + if (debug > 3) + { + printf("PARSE receiver #%d: status %06x, state %08x, time %lx.%08lx, stime %lx.%08lx, ptime %lx.%08lx\n", + CLK_UNIT(parse->peer), + (unsigned int)parsetime.parse_status, + (unsigned int)parsetime.parse_state, + (long)parsetime.parse_time.tv.tv_sec, + (long)parsetime.parse_time.tv.tv_usec, + (long)parsetime.parse_stime.tv.tv_sec, + (long)parsetime.parse_stime.tv.tv_usec, + (long)parsetime.parse_ptime.tv.tv_sec, + (long)parsetime.parse_ptime.tv.tv_usec); + } +#endif + + parse_process(parse, &parsetime); +} + +/*-------------------------------------------------- + * init_iobinding - find and initialize lower layers + */ +static bind_t * +init_iobinding( + struct parseunit *parse + ) +{ + bind_t *b = io_bindings; + + while (b->bd_description != (char *)0) + { + if ((*b->bd_init)(parse)) + { + return b; + } + b++; + } + return (bind_t *)0; +} + +/**=========================================================================== + ** support routines + **/ + +/*-------------------------------------------------- + * convert a flag field to a string + */ +static char * +parsestate( + u_long lstate, + char *buffer + ) +{ + static struct bits + { + u_long bit; + const char *name; + } flagstrings[] = + { + { PARSEB_ANNOUNCE, "DST SWITCH WARNING" }, + { PARSEB_POWERUP, "NOT SYNCHRONIZED" }, + { PARSEB_NOSYNC, "TIME CODE NOT CONFIRMED" }, + { PARSEB_DST, "DST" }, + { PARSEB_UTC, "UTC DISPLAY" }, + { PARSEB_LEAPADD, "LEAP ADD WARNING" }, + { PARSEB_LEAPDEL, "LEAP DELETE WARNING" }, + { PARSEB_LEAPSECOND, "LEAP SECOND" }, + { PARSEB_ALTERNATE, "ALTERNATE ANTENNA" }, + { PARSEB_TIMECODE, "TIME CODE" }, + { PARSEB_PPS, "PPS" }, + { PARSEB_POSITION, "POSITION" }, + { 0 } + }; + + static struct sbits + { + u_long bit; + const char *name; + } sflagstrings[] = + { + { PARSEB_S_LEAP, "LEAP INDICATION" }, + { PARSEB_S_PPS, "PPS SIGNAL" }, + { PARSEB_S_ANTENNA, "ANTENNA" }, + { PARSEB_S_POSITION, "POSITION" }, + { 0 } + }; + int i; + + *buffer = '\0'; + + i = 0; + while (flagstrings[i].bit) + { + if (flagstrings[i].bit & lstate) + { + if (buffer[0]) + strcat(buffer, "; "); + strcat(buffer, flagstrings[i].name); + } + i++; + } + + if (lstate & (PARSEB_S_LEAP|PARSEB_S_ANTENNA|PARSEB_S_PPS|PARSEB_S_POSITION)) + { + char *s, *t; + + if (buffer[0]) + strcat(buffer, "; "); + + strcat(buffer, "("); + + t = s = buffer + strlen(buffer); + + i = 0; + while (sflagstrings[i].bit) + { + if (sflagstrings[i].bit & lstate) + { + if (t != s) + { + strcpy(t, "; "); + t += 2; + } + + strcpy(t, sflagstrings[i].name); + t += strlen(t); + } + i++; + } + strcpy(t, ")"); + } + return buffer; +} + +/*-------------------------------------------------- + * convert a status flag field to a string + */ +static char * +parsestatus( + u_long lstate, + char *buffer + ) +{ + static struct bits + { + u_long bit; + const char *name; + } flagstrings[] = + { + { CVT_OK, "CONVERSION SUCCESSFUL" }, + { CVT_NONE, "NO CONVERSION" }, + { CVT_FAIL, "CONVERSION FAILED" }, + { CVT_BADFMT, "ILLEGAL FORMAT" }, + { CVT_BADDATE, "DATE ILLEGAL" }, + { CVT_BADTIME, "TIME ILLEGAL" }, + { CVT_ADDITIONAL, "ADDITIONAL DATA" }, + { 0 } + }; + int i; + + *buffer = '\0'; + + i = 0; + while (flagstrings[i].bit) + { + if (flagstrings[i].bit & lstate) + { + if (buffer[0]) + strcat(buffer, "; "); + strcat(buffer, flagstrings[i].name); + } + i++; + } + + return buffer; +} + +/*-------------------------------------------------- + * convert a clock status flag field to a string + */ +static const char * +clockstatus( + u_long lstate + ) +{ + static char buffer[20]; + static struct status + { + u_long value; + const char *name; + } flagstrings[] = + { + { CEVNT_NOMINAL, "NOMINAL" }, + { CEVNT_TIMEOUT, "NO RESPONSE" }, + { CEVNT_BADREPLY,"BAD FORMAT" }, + { CEVNT_FAULT, "FAULT" }, + { CEVNT_PROP, "PROPAGATION DELAY" }, + { CEVNT_BADDATE, "ILLEGAL DATE" }, + { CEVNT_BADTIME, "ILLEGAL TIME" }, + { (unsigned)~0L } + }; + int i; + + i = 0; + while (flagstrings[i].value != ~0) + { + if (flagstrings[i].value == lstate) + { + return flagstrings[i].name; + } + i++; + } + + sprintf(buffer, "unknown #%ld", (u_long)lstate); + + return buffer; +} + + +/*-------------------------------------------------- + * l_mktime - make representation of a relative time + */ +static char * +l_mktime( + u_long delta + ) +{ + u_long tmp, m, s; + static char buffer[40]; + + buffer[0] = '\0'; + + if ((tmp = delta / (60*60*24)) != 0) + { + sprintf(buffer, "%ldd+", (u_long)tmp); + delta -= tmp * 60*60*24; + } + + s = delta % 60; + delta /= 60; + m = delta % 60; + delta /= 60; + + sprintf(buffer+strlen(buffer), "%02d:%02d:%02d", + (int)delta, (int)m, (int)s); + + return buffer; +} + + +/*-------------------------------------------------- + * parse_statistics - list summary of clock states + */ +static void +parse_statistics( + struct parseunit *parse + ) +{ + int i; + + NLOG(NLOG_CLOCKSTATIST) /* conditional if clause for conditional syslog */ + { + msyslog(LOG_INFO, "PARSE receiver #%d: running time: %s", + CLK_UNIT(parse->peer), + l_mktime(current_time - parse->generic->timestarted)); + + msyslog(LOG_INFO, "PARSE receiver #%d: current status: %s", + CLK_UNIT(parse->peer), + clockstatus(parse->generic->currentstatus)); + + for (i = 0; i <= CEVNT_MAX; i++) + { + u_long s_time; + u_long percent, d = current_time - parse->generic->timestarted; + + percent = s_time = PARSE_STATETIME(parse, i); + + while (((u_long)(~0) / 10000) < percent) + { + percent /= 10; + d /= 10; + } + + if (d) + percent = (percent * 10000) / d; + else + percent = 10000; + + if (s_time) + msyslog(LOG_INFO, "PARSE receiver #%d: state %18s: %13s (%3ld.%02ld%%)", + CLK_UNIT(parse->peer), + clockstatus((unsigned int)i), + l_mktime(s_time), + percent / 100, percent % 100); + } + } +} + +/*-------------------------------------------------- + * cparse_statistics - wrapper for statistics call + */ +static void +cparse_statistics( + register struct parseunit *parse + ) +{ + if (parse->laststatistic + PARSESTATISTICS < current_time) + parse_statistics(parse); + parse->laststatistic = current_time; +} + +/**=========================================================================== + ** ntp interface routines + **/ + +/*-------------------------------------------------- + * parse_init - initialize internal parse driver data + */ +static void +parse_init(void) +{ + memset((caddr_t)parseunits, 0, sizeof parseunits); +} + + +/*-------------------------------------------------- + * parse_shutdown - shut down a PARSE clock + */ +static void +parse_shutdown( + int unit, + struct peer *peer + ) +{ + struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + + if (parse && !parse->peer) + { + msyslog(LOG_ERR, + "PARSE receiver #%d: parse_shutdown: INTERNAL ERROR, unit not in use", unit); + return; + } + + /* + * print statistics a last time and + * stop statistics machine + */ + parse_statistics(parse); + + if (parse->parse_type->cl_end) + { + parse->parse_type->cl_end(parse); + } + + if (parse->binding) + PARSE_END(parse); + + /* + * Tell the I/O module to turn us off. We're history. + */ + io_closeclock(&parse->generic->io); + + free_varlist(parse->kv); + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: reference clock \"%s\" removed", + CLK_UNIT(parse->peer), parse->parse_type->cl_description); + + parse->peer = (struct peer *)0; /* unused now */ + free(parse); +} + +/*-------------------------------------------------- + * parse_start - open the PARSE devices and initialize data for processing + */ +static int +parse_start( + int sysunit, + struct peer *peer + ) +{ + u_int unit; + int fd232; +#ifdef HAVE_TERMIOS + struct termios tio; /* NEEDED FOR A LONG TIME ! */ +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; /* NEEDED FOR A LONG TIME ! */ +#endif + struct parseunit * parse; + char parsedev[sizeof(PARSEDEVICE)+20]; + parsectl_t tmp_ctl; + u_int type; + + type = CLK_TYPE(peer); + unit = CLK_UNIT(peer); + + if ((type == ~0) || (parse_clockinfo[type].cl_description == (char *)0)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: unsupported clock type %d (max %d)", + unit, CLK_REALTYPE(peer), ncltypes-1); + return 0; + } + + /* + * Unit okay, attempt to open the device. + */ + (void) sprintf(parsedev, PARSEDEVICE, unit); + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + + fd232 = open(parsedev, O_RDWR | O_NOCTTY +#ifdef O_NONBLOCK + | O_NONBLOCK +#endif + , 0777); + + if (fd232 == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: open of %s failed: %m", unit, parsedev); + return 0; + } + + parse = (struct parseunit *)emalloc(sizeof(struct parseunit)); + + memset((char *)parse, 0, sizeof(struct parseunit)); + + parse->generic = peer->procptr; /* link up */ + parse->generic->unitptr = (caddr_t)parse; /* link down */ + + /* + * Set up the structures + */ + parse->generic->timestarted = current_time; + parse->lastchange = current_time; + + parse->generic->currentstatus = CEVNT_TIMEOUT; /* expect the worst */ + + parse->flags = 0; + parse->pollneeddata = 0; + parse->laststatistic = current_time; + parse->lastformat = (unsigned short)~0; /* assume no format known */ + parse->time.parse_status = (unsigned short)~0; /* be sure to mark initial status change */ + parse->lastmissed = 0; /* assume got everything */ + parse->ppsserial = 0; + parse->localdata = (void *)0; + parse->localstate = 0; + parse->kv = (struct ctl_var *)0; + + clear_err(parse, ERR_ALL); + + parse->parse_type = &parse_clockinfo[type]; + + parse->generic->fudgetime1 = parse->parse_type->cl_basedelay; + + parse->generic->fudgetime2 = 0.0; + + parse->generic->clockdesc = parse->parse_type->cl_description; + + peer->rootdelay = parse->parse_type->cl_rootdelay; + peer->sstclktype = parse->parse_type->cl_type; + peer->precision = sys_precision; + + peer->stratum = STRATUM_REFCLOCK; + if (peer->stratum <= 1) + memmove((char *)&parse->generic->refid, parse->parse_type->cl_id, 4); + else + parse->generic->refid = htonl(PARSEHSREFID); + + parse->generic->io.fd = fd232; + + parse->peer = peer; /* marks it also as busy */ + + /* + * configure terminal line + */ + if (TTY_GETATTR(fd232, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcgetattr(%d, &tio): %m", unit, fd232); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + else + { +#ifndef _PC_VDISABLE + memset((char *)tio.c_cc, 0, sizeof(tio.c_cc)); +#else + int disablec; + errno = 0; /* pathconf can deliver -1 without changing errno ! */ + + disablec = fpathconf(parse->generic->io.fd, _PC_VDISABLE); + if (disablec == -1 && errno) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: fpathconf(fd, _PC_VDISABLE): %m", CLK_UNIT(parse->peer)); + memset((char *)tio.c_cc, 0, sizeof(tio.c_cc)); /* best guess */ + } + else + if (disablec != -1) + memset((char *)tio.c_cc, disablec, sizeof(tio.c_cc)); +#endif + +#if defined (VMIN) || defined(VTIME) + if ((parse_clockinfo[type].cl_lflag & ICANON) == 0) + { +#ifdef VMIN + tio.c_cc[VMIN] = 1; +#endif +#ifdef VTIME + tio.c_cc[VTIME] = 0; +#endif + } +#endif + + tio.c_cflag = parse_clockinfo[type].cl_cflag; + tio.c_iflag = parse_clockinfo[type].cl_iflag; + tio.c_oflag = parse_clockinfo[type].cl_oflag; + tio.c_lflag = parse_clockinfo[type].cl_lflag; + + +#ifdef HAVE_TERMIOS + if ((cfsetospeed(&tio, parse_clockinfo[type].cl_speed) == -1) || + (cfsetispeed(&tio, parse_clockinfo[type].cl_speed) == -1)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcset{i,o}speed(&tio, speed): %m", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } +#else + tio.c_cflag |= parse_clockinfo[type].cl_speed; +#endif + +#if defined(HAVE_TIO_SERIAL_STUFF) /* Linux hack: define PPS interface */ + { + struct serial_struct ss; + if (ioctl(fd232, TIOCGSERIAL, &ss) < 0 || + ( +#ifdef ASYNC_LOW_LATENCY + ss.flags |= ASYNC_LOW_LATENCY, +#endif +#ifdef ASYNC_PPS_CD_NEG + ss.flags |= ASYNC_PPS_CD_NEG, +#endif + ioctl(fd232, TIOCSSERIAL, &ss)) < 0) { + msyslog(LOG_NOTICE, "refclock_parse: TIOCSSERIAL fd %d, %m", fd232); + msyslog(LOG_NOTICE, + "refclock_parse: optional PPS processing not available"); + } else { + parse->flags |= PARSE_PPSCLOCK; + msyslog(LOG_INFO, + "refclock_parse: PPS detection on"); + } + } +#endif +#ifdef HAVE_TIOCSPPS /* SUN PPS support */ + if (CLK_PPS(parse->peer)) + { + int i = 1; + + if (ioctl(fd232, TIOCSPPS, (caddr_t)&i) == 0) + { + parse->flags |= PARSE_PPSCLOCK; + } + } +#endif + + if (TTY_SETATTR(fd232, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: tcsetattr(%d, &tio): %m", unit, fd232); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + } + + /* + * Insert in async io device list. + */ + parse->generic->io.srcclock = (caddr_t)parse; + parse->generic->io.datalen = 0; + + if (!io_addclock(&parse->generic->io)) + { + msyslog(LOG_ERR, + "PARSE receiver #%d: parse_start: addclock %s fails (ABORT - clock type requires async io)", CLK_UNIT(parse->peer), parsedev); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; + } + + parse->binding = init_iobinding(parse); + parse->generic->io.clock_recv = parse->binding->bd_receive; /* pick correct receive routine */ + parse->generic->io.io_input = parse->binding->bd_io_input; /* pick correct input routine */ + + if (parse->binding == (bind_t *)0) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: io sub system initialisation failed.", CLK_UNIT(parse->peer)); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + /* + * as we always(?) get 8 bit chars we want to be + * sure, that the upper bits are zero for less + * than 8 bit I/O - so we pass that information on. + * note that there can be only one bit count format + * per file descriptor + */ + + switch (tio.c_cflag & CSIZE) + { + case CS5: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS5; + break; + + case CS6: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS6; + break; + + case CS7: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS7; + break; + + case CS8: + tmp_ctl.parsesetcs.parse_cs = PARSE_IO_CS8; + break; + } + + if (!PARSE_SETCS(parse, &tmp_ctl)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: parse_setcs() FAILED.", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + strcpy(tmp_ctl.parseformat.parse_buffer, parse->parse_type->cl_format); + tmp_ctl.parseformat.parse_count = strlen(tmp_ctl.parseformat.parse_buffer); + + if (!PARSE_SETFMT(parse, &tmp_ctl)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_start: parse_setfmt() FAILED.", unit); + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + + /* + * get rid of all IO accumulated so far + */ +#ifdef HAVE_TERMIOS + (void) tcflush(parse->generic->io.fd, TCIOFLUSH); +#else +#ifdef TCFLSH + { +#ifndef TCIOFLUSH +#define TCIOFLUSH 2 +#endif + int flshcmd = TCIOFLUSH; + + (void) ioctl(parse->generic->io.fd, TCFLSH, (caddr_t)&flshcmd); + } +#endif +#endif + + /* + * try to do any special initializations + */ + if (parse->parse_type->cl_init) + { + if (parse->parse_type->cl_init(parse)) + { + parse_shutdown(CLK_UNIT(parse->peer), peer); /* let our cleaning staff do the work */ + return 0; /* well, ok - special initialisation broke */ + } + } + + /* + * get out Copyright information once + */ + if (!notice) + { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "NTP PARSE support: Copyright (c) 1989-1999, Frank Kardel"); + notice = 1; + } + + /* + * print out configuration + */ + NLOG(NLOG_CLOCKINFO) + { + /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: reference clock \"%s\" (device %s) added", + CLK_UNIT(parse->peer), + parse->parse_type->cl_description, parsedev); + + msyslog(LOG_INFO, "PARSE receiver #%d: Stratum %d, %sPPS support, trust time %s, precision %d", + CLK_UNIT(parse->peer), + parse->peer->stratum, CLK_PPS(parse->peer) ? "" : "no ", + l_mktime(parse->parse_type->cl_maxunsync), parse->peer->precision); + + msyslog(LOG_INFO, "PARSE receiver #%d: rootdelay %.6f s, phaseadjust %.6f s, %s IO handling", + CLK_UNIT(parse->peer), + parse->parse_type->cl_rootdelay, + parse->generic->fudgetime1, + parse->binding->bd_description); + + msyslog(LOG_INFO, "PARSE receiver #%d: Format recognition: %s", CLK_UNIT(parse->peer), + parse->parse_type->cl_format); +#ifdef PPS + msyslog(LOG_INFO, "PARSE receiver #%d: %sPPS ioctl support", CLK_UNIT(parse->peer), + (parse->flags & PARSE_PPSCLOCK) ? "" : "NO "); +#endif + } + + return 1; +} + +/*-------------------------------------------------- + * parse_poll - called by the transmit procedure + */ +static void +parse_poll( + int unit, + struct peer *peer + ) +{ + struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + + if (peer != parse->peer) + { + msyslog(LOG_ERR, + "PARSE receiver #%d: poll: INTERNAL: peer incorrect", + unit); + return; + } + + /* + * Update clock stat counters + */ + parse->generic->polls++; + + if (parse->pollneeddata && + ((current_time - parse->pollneeddata) > (1<<(max(min(parse->peer->hpoll, parse->peer->ppoll), parse->peer->minpoll))))) + { + /* + * start worrying when exceeding a poll inteval + * bad news - didn't get a response last time + */ + parse->generic->noreply++; + parse->lastmissed = current_time; + parse_event(parse, CEVNT_TIMEOUT); + + ERR(ERR_NODATA) + msyslog(LOG_WARNING, "PARSE receiver #%d: no data from device within poll interval (check receiver / cableling)", CLK_UNIT(parse->peer)); + } + + /* + * we just mark that we want the next sample for the clock filter + */ + parse->pollneeddata = current_time; + + if (parse->parse_type->cl_poll) + { + parse->parse_type->cl_poll(parse); + } + + cparse_statistics(parse); + + return; +} + +#define LEN_STATES 300 /* length of state string */ + +/*-------------------------------------------------- + * parse_control - set fudge factors, return statistics + */ +static void +parse_control( + int unit, + struct refclockstat *in, + struct refclockstat *out, + struct peer *peer + ) +{ + register struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + parsectl_t tmpctl; + + static char outstatus[400]; /* status output buffer */ + + if (out) + { + out->lencode = 0; + out->p_lastcode = 0; + out->kv_list = (struct ctl_var *)0; + } + + if (!parse || !parse->peer) + { + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: unit invalid (UNIT INACTIVE)", + unit); + return; + } + + unit = CLK_UNIT(parse->peer); + + if (in) + { + if (in->haveflags & (CLK_HAVEFLAG1|CLK_HAVEFLAG2|CLK_HAVEFLAG3|CLK_HAVEFLAG4)) + { + parse->flags = in->flags & (CLK_FLAG1|CLK_FLAG2|CLK_FLAG3|CLK_FLAG4); + } + } + + if (out) + { + u_long sum = 0; + char *t, *tt, *start; + int i; + + outstatus[0] = '\0'; + + out->type = REFCLK_PARSE; + out->haveflags |= CLK_HAVETIME2; + + /* + * figure out skew between PPS and RS232 - just for informational + * purposes - returned in time2 value + */ + if (PARSE_SYNC(parse->time.parse_state)) + { + if (PARSE_PPS(parse->time.parse_state) && PARSE_TIMECODE(parse->time.parse_state)) + { + l_fp off; + + /* + * we have a PPS and RS232 signal - calculate the skew + * WARNING: assumes on TIMECODE == PULSE (timecode after pulse) + */ + off = parse->time.parse_stime.fp; + L_SUB(&off, &parse->time.parse_ptime.fp); /* true offset */ + tt = add_var(&out->kv_list, 80, RO); + sprintf(tt, "refclock_ppsskew=%s", lfptoms(&off, 6)); + } + } + + if (PARSE_PPS(parse->time.parse_state)) + { + tt = add_var(&out->kv_list, 80, RO|DEF); + sprintf(tt, "refclock_ppstime=\"%s\"", gmprettydate(&parse->time.parse_ptime.fp)); + } + + tt = add_var(&out->kv_list, 128, RO|DEF); + sprintf(tt, "refclock_time=\""); + tt += strlen(tt); + + if (parse->time.parse_time.fp.l_ui == 0) + { + strcpy(tt, "<UNDEFINED>\""); + } + else + { + sprintf(tt, "%s\"", gmprettydate(&parse->time.parse_time.fp)); + t = tt + strlen(tt); + } + + if (!PARSE_GETTIMECODE(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: parse_timecode() FAILED", unit); + } + else + { + tt = add_var(&out->kv_list, 512, RO|DEF); + sprintf(tt, "refclock_status=\""); + tt += strlen(tt); + + /* + * copy PPS flags from last read transaction (informational only) + */ + tmpctl.parsegettc.parse_state |= parse->time.parse_state & + (PARSEB_PPS|PARSEB_S_PPS); + + (void) parsestate(tmpctl.parsegettc.parse_state, tt); + + strcat(tt, "\""); + + if (tmpctl.parsegettc.parse_count) + mkascii(outstatus+strlen(outstatus), (int)(sizeof(outstatus)- strlen(outstatus) - 1), + tmpctl.parsegettc.parse_buffer, (unsigned)(tmpctl.parsegettc.parse_count - 1)); + + parse->generic->badformat += tmpctl.parsegettc.parse_badformat; + } + + tmpctl.parseformat.parse_format = tmpctl.parsegettc.parse_format; + + if (!PARSE_GETFMT(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_control: parse_getfmt() FAILED", unit); + } + else + { + tt = add_var(&out->kv_list, 80, RO|DEF); + sprintf(tt, "refclock_format=\""); + + strncat(tt, tmpctl.parseformat.parse_buffer, tmpctl.parseformat.parse_count); + strcat(tt,"\""); + } + + /* + * gather state statistics + */ + + start = tt = add_var(&out->kv_list, LEN_STATES, RO|DEF); + strcpy(tt, "refclock_states=\""); + tt += strlen(tt); + + for (i = 0; i <= CEVNT_MAX; i++) + { + u_long s_time; + u_long d = current_time - parse->generic->timestarted; + u_long percent; + + percent = s_time = PARSE_STATETIME(parse, i); + + while (((u_long)(~0) / 10000) < percent) + { + percent /= 10; + d /= 10; + } + + if (d) + percent = (percent * 10000) / d; + else + percent = 10000; + + if (s_time) + { + char item[80]; + int count; + + sprintf(item, "%s%s%s: %s (%d.%02d%%)", + sum ? "; " : "", + (parse->generic->currentstatus == i) ? "*" : "", + clockstatus((unsigned int)i), + l_mktime(s_time), + (int)(percent / 100), (int)(percent % 100)); + if ((count = strlen(item)) < (LEN_STATES - 40 - (tt - start))) + { + strcpy(tt, item); + tt += count; + } + sum += s_time; + } + } + + sprintf(tt, "; running time: %s\"", l_mktime(sum)); + + tt = add_var(&out->kv_list, 32, RO); + sprintf(tt, "refclock_id=\"%s\"", parse->parse_type->cl_id); + + tt = add_var(&out->kv_list, 80, RO); + sprintf(tt, "refclock_iomode=\"%s\"", parse->binding->bd_description); + + tt = add_var(&out->kv_list, 128, RO); + sprintf(tt, "refclock_driver_version=\"%s\"", rcsid); + + { + struct ctl_var *k; + + k = parse->kv; + while (k && !(k->flags & EOV)) + { + set_var(&out->kv_list, k->text, strlen(k->text)+1, k->flags); + k++; + } + } + + out->lencode = strlen(outstatus); + out->p_lastcode = outstatus; + } +} + +/**=========================================================================== + ** processing routines + **/ + +/*-------------------------------------------------- + * event handling - note that nominal events will also be posted + */ +static void +parse_event( + struct parseunit *parse, + int event + ) +{ + if (parse->generic->currentstatus != (u_char) event) + { + parse->statetime[parse->generic->currentstatus] += current_time - parse->lastchange; + parse->lastchange = current_time; + + parse->generic->currentstatus = (u_char)event; + + if (parse->parse_type->cl_event) + parse->parse_type->cl_event(parse, event); + + if (event != CEVNT_NOMINAL) + { + parse->generic->lastevent = parse->generic->currentstatus; + } + else + { + NLOG(NLOG_CLOCKSTATUS) + msyslog(LOG_INFO, "PARSE receiver #%d: SYNCHRONIZED", + CLK_UNIT(parse->peer)); + } + + if (event == CEVNT_FAULT) + { + NLOG(NLOG_CLOCKEVENT) /* conditional if clause for conditional syslog */ + ERR(ERR_BADEVENT) + msyslog(LOG_ERR, + "clock %s fault '%s' (0x%02x)", refnumtoa(&parse->peer->srcadr), ceventstr(event), + (u_int)event); + } + else + { + NLOG(NLOG_CLOCKEVENT) /* conditional if clause for conditional syslog */ + if (event == CEVNT_NOMINAL || list_err(parse, ERR_BADEVENT)) + msyslog(LOG_INFO, + "clock %s event '%s' (0x%02x)", refnumtoa(&parse->peer->srcadr), ceventstr(event), + (u_int)event); + } + + report_event(EVNT_PEERCLOCK, parse->peer); + report_event(EVNT_CLOCKEXCPT, parse->peer); + } +} + +/*-------------------------------------------------- + * process a PARSE time sample + */ +static void +parse_process( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + l_fp off, rectime, reftime; + double fudge; + + /* + * check for changes in conversion status + * (only one for each new status !) + */ + if (((parsetime->parse_status & CVT_MASK) != CVT_OK) && + ((parsetime->parse_status & CVT_MASK) != CVT_NONE) && + (parse->time.parse_status != parsetime->parse_status)) + { + char buffer[400]; + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_WARNING, "PARSE receiver #%d: conversion status \"%s\"", + CLK_UNIT(parse->peer), parsestatus(parsetime->parse_status, buffer)); + + if ((parsetime->parse_status & CVT_MASK) == CVT_FAIL) + { + /* + * tell more about the story - list time code + * there is a slight change for a race condition and + * the time code might be overwritten by the next packet + */ + parsectl_t tmpctl; + + if (!PARSE_GETTIMECODE(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_process: parse_timecode() FAILED", CLK_UNIT(parse->peer)); + } + else + { + ERR(ERR_BADDATA) + msyslog(LOG_WARNING, "PARSE receiver #%d: FAILED TIMECODE: \"%s\" (check receiver configuration / cableling)", + CLK_UNIT(parse->peer), mkascii(buffer, sizeof buffer, tmpctl.parsegettc.parse_buffer, (unsigned)(tmpctl.parsegettc.parse_count - 1))); + parse->generic->badformat += tmpctl.parsegettc.parse_badformat; + } + } + } + + /* + * examine status and post appropriate events + */ + if ((parsetime->parse_status & CVT_MASK) != CVT_OK) + { + /* + * got bad data - tell the rest of the system + */ + switch (parsetime->parse_status & CVT_MASK) + { + case CVT_NONE: + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + break; /* well, still waiting - timeout is handled at higher levels */ + + case CVT_FAIL: + parse->generic->badformat++; + if (parsetime->parse_status & CVT_BADFMT) + { + parse_event(parse, CEVNT_BADREPLY); + } + else + if (parsetime->parse_status & CVT_BADDATE) + { + parse_event(parse, CEVNT_BADDATE); + } + else + if (parsetime->parse_status & CVT_BADTIME) + { + parse_event(parse, CEVNT_BADTIME); + } + else + { + parse_event(parse, CEVNT_BADREPLY); /* for the lack of something better */ + } + } + return; /* skip the rest - useless */ + } + + /* + * check for format changes + * (in case somebody has swapped clocks 8-) + */ + if (parse->lastformat != parsetime->parse_format) + { + parsectl_t tmpctl; + + tmpctl.parseformat.parse_format = parsetime->parse_format; + + if (!PARSE_GETFMT(parse, &tmpctl)) + { + ERR(ERR_INTERNAL) + msyslog(LOG_ERR, "PARSE receiver #%d: parse_getfmt() FAILED", CLK_UNIT(parse->peer)); + } + else + { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO, "PARSE receiver #%d: packet format \"%s\"", + CLK_UNIT(parse->peer), tmpctl.parseformat.parse_buffer); + } + parse->lastformat = parsetime->parse_format; + } + + /* + * now, any changes ? + */ + if (parse->time.parse_state != parsetime->parse_state) + { + char tmp1[200]; + char tmp2[200]; + /* + * something happend + */ + + (void) parsestate(parsetime->parse_state, tmp1); + (void) parsestate(parse->time.parse_state, tmp2); + + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_INFO,"PARSE receiver #%d: STATE CHANGE: %s -> %s", + CLK_UNIT(parse->peer), tmp2, tmp1); + } + + /* + * remember for future + */ + parse->time = *parsetime; + + /* + * check to see, whether the clock did a complete powerup or lost PZF signal + * and post correct events for current condition + */ + if (PARSE_POWERUP(parsetime->parse_state)) + { + /* + * this is bad, as we have completely lost synchronisation + * well this is a problem with the receiver here + * for PARSE Meinberg DCF77 receivers the lost synchronisation + * is true as it is the powerup state and the time is taken + * from a crude real time clock chip + * for the PZF series this is only partly true, as + * PARSE_POWERUP only means that the pseudo random + * phase shift sequence cannot be found. this is only + * bad, if we have never seen the clock in the SYNC + * state, where the PHASE and EPOCH are correct. + * for reporting events the above business does not + * really matter, but we can use the time code + * even in the POWERUP state after having seen + * the clock in the synchronized state (PZF class + * receivers) unless we have had a telegram disruption + * after having seen the clock in the SYNC state. we + * thus require having seen the clock in SYNC state + * *after* having missed telegrams (noresponse) from + * the clock. one problem remains: we might use erroneously + * POWERUP data if the disruption is shorter than 1 polling + * interval. fortunately powerdowns last usually longer than 64 + * seconds and the receiver is at least 2 minutes in the + * POWERUP or NOSYNC state before switching to SYNC + */ + parse_event(parse, CEVNT_FAULT); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: NOT SYNCHRONIZED", + CLK_UNIT(parse->peer)); + } + else + { + /* + * we have two states left + * + * SYNC: + * this state means that the EPOCH (timecode) and PHASE + * information has be read correctly (at least two + * successive PARSE timecodes were received correctly) + * this is the best possible state - full trust + * + * NOSYNC: + * The clock should be on phase with respect to the second + * signal, but the timecode has not been received correctly within + * at least the last two minutes. this is a sort of half baked state + * for PARSE Meinberg DCF77 clocks this is bad news (clock running + * without timecode confirmation) + * PZF 535 has also no time confirmation, but the phase should be + * very precise as the PZF signal can be decoded + */ + + if (PARSE_SYNC(parsetime->parse_state)) + { + /* + * currently completely synchronized - best possible state + */ + parse->lastsync = current_time; + clear_err(parse, ERR_BADSTATUS); + } + else + { + /* + * we have had some problems receiving the time code + */ + parse_event(parse, CEVNT_PROP); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: TIMECODE NOT CONFIRMED", + CLK_UNIT(parse->peer)); + } + } + + fudge = parse->generic->fudgetime1; /* standard RS232 Fudgefactor */ + + if (PARSE_TIMECODE(parsetime->parse_state)) + { + rectime = parsetime->parse_stime.fp; + off = reftime = parsetime->parse_time.fp; + + L_SUB(&off, &rectime); /* prepare for PPS adjustments logic */ + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: Reftime %s, Recvtime %s - initial offset %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + lfptoa(&off,6)); +#endif + } + + if (PARSE_PPS(parsetime->parse_state) && CLK_PPS(parse->peer)) + { + l_fp offset; + + /* + * we have a PPS signal - much better than the RS232 stuff (we hope) + */ + offset = parsetime->parse_ptime.fp; + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: PPStime %s\n", + CLK_UNIT(parse->peer), + prettydate(&offset)); +#endif + if (PARSE_TIMECODE(parsetime->parse_state)) + { + if (M_ISGEQ(off.l_i, off.l_f, -1, 0x80000000) && + M_ISGEQ(0, 0x7fffffff, off.l_i, off.l_f)) + { + fudge = parse->generic->fudgetime2; /* pick PPS fudge factor */ + + /* + * RS232 offsets within [-0.5..0.5[ - take PPS offsets + */ + + if (parse->parse_type->cl_flags & PARSE_F_PPSONSECOND) + { + reftime = off = offset; + if (reftime.l_uf & (unsigned)0x80000000) + reftime.l_ui++; + reftime.l_uf = 0; + + + /* + * implied on second offset + */ + off.l_uf = ~off.l_uf; /* map [0.5..1[ -> [-0.5..0[ */ + off.l_ui = (off.l_f < 0) ? ~0 : 0; /* sign extend */ + } + else + { + /* + * time code describes pulse + */ + reftime = off = parsetime->parse_time.fp; + + L_SUB(&off, &offset); /* true offset */ + } + } + /* + * take RS232 offset when PPS when out of bounds + */ + } + else + { + fudge = parse->generic->fudgetime2; /* pick PPS fudge factor */ + /* + * Well, no time code to guide us - assume on second pulse + * and pray, that we are within [-0.5..0.5[ + */ + off = offset; + reftime = offset; + if (reftime.l_uf & (unsigned)0x80000000) + reftime.l_ui++; + reftime.l_uf = 0; + /* + * implied on second offset + */ + off.l_uf = ~off.l_uf; /* map [0.5..1[ -> [-0.5..0[ */ + off.l_ui = (off.l_f < 0) ? ~0 : 0; /* sign extend */ + } + } + else + { + if (!PARSE_TIMECODE(parsetime->parse_state)) + { + /* + * Well, no PPS, no TIMECODE, no more work ... + */ + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + return; + } + } + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: Reftime %s, Recvtime %s - final offset %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + lfptoa(&off,6)); +#endif + + + rectime = reftime; + L_SUB(&rectime, &off); /* just to keep the ntp interface happy */ + +#ifdef DEBUG + if (debug > 3) + printf("PARSE receiver #%d: calculated Reftime %s, Recvtime %s\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime)); +#endif + + if ((parsetime->parse_status & CVT_ADDITIONAL) && + parse->parse_type->cl_message) + parse->parse_type->cl_message(parse, parsetime); + + if (PARSE_SYNC(parsetime->parse_state)) + { + /* + * log OK status + */ + parse_event(parse, CEVNT_NOMINAL); + } + + clear_err(parse, ERR_BADIO); + clear_err(parse, ERR_BADDATA); + clear_err(parse, ERR_NODATA); + clear_err(parse, ERR_INTERNAL); + +#ifdef DEBUG + if (debug > 2) + { + printf("PARSE receiver #%d: refclock_process_offset(reftime=%s, rectime=%s, Fudge=%f)\n", + CLK_UNIT(parse->peer), + prettydate(&reftime), + prettydate(&rectime), + fudge); + } +#endif + + refclock_process_offset(parse->generic, reftime, rectime, fudge); + if (PARSE_PPS(parsetime->parse_state) && CLK_PPS(parse->peer)) + { + (void) pps_sample(&parse->time.parse_ptime.fp); + } + + + /* + * and now stick it into the clock machine + * samples are only valid iff lastsync is not too old and + * we have seen the clock in sync at least once + * after the last time we didn't see an expected data telegram + * see the clock states section above for more reasoning + */ + if (((current_time - parse->lastsync) > parse->parse_type->cl_maxunsync) || + (parse->lastsync <= parse->lastmissed)) + { + parse->generic->leap = LEAP_NOTINSYNC; + } + else + { + if (PARSE_LEAPADD(parsetime->parse_state)) + { + /* + * we pick this state also for time code that pass leap warnings + * without direction information (as earth is currently slowing + * down). + */ + parse->generic->leap = (parse->flags & PARSE_LEAP_DELETE) ? LEAP_DELSECOND : LEAP_ADDSECOND; + } + else + if (PARSE_LEAPDEL(parsetime->parse_state)) + { + parse->generic->leap = LEAP_DELSECOND; + } + else + { + parse->generic->leap = LEAP_NOWARNING; + } + } + + /* + * ready, unless the machine wants a sample + */ + if (!parse->pollneeddata) + return; + + parse->pollneeddata = 0; + + refclock_receive(parse->peer); +} + +/**=========================================================================== + ** special code for special clocks + **/ + +static void +mk_utcinfo( + char *t, + int wnt, + int wnlsf, + int dn, + int dtls, + int dtlsf + ) +{ + l_fp leapdate; + + sprintf(t, "current correction %d sec", dtls); + t += strlen(t); + + if (wnlsf < 990) + wnlsf += 1024; + + if (wnt < 990) + wnt += 1024; + + gpstolfp((unsigned short)wnlsf, (unsigned short)dn, 0, &leapdate); + + if ((dtlsf != dtls) && + ((wnlsf - wnt) < 52)) + { + sprintf(t, ", next correction %d sec on %s, new GPS-UTC offset %d", + dtlsf - dtls, gmprettydate(&leapdate), dtlsf); + } + else + { + sprintf(t, ", last correction on %s", + gmprettydate(&leapdate)); + } +} + +#ifdef CLOCK_MEINBERG +/**=========================================================================== + ** Meinberg GPS166/GPS167 support + **/ + +/*------------------------------------------------------------ + * gps16x_message - process GPS16x messages + */ +static void +gps16x_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + if (parse->time.parse_msglen && parsetime->parse_msg[0] == SOH) + { + GPS_MSG_HDR header; + unsigned char *bufp = (unsigned char *)parsetime->parse_msg + 1; + +#ifdef DEBUG + if (debug > 2) + { + char msgbuffer[600]; + + mkreadable(msgbuffer, sizeof(msgbuffer), (char *)parsetime->parse_msg, parsetime->parse_msglen, 1); + printf("PARSE receiver #%d: received message (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + parsetime->parse_msglen, + msgbuffer); + } +#endif + get_mbg_header(&bufp, &header); + if (header.gps_hdr_csum == mbg_csum(parsetime->parse_msg + 1, 6) && + (header.gps_len == 0 || + (header.gps_len < sizeof(parsetime->parse_msg) && + header.gps_data_csum == mbg_csum(bufp, header.gps_len)))) + { + /* + * clean message + */ + switch (header.gps_cmd) + { + case GPS_SW_REV: + { + char buffer[64]; + SW_REV gps_sw_rev; + + get_mbg_sw_rev(&bufp, &gps_sw_rev); + sprintf(buffer, "meinberg_gps_version=\"%x.%02x%s%s\"", + (gps_sw_rev.code >> 8) & 0xFF, + gps_sw_rev.code & 0xFF, + gps_sw_rev.name[0] ? " " : "", + gps_sw_rev.name); + set_var(&parse->kv, buffer, 64, RO|DEF); + } + break; + + case GPS_STAT: + { + static struct state + { + unsigned short flag; /* status flag */ + unsigned const char *string; /* bit name */ + } states[] = + { + { TM_ANT_DISCONN, (const unsigned char *)"ANTENNA FAULTY" }, + { TM_SYN_FLAG, (const unsigned char *)"NO SYNC SIGNAL" }, + { TM_NO_SYNC, (const unsigned char *)"NO SYNC POWERUP" }, + { TM_NO_POS, (const unsigned char *)"NO POSITION" }, + { 0, (const unsigned char *)"" } + }; + unsigned short status; + struct state *s = states; + char buffer[512]; + char *p, *b; + + status = get_lsb_short(&bufp); + sprintf(buffer, "meinberg_gps_status=\"[0x%04x] ", status); + + if (status) + { + p = b = buffer + strlen(buffer); + while (s->flag) + { + if (status & s->flag) + { + if (p != b) + { + *p++ = ','; + *p++ = ' '; + } + + strcat(p, (const char *)s->string); + } + s++; + } + + *p++ = '"'; + *p = '\0'; + } + else + { + strcat(buffer, "<OK>\""); + } + + set_var(&parse->kv, buffer, 64, RO|DEF); + } + break; + + case GPS_POS_XYZ: + { + XYZ xyz; + char buffer[256]; + + get_mbg_xyz(&bufp, xyz); + sprintf(buffer, "gps_position(XYZ)=\"%s m, %s m, %s m\"", + mfptoa(xyz[XP].l_ui, xyz[XP].l_uf, 1), + mfptoa(xyz[YP].l_ui, xyz[YP].l_uf, 1), + mfptoa(xyz[ZP].l_ui, xyz[ZP].l_uf, 1)); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_POS_LLA: + { + LLA lla; + char buffer[256]; + + get_mbg_lla(&bufp, lla); + + sprintf(buffer, "gps_position(LLA)=\"%s deg, %s deg, %s m\"", + mfptoa(lla[LAT].l_ui, lla[LAT].l_uf, 4), + mfptoa(lla[LON].l_ui, lla[LON].l_uf, 4), + mfptoa(lla[ALT].l_ui, lla[ALT].l_uf, 1)); + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_TZDL: + break; + + case GPS_PORT_PARM: + break; + + case GPS_SYNTH: + break; + + case GPS_ANT_INFO: + { + ANT_INFO antinfo; + u_char buffer[512]; + u_char *p; + + get_mbg_antinfo(&bufp, &antinfo); + sprintf(buffer, "meinberg_antenna_status=\""); + p = buffer + strlen(buffer); + + switch (antinfo.status) + { + case ANT_INVALID: + strcat(p, "<OK>"); + p += strlen(p); + break; + + case ANT_DISCONN: + strcat(p, "DISCONNECTED since "); + NLOG(NLOG_CLOCKSTATUS) + ERR(ERR_BADSTATUS) + msyslog(LOG_ERR,"PARSE receiver #%d: ANTENNA FAILURE: %s", + CLK_UNIT(parse->peer), p); + + p += strlen(p); + mbg_tm_str(&p, &antinfo.tm_disconn); + *p = '\0'; + break; + + case ANT_RECONN: + strcat(p, "RECONNECTED on "); + p += strlen(p); + mbg_tm_str(&p, &antinfo.tm_reconn); + sprintf(p, ", reconnect clockoffset %c%ld.%07ld s, disconnect time ", + (antinfo.delta_t < 0) ? '-' : '+', + ABS(antinfo.delta_t) / 10000, + ABS(antinfo.delta_t) % 10000); + p += strlen(p); + mbg_tm_str(&p, &antinfo.tm_disconn); + *p = '\0'; + break; + + default: + sprintf(p, "bad status 0x%04x", antinfo.status); + p += strlen(p); + break; + } + + *p++ = '"'; + *p = '\0'; + + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_UCAP: + break; + + case GPS_CFGH: + { + CFGH cfgh; + u_char buffer[512]; + u_char *p; + + get_mbg_cfgh(&bufp, &cfgh); + if (cfgh.valid) + { + int i; + + p = buffer; + strcpy(buffer, "gps_tot_51=\""); + p += strlen(p); + mbg_tgps_str(&p, &cfgh.tot_51); + *p++ = '"'; + *p = '\0'; + set_var(&parse->kv, buffer, sizeof(buffer), RO); + + p = buffer; + strcpy(buffer, "gps_tot_63=\""); + p += strlen(p); + mbg_tgps_str(&p, &cfgh.tot_63); + *p++ = '"'; + *p = '\0'; + set_var(&parse->kv, buffer, sizeof(buffer), RO); + + p = buffer; + strcpy(buffer, "gps_t0a=\""); + p += strlen(p); + mbg_tgps_str(&p, &cfgh.t0a); + *p++ = '"'; + *p = '\0'; + set_var(&parse->kv, buffer, sizeof(buffer), RO); + + for (i = MIN_SVNO; i <= MAX_SVNO; i++) + { + p = buffer; + sprintf(p, "gps_cfg[%d]=\"[0x%x] ", i, cfgh.cfg[i]); + p += strlen(p); + switch (cfgh.cfg[i] & 0x7) + { + case 0: + strcpy(p, "BLOCK I"); + break; + case 1: + strcpy(p, "BLOCK II"); + break; + default: + sprintf(p, "bad CFG"); + break; + } + strcat(p, "\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO); + + p = buffer; + sprintf(p, "gps_health[%d]=\"[0x%x] ", i, cfgh.health[i]); + p += strlen(p); + switch ((cfgh.health[i] >> 5) & 0x7 ) + { + case 0: + strcpy(p, "OK;"); + break; + case 1: + strcpy(p, "PARITY;"); + break; + case 2: + strcpy(p, "TLM/HOW;"); + break; + case 3: + strcpy(p, "Z-COUNT;"); + break; + case 4: + strcpy(p, "SUBFRAME 1,2,3;"); + break; + case 5: + strcpy(p, "SUBFRAME 4,5;"); + break; + case 6: + strcpy(p, "UPLOAD BAD;"); + break; + case 7: + strcpy(p, "DATA BAD;"); + break; + } + + p += strlen(p); + + switch (cfgh.health[i] & 0x1F) + { + case 0: + strcpy(p, "SIGNAL OK"); + break; + case 0x1C: + strcpy(p, "SV TEMP OUT"); + break; + case 0x1D: + strcpy(p, "SV WILL BE TEMP OUT"); + break; + case 0x1E: + break; + case 0x1F: + strcpy(p, "MULTIPLE ERRS"); + break; + default: + strcpy(p, "TRANSMISSION PROBLEMS"); + break; + } + + strcat(p, "\""); + set_var(&parse->kv, buffer, sizeof(buffer), RO); + } + } + } + break; + + case GPS_ALM: + break; + + case GPS_EPH: + break; + + case GPS_UTC: + { + UTC utc; + char buffer[512]; + char *p; + + p = buffer; + + get_mbg_utc(&bufp, &utc); + + if (utc.valid) + { + strcpy(p, "gps_utc_correction=\""); + p += strlen(p); + mk_utcinfo(p, utc.t0t.wn, utc.WNlsf, utc.DNt, utc.delta_tls, utc.delta_tlsf); + strcat(p, "\""); + } + else + { + strcpy(p, "gps_utc_correction=\"<NO UTC DATA>\""); + } + set_var(&parse->kv, buffer, sizeof(buffer), RO|DEF); + } + break; + + case GPS_IONO: + break; + + case GPS_ASCII_MSG: + { + ASCII_MSG gps_ascii_msg; + char buffer[128]; + + get_mbg_ascii_msg(&bufp, &gps_ascii_msg); + + if (gps_ascii_msg.valid) + { + char buffer1[128]; + mkreadable(buffer1, sizeof(buffer1), gps_ascii_msg.s, strlen(gps_ascii_msg.s), (int)0); + + sprintf(buffer, "gps_message=\"%s\"", buffer1); + } + else + strcpy(buffer, "gps_message=<NONE>"); + + set_var(&parse->kv, buffer, 128, RO|DEF); + } + + break; + + default: + break; + } + } + else + { + msyslog(LOG_DEBUG, "PARSE receiver #%d: gps16x_message: message checksum error: hdr_csum = 0x%x (expected 0x%lx), data_len = %d, data_csum = 0x%x (expected 0x%lx)", + CLK_UNIT(parse->peer), + header.gps_hdr_csum, mbg_csum(parsetime->parse_msg + 1, 6), + header.gps_len, + header.gps_data_csum, mbg_csum(bufp, (unsigned)((header.gps_len < sizeof(parsetime->parse_msg)) ? header.gps_len : 0))); + } + } + + return; +} + +/*------------------------------------------------------------ + * gps16x_poll - query the reciver peridically + */ +static void +gps16x_poll( + struct peer *peer + ) +{ + struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + + static GPS_MSG_HDR sequence[] = + { + { GPS_SW_REV, 0, 0, 0 }, + { GPS_STAT, 0, 0, 0 }, + { GPS_UTC, 0, 0, 0 }, + { GPS_ASCII_MSG, 0, 0, 0 }, + { GPS_ANT_INFO, 0, 0, 0 }, + { GPS_CFGH, 0, 0, 0 }, + { GPS_POS_XYZ, 0, 0, 0 }, + { GPS_POS_LLA, 0, 0, 0 }, + { (unsigned short)~0, 0, 0, 0 } + }; + + int rtc; + unsigned char cmd_buffer[64]; + unsigned char *outp = cmd_buffer; + GPS_MSG_HDR *header; + + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->nextaction = current_time + ((poll_info_t *)parse->parse_type->cl_data)->rate; + } + + if (sequence[parse->localstate].gps_cmd == (unsigned short)~0) + parse->localstate = 0; + + header = sequence + parse->localstate++; + + *outp++ = SOH; /* start command */ + + put_mbg_header(&outp, header); + outp = cmd_buffer + 1; + + header->gps_hdr_csum = (short)mbg_csum(outp, 6); + put_mbg_header(&outp, header); + +#ifdef DEBUG + if (debug > 2) + { + char buffer[128]; + + mkreadable(buffer, sizeof(buffer), (char *)cmd_buffer, (unsigned)(outp - cmd_buffer), 1); + printf("PARSE receiver #%d: transmitted message #%ld (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + parse->localstate - 1, + (int)(outp - cmd_buffer), + buffer); + } +#endif + + rtc = write(parse->generic->io.fd, cmd_buffer, (unsigned long)(outp - cmd_buffer)); + + if (rtc < 0) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: gps16x_poll: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + if (rtc != outp - cmd_buffer) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: gps16x_poll: failed to send cmd incomplete (%d of %d bytes sent)", CLK_UNIT(parse->peer), rtc, (int)(outp - cmd_buffer)); + } + + clear_err(parse, ERR_BADIO); + return; +} + +/*-------------------------------------------------- + * init routine - setup timer + */ +static int +gps16x_poll_init( + struct parseunit *parse + ) +{ + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->action = gps16x_poll; + gps16x_poll(parse->peer); + } + + return 0; +} + +#else +static void +gps16x_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{} +static int +gps16x_poll_init( + struct parseunit *parse + ) +{ + return 1; +} +#endif /* CLOCK_MEINBERG */ + +/**=========================================================================== + ** clock polling support + **/ + +/*-------------------------------------------------- + * direct poll routine + */ +static void +poll_dpoll( + struct parseunit *parse + ) +{ + int rtc; + const char *ps = ((poll_info_t *)parse->parse_type->cl_data)->string; + int ct = ((poll_info_t *)parse->parse_type->cl_data)->count; + + rtc = write(parse->generic->io.fd, ps, (unsigned long)ct); + if (rtc < 0) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: poll_dpoll: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + if (rtc != ct) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: poll_dpoll: failed to send cmd incomplete (%d of %d bytes sent)", CLK_UNIT(parse->peer), rtc, ct); + } + clear_err(parse, ERR_BADIO); +} + +/*-------------------------------------------------- + * periodic poll routine + */ +static void +poll_poll( + struct peer *peer + ) +{ + struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + + if (parse->parse_type->cl_poll) + parse->parse_type->cl_poll(parse); + + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->nextaction = current_time + ((poll_info_t *)parse->parse_type->cl_data)->rate; + } +} + +/*-------------------------------------------------- + * init routine - setup timer + */ +static int +poll_init( + struct parseunit *parse + ) +{ + if (((poll_info_t *)parse->parse_type->cl_data)->rate) + { + parse->peer->action = poll_poll; + poll_poll(parse->peer); + } + + return 0; +} + +/**=========================================================================== + ** Trimble support + **/ + +/*------------------------------------------------------------- + * trimble TAIP init routine - setup EOL and then do poll_init. + */ +static int +trimbletaip_init( + struct parseunit *parse + ) +{ +#ifdef HAVE_TERMIOS + struct termios tio; +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; +#endif + /* + * configure terminal line for trimble receiver + */ + if (TTY_GETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_init: tcgetattr(fd, &tio): %m", CLK_UNIT(parse->peer)); + return 0; + } + else + { + tio.c_cc[VEOL] = TRIMBLETAIP_EOL; + + if (TTY_SETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_init: tcsetattr(fd, &tio): %m", CLK_UNIT(parse->peer)); + return 0; + } + } + return poll_init(parse); +} + +/*-------------------------------------------------- + * trimble TAIP event routine - reset receiver upon data format trouble + */ +static const char *taipinit[] = { + ">FPV00000000<", + ">SRM;ID_FLAG=F;CS_FLAG=T;EC_FLAG=F;FR_FLAG=T;CR_FLAG=F<", + ">FTM00020001<", + (char *)0 +}; + +static void +trimbletaip_event( + struct parseunit *parse, + int event + ) +{ + switch (event) + { + case CEVNT_BADREPLY: /* reset on garbled input */ + case CEVNT_TIMEOUT: /* reset on no input */ + { + const char **iv; + + iv = taipinit; + while (*iv) + { + int rtc = write(parse->generic->io.fd, *iv, strlen(*iv)); + if (rtc < 0) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + return; + } + else + { + if (rtc != strlen(*iv)) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: failed to send cmd incomplete (%d of %d bytes sent)", + CLK_UNIT(parse->peer), rtc, (int)strlen(*iv)); + return; + } + } + iv++; + } + + NLOG(NLOG_CLOCKINFO) + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletaip_event: RECEIVER INITIALIZED", + CLK_UNIT(parse->peer)); + } + break; + + default: /* ignore */ + break; + } +} + +/* + * This driver supports the Trimble SVee Six Plus GPS receiver module. + * It should support other Trimble receivers which use the Trimble Standard + * Interface Protocol (see below). + * + * The module has a serial I/O port for command/data and a 1 pulse-per-second + * output, about 1 microsecond wide. The leading edge of the pulse is + * coincident with the change of the GPS second. This is the same as + * the change of the UTC second +/- ~1 microsecond. Some other clocks + * specifically use a feature in the data message as a timing reference, but + * the SVee Six Plus does not do this. In fact there is considerable jitter + * on the timing of the messages, so this driver only supports the use + * of the PPS pulse for accurate timing. Where it is determined that + * the offset is way off, when first starting up ntpd for example, + * the timing of the data stream is used until the offset becomes low enough + * (|offset| < CLOCK_MAX), at which point the pps offset is used. + * + * It can use either option for receiving PPS information - the 'ppsclock' + * stream pushed onto the serial data interface to timestamp the Carrier + * Detect interrupts, where the 1PPS connects to the CD line. This only + * works on SunOS 4.1.x currently. To select this, define PPSPPS in + * Config.local. The other option is to use a pulse-stretcher/level-converter + * to convert the PPS pulse into a RS232 start pulse & feed this into another + * tty port. To use this option, define PPSCLK in Config.local. The pps input, + * by whichever method, is handled in ntp_loopfilter.c + * + * The receiver uses a serial message protocol called Trimble Standard + * Interface Protocol (it can support others but this driver only supports + * TSIP). Messages in this protocol have the following form: + * + * <DLE><id> ... <data> ... <DLE><ETX> + * + * Any bytes within the <data> portion of value 10 hex (<DLE>) are doubled + * on transmission and compressed back to one on reception. Otherwise + * the values of data bytes can be anything. The serial interface is RS-422 + * asynchronous using 9600 baud, 8 data bits with odd party (**note** 9 bits + * in total!), and 1 stop bit. The protocol supports byte, integer, single, + * and double datatypes. Integers are two bytes, sent most significant first. + * Singles are IEEE754 single precision floating point numbers (4 byte) sent + * sign & exponent first. Doubles are IEEE754 double precision floating point + * numbers (8 byte) sent sign & exponent first. + * The receiver supports a large set of messages, only a small subset of + * which are used here. From driver to receiver the following are used: + * + * ID Description + * + * 21 Request current time + * 22 Mode Select + * 2C Set/Request operating parameters + * 2F Request UTC info + * 35 Set/Request I/O options + + * From receiver to driver the following are recognised: + * + * ID Description + * + * 41 GPS Time + * 44 Satellite selection, PDOP, mode + * 46 Receiver health + * 4B Machine code/status + * 4C Report operating parameters (debug only) + * 4F UTC correction data (used to get leap second warnings) + * 55 I/O options (debug only) + * + * All others are accepted but ignored. + * + */ + +#define PI 3.1415926535898 /* lots of sig figs */ +#define D2R PI/180.0 + +/*------------------------------------------------------------------- + * sendcmd, sendbyte, sendetx, sendflt, sendint implement the command + * interface to the receiver. + * + * CAVEAT: the sendflt, sendint routines are byte order dependend and + * float implementation dependend - these must be converted to portable + * versions ! + * + * CURRENT LIMITATION: float implementation. This runs only on systems + * with IEEE754 floats as native floats + */ + +typedef struct trimble +{ + u_long last_msg; /* last message received */ + u_char qtracking; /* query tracking status */ + u_long ctrack; /* current tracking set */ + u_long ltrack; /* last tracking set */ +} trimble_t; + +union uval { + u_char bd[8]; + int iv; + float fv; + double dv; +}; + +struct txbuf +{ + short idx; /* index to first unused byte */ + u_char *txt; /* pointer to actual data buffer */ +}; + +void sendcmd P((struct txbuf *buf, int c)); +void sendbyte P((struct txbuf *buf, int b)); +void sendetx P((struct txbuf *buf, struct parseunit *parse)); +void sendint P((struct txbuf *buf, int a)); +void sendflt P((struct txbuf *buf, double a)); + +void +sendcmd( + struct txbuf *buf, + int c + ) +{ + buf->txt[0] = DLE; + buf->txt[1] = (u_char)c; + buf->idx = 2; +} + +void +sendbyte( + struct txbuf *buf, + int b + ) +{ + if (b == DLE) + buf->txt[buf->idx++] = DLE; + buf->txt[buf->idx++] = (u_char)b; +} + +void +sendetx( + struct txbuf *buf, + struct parseunit *parse + ) +{ + buf->txt[buf->idx++] = DLE; + buf->txt[buf->idx++] = ETX; + + if (write(parse->generic->io.fd, buf->txt, (unsigned long)buf->idx) != buf->idx) + { + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: sendetx: failed to send cmd to clock: %m", CLK_UNIT(parse->peer)); + } + else + { +#ifdef DEBUG + if (debug > 2) + { + char buffer[256]; + + mkreadable(buffer, sizeof(buffer), (char *)buf->txt, (unsigned)buf->idx, 1); + printf("PARSE receiver #%d: transmitted message (%d bytes) >%s<\n", + CLK_UNIT(parse->peer), + buf->idx, buffer); + } +#endif + clear_err(parse, ERR_BADIO); + } +} + +void +sendint( + struct txbuf *buf, + int a + ) +{ + /* send 16bit int, msbyte first */ + sendbyte(buf, (u_char)((a>>8) & 0xff)); + sendbyte(buf, (u_char)(a & 0xff)); +} + +void +sendflt( + struct txbuf *buf, + double a + ) +{ + int i; + union uval uval; + + uval.fv = a; +#ifdef WORDS_BIGENDIAN + for (i=0; i<=3; i++) +#else + for (i=3; i>=0; i--) +#endif + sendbyte(buf, uval.bd[i]); +} + +#define TRIM_POS_OPT 0x13 /* output position with high precision */ +#define TRIM_TIME_OPT 0x03 /* use UTC time stamps, on second */ + +/*-------------------------------------------------- + * trimble TSIP setup routine + */ +static int +trimbletsip_setup( + struct parseunit *parse, + const char *reason + ) +{ + u_char buffer[256]; + struct txbuf buf; + + buf.txt = buffer; + + sendcmd(&buf, CMD_CVERSION); /* request software versions */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_COPERPARAM); /* set operating parameters */ + sendbyte(&buf, 4); /* static */ + sendflt(&buf, 5.0*D2R); /* elevation angle mask = 10 deg XXX */ + sendflt(&buf, 4.0); /* s/n ratio mask = 6 XXX */ + sendflt(&buf, 12.0); /* PDOP mask = 12 */ + sendflt(&buf, 8.0); /* PDOP switch level = 8 */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CMODESEL); /* fix mode select */ + sendbyte(&buf, 0); /* automatic */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CMESSAGE); /* request system message */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CSUPER); /* superpacket fix */ + sendbyte(&buf, 0x2); /* binary mode */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CIOOPTIONS); /* set I/O options */ + sendbyte(&buf, TRIM_POS_OPT); /* position output */ + sendbyte(&buf, 0x00); /* no velocity output */ + sendbyte(&buf, TRIM_TIME_OPT); /* UTC, compute on seconds */ + sendbyte(&buf, 0x00); /* no raw measurements */ + sendetx(&buf, parse); + + sendcmd(&buf, CMD_CUTCPARAM); /* request UTC correction data */ + sendetx(&buf, parse); + + NLOG(NLOG_CLOCKINFO) + ERR(ERR_BADIO) + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_setup: RECEIVER RE-INITIALIZED (%s)", CLK_UNIT(parse->peer), reason); + + return 0; +} + +/*-------------------------------------------------- + * TRIMBLE TSIP check routine + */ +static void +trimble_check( + struct peer *peer + ) +{ + struct parseunit *parse = (struct parseunit *)peer->procptr->unitptr; + trimble_t *t = parse->localdata; + u_char buffer[256]; + struct txbuf buf; + buf.txt = buffer; + + if (t) + { + if (current_time > t->last_msg + TRIMBLETSIP_IDLE_TIME) + (void)trimbletsip_setup(parse, "message timeout"); + } + poll_poll(parse->peer); /* emit query string and re-arm timer */ + + if (t->qtracking) + { + u_long oldsats = t->ltrack & ~t->ctrack; + + t->qtracking = 0; + t->ltrack = t->ctrack; + + if (oldsats) + { + int i; + + for (i = 0; oldsats; i++) + if (oldsats & (1 << i)) + { + sendcmd(&buf, CMD_CSTATTRACK); + sendbyte(&buf, i+1); /* old sat */ + sendetx(&buf, parse); + } + oldsats &= ~(1 << i); + } + + sendcmd(&buf, CMD_CSTATTRACK); + sendbyte(&buf, 0x00); /* current tracking set */ + sendetx(&buf, parse); + } +} + +/*-------------------------------------------------- + * TRIMBLE TSIP end routine + */ +static void +trimbletsip_end( + struct parseunit *parse + ) +{ trimble_t *t = parse->localdata; + + if (t) + { + free(t); + parse->localdata = (void *)0; + } + parse->peer->nextaction = 0; + parse->peer->action = (void (*) P((struct peer *)))0; +} + +/*-------------------------------------------------- + * TRIMBLE TSIP init routine + */ +static int +trimbletsip_init( + struct parseunit *parse + ) +{ +#if defined(VEOL) || defined(VEOL2) +#ifdef HAVE_TERMIOS + struct termios tio; /* NEEDED FOR A LONG TIME ! */ +#endif +#ifdef HAVE_SYSV_TTYS + struct termio tio; /* NEEDED FOR A LONG TIME ! */ +#endif + /* + * allocate local data area + */ + if (!parse->localdata) + { + trimble_t *t; + + t = (trimble_t *)(parse->localdata = emalloc(sizeof(trimble_t))); + + if (t) + { + memset((char *)t, 0, sizeof(trimble_t)); + t->last_msg = current_time; + } + } + + parse->peer->action = trimble_check; + parse->peer->nextaction = current_time; + + /* + * configure terminal line for ICANON mode with VEOL characters + */ + if (TTY_GETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_init: tcgetattr(%d, &tio): %m", CLK_UNIT(parse->peer), parse->generic->io.fd); + return 0; + } + else + { + if ((parse_clockinfo[CLK_TYPE(parse->peer)].cl_lflag & ICANON)) + { +#ifdef VEOL + tio.c_cc[VEOL] = ETX; +#endif +#ifdef VEOL2 + tio.c_cc[VEOL2] = DLE; +#endif + } + + if (TTY_SETATTR(parse->generic->io.fd, &tio) == -1) + { + msyslog(LOG_ERR, "PARSE receiver #%d: trimbletsip_init: tcsetattr(%d, &tio): %m", CLK_UNIT(parse->peer), parse->generic->io.fd); + return 0; + } + } +#endif + return trimbletsip_setup(parse, "initial startup"); +} + +/*------------------------------------------------------------ + * trimbletsip_event - handle Trimble events + * simple evente handler - attempt to re-initialize receiver + */ +static void +trimbletsip_event( + struct parseunit *parse, + int event + ) +{ + switch (event) + { + case CEVNT_BADREPLY: /* reset on garbled input */ + case CEVNT_TIMEOUT: /* reset on no input */ + (void)trimbletsip_setup(parse, "event BAD_REPLY/TIMEOUT"); + break; + + default: /* ignore */ + break; + } +} + +/* + * getflt, getint convert fields in the incoming data into the + * appropriate type of item + * + * CAVEAT: these routines are currently definitely byte order dependent + * and assume Representation(float) == IEEE754 + * These functions MUST be converted to portable versions (especially + * converting the float representation into ntp_fp formats in order + * to avoid floating point operations at all! + */ + +static float +getflt( + u_char *bp + ) +{ + union uval uval; + +#ifdef WORDS_BIGENDIAN + uval.bd[0] = *bp++; + uval.bd[1] = *bp++; + uval.bd[2] = *bp++; + uval.bd[3] = *bp; +#else /* ! WORDS_BIGENDIAN */ + uval.bd[3] = *bp++; + uval.bd[2] = *bp++; + uval.bd[1] = *bp++; + uval.bd[0] = *bp; +#endif /* ! WORDS_BIGENDIAN */ + return uval.fv; +} + +static double +getdbl( + u_char *bp + ) +{ + union uval uval; + +#ifdef WORDS_BIGENDIAN + uval.bd[0] = *bp++; + uval.bd[1] = *bp++; + uval.bd[2] = *bp++; + uval.bd[3] = *bp++; + uval.bd[4] = *bp++; + uval.bd[5] = *bp++; + uval.bd[6] = *bp++; + uval.bd[7] = *bp; +#else /* ! WORDS_BIGENDIAN */ + uval.bd[7] = *bp++; + uval.bd[6] = *bp++; + uval.bd[5] = *bp++; + uval.bd[4] = *bp++; + uval.bd[3] = *bp++; + uval.bd[2] = *bp++; + uval.bd[1] = *bp++; + uval.bd[0] = *bp; +#endif /* ! WORDS_BIGENDIAN */ + return uval.dv; +} + +static int +getshort( + unsigned char *p + ) +{ + return get_msb_short(&p); +} + +/*-------------------------------------------------- + * trimbletsip_message - process trimble messages + */ +#define RTOD (180.0 / 3.1415926535898) +#define mb(_X_) (buffer[2+(_X_)]) /* shortcut for buffer access */ + +static void +trimbletsip_message( + struct parseunit *parse, + parsetime_t *parsetime + ) +{ + unsigned char *buffer = parsetime->parse_msg; + unsigned int size = parsetime->parse_msglen; + + if ((size < 4) || + (buffer[0] != DLE) || + (buffer[size-1] != ETX) || + (buffer[size-2] != DLE)) + { +#ifdef DEBUG + if (debug > 2) { + int i; + + printf("TRIMBLE BAD packet, size %d:\n ", size); + for (i = 0; i < size; i++) { + printf ("%2.2x, ", buffer[i]&0xff); + if (i%16 == 15) printf("\n\t"); + } + printf("\n"); + } +#endif + return; + } + else + { + int var_flag; + trimble_t *tr = parse->localdata; + unsigned int cmd = buffer[1]; + char pbuffer[200]; + char *t = pbuffer; + cmd_info_t *s; + +#ifdef DEBUG + if (debug > 3) { + int i; + + printf("TRIMBLE packet 0x%02x, size %d:\n ", cmd, size); + for (i = 0; i < size; i++) { + printf ("%2.2x, ", buffer[i]&0xff); + if (i%16 == 15) printf("\n\t"); + } + printf("\n"); + } +#endif + + if (tr) + tr->last_msg = current_time; + + s = trimble_convert(cmd, trimble_rcmds); + + if (s) + { + sprintf(t, "%s=\"", s->varname); + } + else + { + printf("TRIMBLE unknown command 0x%02x\n", cmd); + return; + } + + var_flag = s->varmode; + + t += strlen(t); + + switch(cmd) + { + case CMD_RCURTIME: + sprintf(t, "%f, %d, %f", + getflt((unsigned char *)&mb(0)), getshort((unsigned char *)&mb(4)), + getflt((unsigned char *)&mb(6))); + break; + + case CMD_RBEST4: + strcpy(t, "mode: "); + t += strlen(t); + switch (mb(0) & 0xF) + { + default: + sprintf(t, "0x%x", mb(0) & 0x7); + break; + + case 1: + strcat(t, "0D"); + break; + + case 3: + strcat(t, "2D"); + break; + + case 4: + strcat(t, "3D"); + break; + } + t += strlen(t); + if (mb(0) & 0x10) + strcpy(t, "-MANUAL, "); + else + strcpy(t, "-AUTO, "); + t += strlen(t); + + sprintf(t, "satellites %02d %02d %02d %02d, PDOP %.2f, HDOP %.2f, VDOP %.2f, TDOP %.2f", + mb(1), mb(2), mb(3), mb(4), + getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), + getflt((unsigned char *)&mb(13)), + getflt((unsigned char *)&mb(17))); + + break; + + case CMD_RVERSION: + sprintf(t, "%d.%d (%d/%d/%d)", + mb(0)&0xff, mb(1)&0xff, 1900+(mb(4)&0xff), mb(2)&0xff, mb(3)&0xff); + break; + + case CMD_RRECVHEALTH: + { + static const char *msgs[] = + { + "Battery backup failed", + "Signal processor error", + "Alignment error, channel or chip 1", + "Alignment error, channel or chip 2", + "Antenna feed line fault", + "Excessive ref freq. error", + "<BIT 6>", + "<BIT 7>" + }; + + int i, bits; + + switch (mb(0) & 0xFF) + { + default: + sprintf(t, "illegal value 0x%02x", mb(0) & 0xFF); + break; + case 0x00: + strcpy(t, "doing position fixes"); + break; + case 0x01: + strcpy(t, "no GPS time yet"); + break; + case 0x03: + strcpy(t, "PDOP too high"); + break; + case 0x08: + strcpy(t, "no usable satellites"); + break; + case 0x09: + strcpy(t, "only ONE usable satellite"); + break; + case 0x0A: + strcpy(t, "only TWO usable satellites"); + break; + case 0x0B: + strcpy(t, "only THREE usable satellites"); + break; + case 0x0C: + strcpy(t, "the chosen satellite is unusable"); + break; + } + + t += strlen(t); + + bits = mb(1) & 0xFF; + + for (i = 0; i < 8; i++) + if (bits & (0x1<<i)) + { + sprintf(t, ", %s", msgs[i]); + t += strlen(t); + } + } + break; + + case CMD_RMESSAGE: + mkreadable(t, (int)(sizeof(pbuffer) - (t - pbuffer)), (char *)&mb(0), (unsigned)(size - 2 - (&mb(0) - buffer)), 0); + break; + + case CMD_RMACHSTAT: + { + static const char *msgs[] = + { + "Synthesizer Fault", + "Battery Powered Time Clock Fault", + "A-to-D Converter Fault", + "The almanac stored in the receiver is not complete and current", + "<BIT 4>", + "<BIT 5", + "<BIT 6>", + "<BIT 7>" + }; + + int i, bits; + + sprintf(t, "machine id 0x%02x", mb(0) & 0xFF); + t += strlen(t); + + bits = mb(1) & 0xFF; + + for (i = 0; i < 8; i++) + if (bits & (0x1<<i)) + { + sprintf(t, ", %s", msgs[i]); + t += strlen(t); + } + + sprintf(t, ", Superpackets %ssupported", (mb(2) & 0xFF) ? "" :"un" ); + } + break; + + case CMD_ROPERPARAM: + sprintf(t, "%2x %.1f %.1f %.1f %.1f", + mb(0), getflt((unsigned char *)&mb(1)), getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), getflt((unsigned char *)&mb(13))); + break; + + case CMD_RUTCPARAM: + { + float t0t = getflt((unsigned char *)&mb(14)); + short wnt = getshort((unsigned char *)&mb(18)); + short dtls = getshort((unsigned char *)&mb(12)); + short wnlsf = getshort((unsigned char *)&mb(20)); + short dn = getshort((unsigned char *)&mb(22)); + short dtlsf = getshort((unsigned char *)&mb(24)); + + if ((int)t0t != 0) + { + mk_utcinfo(t, wnt, wnlsf, dn, dtls, dtlsf); + } + else + { + strcpy(t, "<NO UTC DATA>"); + } + } + break; + + case CMD_RSAT1BIAS: + sprintf(t, "%.1fm %.2fm/s at %.1fs", + getflt(&mb(0)), getflt(&mb(4)), getflt(&mb(8))); + break; + + case CMD_RIOOPTIONS: + { + sprintf(t, "%02x %02x %02x %02x", + mb(0), mb(1), mb(2), mb(3)); + if (mb(0) != TRIM_POS_OPT || + mb(2) != TRIM_TIME_OPT) + { + (void)trimbletsip_setup(parse, "bad io options"); + } + } + break; + + case CMD_RSPOSXYZ: + { + double x = getflt((unsigned char *)&mb(0)); + double y = getflt((unsigned char *)&mb(4)); + double z = getflt((unsigned char *)&mb(8)); + double f = getflt((unsigned char *)&mb(12)); + + if (f > 0.0) + sprintf(t, "x= %.1fm, y= %.1fm, z= %.1fm, time_of_fix= %f sec", + x, y, z, + f); + else + return; + } + break; + + case CMD_RSLLAPOS: + { + double lat = getflt((unsigned char *)&mb(0)); + double lng = getflt((unsigned char *)&mb(4)); + double f = getflt((unsigned char *)&mb(12)); + + if (f > 0.0) + sprintf(t, "lat %f %c, long %f %c, alt %.2fm", + ((lat < 0.0) ? (-lat) : (lat))*RTOD, (lat < 0.0 ? 'S' : 'N'), + ((lng < 0.0) ? (-lng) : (lng))*RTOD, (lng < 0.0 ? 'W' : 'E'), + getflt((unsigned char *)&mb(8))); + else + return; + } + break; + + case CMD_RDOUBLEXYZ: + { + double x = getdbl((unsigned char *)&mb(0)); + double y = getdbl((unsigned char *)&mb(8)); + double z = getdbl((unsigned char *)&mb(16)); + sprintf(t, "x= %.1fm, y= %.1fm, z= %.1fm", + x, y, z); + } + break; + + case CMD_RDOUBLELLA: + { + double lat = getdbl((unsigned char *)&mb(0)); + double lng = getdbl((unsigned char *)&mb(8)); + sprintf(t, "lat %f %c, lon %f %c, alt %.2fm", + ((lat < 0.0) ? (-lat) : (lat))*RTOD, (lat < 0.0 ? 'S' : 'N'), + ((lng < 0.0) ? (-lng) : (lng))*RTOD, (lng < 0.0 ? 'W' : 'E'), + getdbl((unsigned char *)&mb(16))); + } + break; + + case CMD_RALLINVIEW: + { + int i, sats; + + strcpy(t, "mode: "); + t += strlen(t); + switch (mb(0) & 0x7) + { + default: + sprintf(t, "0x%x", mb(0) & 0x7); + break; + + case 3: + strcat(t, "2D"); + break; + + case 4: + strcat(t, "3D"); + break; + } + t += strlen(t); + if (mb(0) & 0x8) + strcpy(t, "-MANUAL, "); + else + strcpy(t, "-AUTO, "); + t += strlen(t); + + sats = (mb(0)>>4) & 0xF; + + sprintf(t, "PDOP %.2f, HDOP %.2f, VDOP %.2f, TDOP %.2f, %d satellite%s in view: ", + getflt((unsigned char *)&mb(1)), + getflt((unsigned char *)&mb(5)), + getflt((unsigned char *)&mb(9)), + getflt((unsigned char *)&mb(13)), + sats, (sats == 1) ? "" : "s"); + t += strlen(t); + + for (i=0; i < sats; i++) + { + sprintf(t, "%s%02d", i ? ", " : "", mb(17+i)); + t += strlen(t); + if (tr) + tr->ctrack |= (1 << (mb(17+i)-1)); + } + + if (tr) + { /* mark for tracking status query */ + tr->qtracking = 1; + } + } + break; + + case CMD_RSTATTRACK: + { + sprintf(t-2, "[%02d]=\"", mb(0)); /* add index to var name */ + t += strlen(t); + + if (getflt((unsigned char *)&mb(4)) < 0.0) + { + strcpy(t, "<NO MEASUREMENTS>"); + var_flag &= ~DEF; + } + else + { + sprintf(t, "ch=%d, acq=%s, eph=%d, signal_level= %5.2f, elevation= %5.2f, azimuth= %6.2f", + (mb(1) & 0xFF)>>3, + mb(2) ? ((mb(2) == 1) ? "ACQ" : "SRCH") : "NEVER", + mb(3), + getflt((unsigned char *)&mb(4)), + getflt((unsigned char *)&mb(12)) * RTOD, + getflt((unsigned char *)&mb(16)) * RTOD); + t += strlen(t); + if (mb(20)) + { + var_flag &= ~DEF; + strcpy(t, ", OLD"); + } + t += strlen(t); + if (mb(22)) + { + if (mb(22) == 1) + strcpy(t, ", BAD PARITY"); + else + if (mb(22) == 2) + strcpy(t, ", BAD EPH HEALTH"); + } + t += strlen(t); + if (mb(23)) + strcpy(t, ", collecting data"); + } + } + break; + + default: + strcpy(t, "<UNDECODED>"); + break; + } + strcat(t,"\""); + set_var(&parse->kv, pbuffer, sizeof(pbuffer), var_flag); + } +} + + +/**============================================================ + ** RAWDCF support + **/ + +/*-------------------------------------------------- + * rawdcf_init_1 - set up modem lines for RAWDCF receivers + * SET DTR line + */ +#if defined(TIOCMSET) && (defined(TIOCM_DTR) || defined(CIOCM_DTR)) +static int +rawdcf_init_1( + struct parseunit *parse + ) +{ + /* fixed 2000 for using with Linux by Wolfram Pienkoss <wp@bszh.de> */ + /* + * You can use the RS232 to supply the power for a DCF77 receiver. + * Here a voltage between the DTR and the RTS line is used. Unfortunately + * the name has changed from CIOCM_DTR to TIOCM_DTR recently. + */ + int sl232; + + if (ioctl(parse->generic->io.fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: ioctl(fd, TIOCMGET, [C|T]IOCM_DTR): %m", CLK_UNIT(parse->peer)); + return 0; + } + +#ifdef TIOCM_DTR + sl232 = (sl232 & ~TIOCM_RTS) | TIOCM_DTR; /* turn on DTR, clear RTS for power supply */ +#else + sl232 = (sl232 & ~CIOCM_RTS) | CIOCM_DTR; /* turn on DTR, clear RTS for power supply */ +#endif + + if (ioctl(parse->generic->io.fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: ioctl(fd, TIOCMSET, [C|T]IOCM_DTR): %m", CLK_UNIT(parse->peer)); + } + return 0; +} +#else +static int +rawdcfdtr_init_1( + struct parseunit *parse + ) +{ + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_1: WARNING: OS interface incapable of setting DTR to power DCF modules", CLK_UNIT(parse->peer)); + return 0; +} +#endif /* DTR initialisation type */ + +/*-------------------------------------------------- + * rawdcf_init_2 - set up modem lines for RAWDCF receivers + * CLR DTR line, SET RTS line + */ +#if defined(TIOCMSET) && (defined(TIOCM_RTS) || defined(CIOCM_RTS)) +static int +rawdcf_init_2( + struct parseunit *parse + ) +{ + /* fixed 2000 for using with Linux by Wolfram Pienkoss <wp@bszh.de> */ + /* + * You can use the RS232 to supply the power for a DCF77 receiver. + * Here a voltage between the DTR and the RTS line is used. Unfortunately + * the name has changed from CIOCM_DTR to TIOCM_DTR recently. + */ + int sl232; + + if (ioctl(parse->generic->io.fd, TIOCMGET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: ioctl(fd, TIOCMGET, [C|T]IOCM_RTS): %m", CLK_UNIT(parse->peer)); + return 0; + } + +#ifdef TIOCM_RTS + sl232 = (sl232 & ~TIOCM_DTR) | TIOCM_RTS; /* turn on RTS, clear DTR for power supply */ +#else + sl232 = (sl232 & ~CIOCM_DTR) | CIOCM_RTS; /* turn on RTS, clear DTR for power supply */ +#endif + + if (ioctl(parse->generic->io.fd, TIOCMSET, (caddr_t)&sl232) == -1) + { + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: ioctl(fd, TIOCMSET, [C|T]IOCM_RTS): %m", CLK_UNIT(parse->peer)); + } + return 0; +} +#else +static int +rawdcf_init_2( + struct parseunit *parse + ) +{ + msyslog(LOG_NOTICE, "PARSE receiver #%d: rawdcf_init_2: WARNING: OS interface incapable of setting RTS to power DCF modules", CLK_UNIT(parse->peer)); + return 0; +} +#endif /* DTR initialisation type */ + +#else /* defined(REFCLOCK) && defined(PARSE) */ +int refclock_parse_bs; +#endif /* defined(REFCLOCK) && defined(PARSE) */ + +/* + * History: + * + * refclock_parse.c,v + * Revision 4.36 1999/11/28 17:18:20 kardel + * disabled burst mode + * + * Revision 4.35 1999/11/28 09:14:14 kardel + * RECON_4_0_98F + * + * Revision 4.34 1999/05/14 06:08:05 kardel + * store current_time in a suitable container (u_long) + * + * Revision 4.33 1999/05/13 21:48:38 kardel + * double the no response timeout interval + * + * Revision 4.32 1999/05/13 20:09:13 kardel + * complain only about missing polls after a full poll interval + * + * Revision 4.31 1999/05/13 19:59:32 kardel + * add clock type 16 for RTS set DTR clr in RAWDCF + * + * Revision 4.30 1999/02/28 20:36:43 kardel + * fixed printf fmt + * + * Revision 4.29 1999/02/28 19:58:23 kardel + * updated copyright information + * + * Revision 4.28 1999/02/28 19:01:50 kardel + * improved debug out on sent Meinberg messages + * + * Revision 4.27 1999/02/28 18:05:55 kardel + * no linux/ppsclock.h stuff + * + * Revision 4.26 1999/02/28 15:27:27 kardel + * wharton clock integration + * + * Revision 4.25 1999/02/28 14:04:46 kardel + * added missing double quotes to UTC information string + * + * Revision 4.24 1999/02/28 12:06:50 kardel + * (parse_control): using gmprettydate instead of prettydate() + * (mk_utcinfo): new function for formatting GPS derived UTC information + * (gps16x_message): changed to use mk_utcinfo() + * (trimbletsip_message): changed to use mk_utcinfo() + * ignoring position information in unsynchronized mode + * (parse_start): augument linux support for optional ASYNC_LOW_LATENCY + * + * Revision 4.23 1999/02/23 19:47:53 kardel + * fixed #endifs + * (stream_receive): fixed formats + * + * Revision 4.22 1999/02/22 06:21:02 kardel + * use new autoconfig symbols + * + * Revision 4.21 1999/02/21 12:18:13 kardel + * 4.91f reconcilation + * + * Revision 4.20 1999/02/21 10:53:36 kardel + * initial Linux PPSkit version + * + * Revision 4.19 1999/02/07 09:10:45 kardel + * clarify STREAMS mitigation rules in comment + * + * Revision 4.18 1998/12/20 23:45:34 kardel + * fix types and warnings + * + * Revision 4.17 1998/11/15 21:24:51 kardel + * cannot access mbg_ routines when CLOCK_MEINBERG + * is not defined + * + * Revision 4.16 1998/11/15 20:28:17 kardel + * Release 4.0.73e13 reconcilation + * + * Revision 4.15 1998/08/22 21:56:08 kardel + * fixed IO handling for non-STREAM IO + * + * Revision 4.14 1998/08/16 19:00:48 kardel + * (gps16x_message): reduced UTC parameter information (dropped A0,A1) + * made uval a local variable (killed one of the last globals) + * (sendetx): added logging of messages when in debug mode + * (trimble_check): added periodic checks to facilitate re-initialization + * (trimbletsip_init): made use of EOL character if in non-kernel operation + * (trimbletsip_message): extended message interpretation + * (getdbl): fixed data conversion + * + * Revision 4.13 1998/08/09 22:29:13 kardel + * Trimble TSIP support + * + * Revision 4.12 1998/07/11 10:05:34 kardel + * Release 4.0.73d reconcilation + * + * Revision 4.11 1998/06/14 21:09:42 kardel + * Sun acc cleanup + * + * Revision 4.10 1998/06/13 12:36:45 kardel + * signed/unsigned, name clashes + * + * Revision 4.9 1998/06/12 15:30:00 kardel + * prototype fixes + * + * Revision 4.8 1998/06/12 11:19:42 kardel + * added direct input processing routine for refclocks in + * order to avaiod that single character io gobbles up all + * receive buffers and drops input data. (Problem started + * with fast machines so a character a buffer was possible + * one of the few cases where faster machines break existing + * allocation algorithms) + * + * Revision 4.7 1998/06/06 18:35:20 kardel + * (parse_start): added BURST mode initialisation + * + * Revision 4.6 1998/05/27 06:12:46 kardel + * RAWDCF_BASEDELAY default added + * old comment removed + * casts for ioctl() + * + * Revision 4.5 1998/05/25 22:05:09 kardel + * RAWDCF_SETDTR option removed + * clock type 14 attempts to set DTR for + * power supply of RAWDCF receivers + * + * Revision 4.4 1998/05/24 16:20:47 kardel + * updated comments referencing Meinberg clocks + * added RAWDCF clock with DTR set option as type 14 + * + * Revision 4.3 1998/05/24 10:48:33 kardel + * calibrated CONRAD RAWDCF default fudge factor + * + * Revision 4.2 1998/05/24 09:59:35 kardel + * corrected version information (ntpq support) + * + * Revision 4.1 1998/05/24 09:52:31 kardel + * use fixed format only (new IO model) + * output debug to stdout instead of msyslog() + * don't include >"< in ASCII output in order not to confuse + * ntpq parsing + * + * Revision 4.0 1998/04/10 19:52:11 kardel + * Start 4.0 release version numbering + * + * Revision 1.2 1998/04/10 19:28:04 kardel + * initial NTP VERSION 4 integration of PARSE with GPS166 binary support + * derived from 3.105.1.2 from V3 tree + * + * Revision information 3.1 - 3.105 from log deleted 1998/04/10 kardel + * + */ diff --git a/ntpd/refclock_pcf.c b/ntpd/refclock_pcf.c new file mode 100644 index 0000000..d4e9fd1 --- /dev/null +++ b/ntpd/refclock_pcf.c @@ -0,0 +1,224 @@ +/* + * refclock_pcf - clock driver for the Conrad parallel port radio clock + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_PCF) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +/* + * This driver supports the parallel port radio clock sold by Conrad + * Electronic under order numbers 967602 and 642002. + * + * It requires that the local timezone be CET/CEST and that the pcfclock + * device driver be installed. A device driver for Linux is available at + * http://home.pages.de/~voegele/pcf.html. Information about a FreeBSD + * driver is available at http://schumann.cx/pcfclock/. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/pcfclocks/%d" +#define OLDDEVICE "/dev/pcfclock%d" +#define PRECISION (-1) /* precision assumed (about 0.5 s) */ +#define REFID "PCF" +#define DESCRIPTION "Conrad parallel port radio clock" + +#define LENPCF 18 /* timecode length */ + +/* + * Function prototypes + */ +static int pcf_start P((int, struct peer *)); +static void pcf_shutdown P((int, struct peer *)); +static void pcf_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_pcf = { + pcf_start, /* start up driver */ + pcf_shutdown, /* shut down driver */ + pcf_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; + + +/* + * pcf_start - open the device and initialize data for processing + */ +static int +pcf_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + int fd; + char device[128]; + + /* + * Open device file for reading. + */ + (void)sprintf(device, DEVICE, unit); + fd = open(device, O_RDONLY); + if (fd == -1) { + (void)sprintf(device, OLDDEVICE, unit); + fd = open(device, O_RDONLY); + } +#ifdef DEBUG + if (debug) + printf ("starting PCF with device %s\n",device); +#endif + if (fd == -1) { + return (0); + } + + pp = peer->procptr; + pp->io.clock_recv = noentry; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + /* one transmission takes 172.5 milliseconds since the radio clock + transmits 69 bits with a period of 2.5 milliseconds per bit */ + pp->fudgetime1 = 0.1725; + memcpy((char *)&pp->refid, REFID, 4); + + return (1); +} + + +/* + * pcf_shutdown - shut down the clock + */ +static void +pcf_shutdown( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + (void)close(pp->io.fd); +} + + +/* + * pcf_poll - called by the transmit procedure + */ +static void +pcf_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + char buf[LENPCF]; + struct tm tm, *tp; + time_t t; + + pp = peer->procptr; + + buf[0] = 0; + if (read(pp->io.fd, buf, sizeof(buf)) < sizeof(buf) || buf[0] != 9) { + refclock_report(peer, CEVNT_FAULT); + return; + } + + tm.tm_mday = buf[11] * 10 + buf[10]; + tm.tm_mon = buf[13] * 10 + buf[12] - 1; + tm.tm_year = buf[15] * 10 + buf[14]; + tm.tm_hour = buf[7] * 10 + buf[6]; + tm.tm_min = buf[5] * 10 + buf[4]; + tm.tm_sec = buf[3] * 10 + buf[2]; + tm.tm_isdst = (buf[8] & 1) ? 1 : (buf[8] & 2) ? 0 : -1; + + /* + * Y2K convert the 2-digit year + */ + if (tm.tm_year < 99) + tm.tm_year += 100; + + t = mktime(&tm); + if (t == (time_t) -1) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + +#if defined(__GLIBC__) && defined(_BSD_SOURCE) + if ((tm.tm_isdst > 0 && tm.tm_gmtoff != 7200) + || (tm.tm_isdst == 0 && tm.tm_gmtoff != 3600) + || tm.tm_isdst < 0) { +#ifdef DEBUG + if (debug) + printf ("local time zone not set to CET/CEST\n"); +#endif + refclock_report(peer, CEVNT_BADTIME); + return; + } +#endif + + pp->lencode = strftime(pp->a_lastcode, BMAX, "%Y %m %d %H %M %S", &tm); + +#if defined(_REENTRANT) || defined(_THREAD_SAFE) + tp = gmtime_r(&t, &tm); +#else + tp = gmtime(&t); +#endif + if (!tp) { + refclock_report(peer, CEVNT_FAULT); + return; + } + + get_systime(&pp->lastrec); + pp->polls++; + pp->year = tp->tm_year + 1900; + pp->day = tp->tm_yday + 1; + pp->hour = tp->tm_hour; + pp->minute = tp->tm_min; + pp->second = tp->tm_sec; + pp->nsec = buf[16] * 31250000; + if (buf[17] & 1) + pp->nsec += 500000000; + +#ifdef DEBUG + if (debug) + printf ("pcf%d: time is %04d/%02d/%02d %02d:%02d:%02d UTC\n", + unit, pp->year, tp->tm_mon + 1, tp->tm_mday, pp->hour, + pp->minute, pp->second); +#endif + + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + record_clock_stats(&peer->srcadr, pp->a_lastcode); + if ((buf[1] & 1) && !(pp->sloppyclockflag & CLK_FLAG2)) + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + pp->lastref = pp->lastrec; + refclock_receive(peer); +} +#else +int refclock_pcf_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_pst.c b/ntpd/refclock_pst.c new file mode 100644 index 0000000..2443b2c --- /dev/null +++ b/ntpd/refclock_pst.c @@ -0,0 +1,321 @@ +/* + * refclock_pst - clock driver for PSTI/Traconex WWV/WWVH receivers + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_PST) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the PSTI 1010 and Traconex 1020 WWV/WWVH + * Receivers. No specific claim of accuracy is made for these receiver, + * but actual experience suggests that 10 ms would be a conservative + * assumption. + * + * The DIPswitches should be set for 9600 bps line speed, 24-hour day- + * of-year format and UTC time zone. Automatic correction for DST should + * be disabled. It is very important that the year be set correctly in + * the DIPswitches; otherwise, the day of year will be incorrect after + * 28 April of a normal or leap year. The propagation delay DIPswitches + * should be set according to the distance from the transmitter for both + * WWV and WWVH, as described in the instructions. While the delay can + * be set only to within 11 ms, the fudge time1 parameter can be used + * for vernier corrections. + * + * Using the poll sequence QTQDQM, the response timecode is in three + * sections totalling 50 ASCII printing characters, as concatenated by + * the driver, in the following format: + * + * ahh:mm:ss.fffs<cr> yy/dd/mm/ddd<cr> frdzycchhSSFTttttuuxx<cr> + * + * on-time = first <cr> + * hh:mm:ss.fff = hours, minutes, seconds, milliseconds + * a = AM/PM indicator (' ' for 24-hour mode) + * yy = year (from internal switches) + * dd/mm/ddd = day of month, month, day of year + * s = daylight-saving indicator (' ' for 24-hour mode) + * f = frequency enable (O = all frequencies enabled) + * r = baud rate (3 = 1200, 6 = 9600) + * d = features indicator (@ = month/day display enabled) + * z = time zone (0 = UTC) + * y = year (5 = 91) + * cc = WWV propagation delay (52 = 22 ms) + * hh = WWVH propagation delay (81 = 33 ms) + * SS = status (80 or 82 = operating correctly) + * F = current receive frequency (4 = 15 MHz) + * T = transmitter (C = WWV, H = WWVH) + * tttt = time since last update (0000 = minutes) + * uu = flush character (03 = ^c) + * xx = 94 (unknown) + * + * The alarm condition is indicated by other than '8' at A, which occurs + * during initial synchronization and when received signal is lost for + * an extended period; unlock condition is indicated by other than + * "0000" in the tttt subfield at Q. + * + * Fudge Factors + * + * There are no special fudge factors other than the generic. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/wwv%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define WWVREFID "WWV\0" /* WWV reference ID */ +#define WWVHREFID "WWVH" /* WWVH reference ID */ +#define DESCRIPTION "PSTI/Traconex WWV/WWVH Receiver" /* WRU */ +#define PST_PHI (10e-6) /* max clock oscillator offset */ +#define LENPST 46 /* min timecode length */ + +/* + * Unit control structure + */ +struct pstunit { + int tcswitch; /* timecode switch */ + char *lastptr; /* pointer to timecode data */ +}; + +/* + * Function prototypes + */ +static int pst_start P((int, struct peer *)); +static void pst_shutdown P((int, struct peer *)); +static void pst_receive P((struct recvbuf *)); +static void pst_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_pst = { + pst_start, /* start up driver */ + pst_shutdown, /* shut down driver */ + pst_poll, /* transmit poll message */ + noentry, /* not used (old pst_control) */ + noentry, /* initialize driver */ + noentry, /* not used (old pst_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * pst_start - open the devices and initialize data for processing + */ +static int +pst_start( + int unit, + struct peer *peer + ) +{ + register struct pstunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct pstunit *)emalloc(sizeof(struct pstunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct pstunit)); + pp = peer->procptr; + pp->io.clock_recv = pst_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, WWVREFID, 4); + peer->burst = MAXSTAGE; + return (1); +} + + +/* + * pst_shutdown - shut down the clock + */ +static void +pst_shutdown( + int unit, + struct peer *peer + ) +{ + register struct pstunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct pstunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * pst_receive - receive data from the serial interface + */ +static void +pst_receive( + struct recvbuf *rbufp + ) +{ + register struct pstunit *up; + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + u_long ltemp; + char ampmchar; /* AM/PM indicator */ + char daychar; /* standard/daylight indicator */ + char junque[10]; /* "yy/dd/mm/" discard */ + char info[14]; /* "frdzycchhSSFT" clock info */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct pstunit *)pp->unitptr; + up->lastptr += refclock_gtlin(rbufp, up->lastptr, pp->a_lastcode + + BMAX - 2 - up->lastptr, &trtmp); + *up->lastptr++ = ' '; + *up->lastptr = '\0'; + + /* + * Note we get a buffer and timestamp for each <cr>, but only + * the first timestamp is retained. + */ + if (up->tcswitch == 0) + pp->lastrec = trtmp; + up->tcswitch++; + pp->lencode = up->lastptr - pp->a_lastcode; + if (up->tcswitch < 3) + return; + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + if (pp->lencode < LENPST) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Timecode format: + * "ahh:mm:ss.fffs yy/dd/mm/ddd frdzycchhSSFTttttuuxx" + */ + if (sscanf(pp->a_lastcode, + "%c%2d:%2d:%2d.%3ld%c %9s%3d%13s%4ld", + &mchar, &pp->hour, &pp->minute, &pp->second, &pp->nsec, + &daychar, junque, &pp->day, info, <emp) != 10) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + pp->nsec *= 1000000; + + /* + * Decode synchronization, quality and last update. If + * unsynchronized, set the leap bits accordingly and exit. Once + * synchronized, the dispersion depends only on when the clock + * was last heard, which depends on the time since last update, + * as reported by the clock. + */ + if (info[9] != '8') + pp->leap = LEAP_NOTINSYNC; + if (info[12] == 'H') + memcpy((char *)&pp->refid, WWVHREFID, 4); + else + memcpy((char *)&pp->refid, WWVREFID, 4); + if (peer->stratum <= 1) + peer->refid = pp->refid; + if (ltemp == 0) + pp->lastref = pp->lastrec; + pp->disp = PST_PHI * ltemp * 60; + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) + refclock_report(peer, CEVNT_BADTIME); + +} + + +/* + * pst_poll - called by the transmit procedure + */ +static void +pst_poll( + int unit, + struct peer *peer + ) +{ + register struct pstunit *up; + struct refclockproc *pp; + + /* + * Time to poll the clock. The PSTI/Traconex clock responds to a + * "QTQDQMT" by returning a timecode in the format specified + * above. Note there is no checking on state, since this may not + * be the only customer reading the clock. Only one customer + * need poll the clock; all others just listen in. If the clock + * becomes unreachable, declare a timeout and keep going. + */ + pp = peer->procptr; + up = (struct pstunit *)pp->unitptr; + up->tcswitch = 0; + up->lastptr = pp->a_lastcode; + if (write(pp->io.fd, "QTQDQMT", 6) != 6) + refclock_report(peer, CEVNT_FAULT); + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("pst: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + peer->burst = MAXSTAGE; + pp->polls++; +} + +#else +int refclock_pst_int; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_ptbacts.c b/ntpd/refclock_ptbacts.c new file mode 100644 index 0000000..09d7bf4 --- /dev/null +++ b/ntpd/refclock_ptbacts.c @@ -0,0 +1,16 @@ +/* + * crude hack to avoid hard links in distribution + * and keep only one ACTS type source for different + * ACTS refclocks + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_PTBACTS) +# define KEEPPTBACTS +# include "refclock_acts.c" +#else /* not (REFCLOCK && CLOCK_PTBACTS) */ +int refclock_ptbacts_bs; +#endif /* not (REFCLOCK && CLOCK_PTBACTS) */ diff --git a/ntpd/refclock_ripencc.c b/ntpd/refclock_ripencc.c new file mode 100644 index 0000000..6337f63 --- /dev/null +++ b/ntpd/refclock_ripencc.c @@ -0,0 +1,4870 @@ +/* + * $Id: refclock_ripencc.c,v 1.13 2002/06/18 14:20:55 marks Exp marks $ + * + * Copyright (c) 2002 RIPE NCC + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of the author not be + * used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL + * AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * + * + * This driver was developed for use with the RIPE NCC TTM project. + * + * + * The initial driver was developed by Daniel Karrenberg <dfk@ripe.net> + * using the code made available by Trimble. This was for xntpd-3.x.x + * + * Rewrite of the driver for ntpd-4.x.x by Mark Santcroos <marks@ripe.net> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#if defined(REFCLOCK) && defined(CLOCK_RIPENCC) + +#include "ntp_stdlib.h" +#include "ntpd.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_io.h" + +#ifdef HAVE_TIMEPPS_H +# include <timepps.h> +#else /* HAVE_TIMEPPS_H */ +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif /* HAVE_SYS_TIMEPPS_H */ +#endif /* HAVE_TIMEPPS_H */ + +/* + * Definitions + */ + +/* we are on little endian */ +#define BYTESWAP + +/* + * DEBUG statements: uncomment if necessary + */ +/* #define DEBUG_NCC */ /* general debug statements */ +/* #define DEBUG_PPS */ /* debug pps */ +/* #define DEBUG_RAW */ /* print raw packets */ + +#define TRIMBLE_OUTPUT_FUNC +#define TSIP_VERNUM "7.12a" + +#ifndef FALSE +#define FALSE (0) +#define TRUE (!FALSE) +#endif /* FALSE */ + +#define GPS_PI (3.1415926535898) +#define GPS_C (299792458.) +#define D2R (GPS_PI/180.0) +#define R2D (180.0/GPS_PI) +#define WEEK (604800.) +#define MAXCHAN (8) + +/* control characters for TSIP packets */ +#define DLE (0x10) +#define ETX (0x03) + +#define MAX_RPTBUF (256) + +/* values of TSIPPKT.status */ +#define TSIP_PARSED_EMPTY 0 +#define TSIP_PARSED_FULL 1 +#define TSIP_PARSED_DLE_1 2 +#define TSIP_PARSED_DATA 3 +#define TSIP_PARSED_DLE_2 4 + +#define UTCF_UTC_AVAIL (unsigned char) (1) /* UTC available */ +#define UTCF_LEAP_SCHD (unsigned char) (1<<4) /* Leap scheduled */ +#define UTCF_LEAP_PNDG (unsigned char) (1<<5) /* Leap pending, will occur at end of day */ + +#define DEVICE "/dev/gps%d" /* name of radio device */ +#define PRECISION (-9) /* precision assumed (about 2 ms) */ +#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference id */ +#define REFID_LEN 4 +#define DESCRIPTION "RIPE NCC GPS (Palisade)" /* Description */ +#define SPEED232 B9600 /* 9600 baud */ + +#define NSAMPLES 3 /* stages of median filter */ + +/* Structures */ + +/* TSIP packets have the following structure, whether report or command. */ +typedef struct { + short + counter, /* counter */ + len; /* size of buf; < MAX_RPTBUF unsigned chars */ + unsigned char + status, /* TSIP packet format/parse status */ + code, /* TSIP code */ + buf[MAX_RPTBUF];/* report or command string */ +} TSIPPKT; + +/* TSIP binary data structures */ +typedef struct { + unsigned char + t_oa_raw, SV_health; + float + e, t_oa, i_0, OMEGADOT, sqrt_A, + OMEGA_0, omega, M_0, a_f0, a_f1, + Axis, n, OMEGA_n, ODOT_n, t_zc; + short + weeknum, wn_oa; +} ALM_INFO; + +typedef struct { /* Almanac health page (25) parameters */ + unsigned char + WN_a, SV_health[32], t_oa; +} ALH_PARMS; + +typedef struct { /* Universal Coordinated Time (UTC) parms */ + double + A_0; + float + A_1; + short + delta_t_LS; + float + t_ot; + short + WN_t, WN_LSF, DN, delta_t_LSF; +} UTC_INFO; + +typedef struct { /* Ionospheric info (float) */ + float + alpha_0, alpha_1, alpha_2, alpha_3, + beta_0, beta_1, beta_2, beta_3; +} ION_INFO; + +typedef struct { /* Subframe 1 info (float) */ + short + weeknum; + unsigned char + codeL2, L2Pdata, SVacc_raw, SV_health; + short + IODC; + float + T_GD, t_oc, a_f2, a_f1, a_f0, SVacc; +} EPHEM_CLOCK; + +typedef struct { /* Ephemeris info (float) */ + unsigned char + IODE, fit_interval; + float + C_rs, delta_n; + double + M_0; + float + C_uc; + double + e; + float + C_us; + double + sqrt_A; + float + t_oe, C_ic; + double + OMEGA_0; + float + C_is; + double + i_0; + float + C_rc; + double + omega; + float + OMEGADOT, IDOT; + double + Axis, n, r1me2, OMEGA_n, ODOT_n; +} EPHEM_ORBIT; + +typedef struct { /* Navigation data structure */ + short + sv_number; /* SV number (0 = no entry) */ + float + t_ephem; /* time of ephemeris collection */ + EPHEM_CLOCK + ephclk; /* subframe 1 data */ + EPHEM_ORBIT + ephorb; /* ephemeris data */ +} NAV_INFO; + +typedef struct { + unsigned char + bSubcode, + operating_mode, + dgps_mode, + dyn_code, + trackmode; + float + elev_mask, + cno_mask, + dop_mask, + dop_switch; + unsigned char + dgps_age_limit; +} TSIP_RCVR_CFG; + + +#ifdef TRIMBLE_OUTPUT_FUNC +static char + *dayname[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}, + old_baudnum[] = {0, 1, 4, 5, 6, 8, 9, 11, 28, 12}, + *st_baud_text_app [] = {"", "", " 300", " 600", " 1200", " 2400", + " 4800", " 9600", "19200", "38400"}, + *old_parity_text[] = {"EVEN", "ODD", "", "", "NONE"}, + *parity_text [] = {"NONE", "ODD", "EVEN"}, + *old_input_ch[] = { "TSIP", "RTCM (6 of 8 bits)"}, + *old_output_ch[] = { "TSIP", "No output", "", "", "", "NMEA 0183"}, + *protocols_in_text[] = { "", "TSIP", "", ""}, + *protocols_out_text[] = { "", "TSIP", "NMEA"}, + *rcvr_port_text [] = { "Port A ", "Port B ", "Current Port"}, + *dyn_text [] = {"Unchanged", "Land", "Sea", "Air", "Static"}, + *NavModeText0xBB[] = {"automatic", "time only (0-D)", "", "2-D", + "3-D", "", "", "OverDetermined Time"}, + *PPSTimeBaseText[] = {"GPS", "UTC", "USER"}, + *PPSPolarityText[] = {"Positive", "Negative"}, + *MaskText[] = { "Almanac ", "Ephemeris", "UTC ", "Iono ", + "GPS Msg ", "Alm Hlth ", "Time Fix ", "SV Select", + "Ext Event", "Pos Fix ", "Raw Meas "}; + +#endif /* TRIMBLE_OUTPUT_FUNC */ + +/* + * Unit control structure + */ +struct ripencc_unit { + int unit; /* unit number */ + int pollcnt; /* poll message counter */ + int polled; /* Hand in a sample? */ + char leapdelta; /* delta of next leap event */ + unsigned char utcflags; /* delta of next leap event */ + l_fp tstamp; /* timestamp of last poll */ + + struct timespec ts; /* last timestamp */ + pps_params_t pps_params; /* pps parameters */ + pps_info_t pps_info; /* last pps data */ + pps_handle_t handle; /* pps handlebars */ + +}; + + +/******************* PROTOYPES *****************/ + +/* prototypes for report parsing primitives */ +short rpt_0x3D (TSIPPKT *rpt, unsigned char *tx_baud_index, + unsigned char *rx_baud_index, unsigned char *char_format_index, + unsigned char *stop_bits, unsigned char *tx_mode_index, + unsigned char *rx_mode_index); +short rpt_0x40 (TSIPPKT *rpt, unsigned char *sv_prn, short *week_num, + float *t_zc, float *eccentricity, float *t_oa, float *i_0, + float *OMEGA_dot, float *sqrt_A, float *OMEGA_0, float *omega, + float *M_0); +short rpt_0x41 (TSIPPKT *rpt, float *time_of_week, float *UTC_offset, + short *week_num); +short rpt_0x42 (TSIPPKT *rpt, float ECEF_pos[3], float *time_of_fix); +short rpt_0x43 (TSIPPKT *rpt, float ECEF_vel[3], float *freq_offset, + float *time_of_fix); +short rpt_0x45 (TSIPPKT *rpt, unsigned char *major_nav_version, + unsigned char *minor_nav_version, unsigned char *nav_day, + unsigned char *nav_month, unsigned char *nav_year, + unsigned char *major_dsp_version, unsigned char *minor_dsp_version, + unsigned char *dsp_day, unsigned char *dsp_month, + unsigned char *dsp_year); +short rpt_0x46 (TSIPPKT *rpt, unsigned char *status1, unsigned char *status2); +short rpt_0x47 (TSIPPKT *rpt, unsigned char *nsvs, unsigned char *sv_prn, + float *snr); +short rpt_0x48 (TSIPPKT *rpt, unsigned char *message); +short rpt_0x49 (TSIPPKT *rpt, unsigned char *sv_health); +short rpt_0x4A (TSIPPKT *rpt, float *lat, float *lon, float *alt, + float *clock_bias, float *time_of_fix); +short rpt_0x4A_2 (TSIPPKT *rpt, float *alt, float *dummy, + unsigned char *alt_flag); +short rpt_0x4B (TSIPPKT *rpt, unsigned char *machine_id, + unsigned char *status3, unsigned char *status4); +short rpt_0x4C (TSIPPKT *rpt, unsigned char *dyn_code, float *el_mask, + float *snr_mask, float *dop_mask, float *dop_switch); +short rpt_0x4D (TSIPPKT *rpt, float *osc_offset); +short rpt_0x4E (TSIPPKT *rpt, unsigned char *response); +short rpt_0x4F (TSIPPKT *rpt, double *a0, float *a1, float *time_of_data, + short *dt_ls, short *wn_t, short *wn_lsf, short *dn, short *dt_lsf); +short rpt_0x54 (TSIPPKT *rpt, float *clock_bias, float *freq_offset, + float *time_of_fix); +short rpt_0x55 (TSIPPKT *rpt, unsigned char *pos_code, unsigned char *vel_code, + unsigned char *time_code, unsigned char *aux_code); +short rpt_0x56 (TSIPPKT *rpt, float vel_ENU[3], float *freq_offset, + float *time_of_fix); +short rpt_0x57 (TSIPPKT *rpt, unsigned char *source_code, + unsigned char *diag_code, short *week_num, float *time_of_fix); +short rpt_0x58 (TSIPPKT *rpt, unsigned char *op_code, unsigned char *data_type, + unsigned char *sv_prn, unsigned char *data_length, + unsigned char *data_packet); +short rpt_0x59 (TSIPPKT *rpt, unsigned char *code_type, + unsigned char status_code[32]); +short rpt_0x5A (TSIPPKT *rpt, unsigned char *sv_prn, float *sample_length, + float *signal_level, float *code_phase, float *Doppler, + double *time_of_fix); +short rpt_0x5B (TSIPPKT *rpt, unsigned char *sv_prn, unsigned char *sv_health, + unsigned char *sv_iode, unsigned char *fit_interval_flag, + float *time_of_collection, float *time_of_eph, float *sv_accy); +short rpt_0x5C (TSIPPKT *rpt, unsigned char *sv_prn, unsigned char *slot, + unsigned char *chan, unsigned char *acq_flag, unsigned char *eph_flag, + float *signal_level, float *time_of_last_msmt, float *elev, + float *azim, unsigned char *old_msmt_flag, + unsigned char *integer_msec_flag, unsigned char *bad_data_flag, + unsigned char *data_collect_flag); +short rpt_0x6D (TSIPPKT *rpt, unsigned char *manual_mode, unsigned char *nsvs, + unsigned char *ndim, unsigned char sv_prn[], float *pdop, + float *hdop, float *vdop, float *tdop); +short rpt_0x82 (TSIPPKT *rpt, unsigned char *diff_mode); +short rpt_0x83 (TSIPPKT *rpt, double ECEF_pos[3], double *clock_bias, + float *time_of_fix); +short rpt_0x84 (TSIPPKT *rpt, double *lat, double *lon, double *alt, + double *clock_bias, float *time_of_fix); +short rpt_Paly0xBB(TSIPPKT *rpt, TSIP_RCVR_CFG *TsipxBB); +short rpt_0xBC (TSIPPKT *rpt, unsigned char *port_num, + unsigned char *in_baud, unsigned char *out_baud, + unsigned char *data_bits, unsigned char *parity, + unsigned char *stop_bits, unsigned char *flow_control, + unsigned char *protocols_in, unsigned char *protocols_out, + unsigned char *reserved); + +/* prototypes for superpacket parsers */ + +short rpt_0x8F0B (TSIPPKT *rpt, unsigned short *event, double *tow, + unsigned char *date, unsigned char *month, short *year, + unsigned char *dim_mode, short *utc_offset, double *bias, double *drift, + float *bias_unc, float *dr_unc, double *lat, double *lon, double *alt, + char sv_id[8]); +short rpt_0x8F14 (TSIPPKT *rpt, short *datum_idx, double datum_coeffs[5]); +short rpt_0x8F15 (TSIPPKT *rpt, short *datum_idx, double datum_coeffs[5]); +short rpt_0x8F20 (TSIPPKT *rpt, unsigned char *info, double *lat, + double *lon, double *alt, double vel_enu[], double *time_of_fix, + short *week_num, unsigned char *nsvs, unsigned char sv_prn[], + short sv_IODC[], short *datum_index); +short rpt_0x8F41 (TSIPPKT *rpt, unsigned char *bSearchRange, + unsigned char *bBoardOptions, unsigned long *iiSerialNumber, + unsigned char *bBuildYear, unsigned char *bBuildMonth, + unsigned char *bBuildDay, unsigned char *bBuildHour, + float *fOscOffset, unsigned short *iTestCodeId); +short rpt_0x8F42 (TSIPPKT *rpt, unsigned char *bProdOptionsPre, + unsigned char *bProdNumberExt, unsigned short *iCaseSerialNumberPre, + unsigned long *iiCaseSerialNumber, unsigned long *iiProdNumber, + unsigned short *iPremiumOptions, unsigned short *iMachineID, + unsigned short *iKey); +short rpt_0x8F45 (TSIPPKT *rpt, unsigned char *bSegMask); +short rpt_0x8F4A_16 (TSIPPKT *rpt, unsigned char *pps_enabled, + unsigned char *pps_timebase, unsigned char *pos_polarity, + double *pps_offset, float *bias_unc_threshold); +short rpt_0x8F4B (TSIPPKT *rpt, unsigned long *decorr_max); +short rpt_0x8F4D (TSIPPKT *rpt, unsigned long *event_mask); +short rpt_0x8FA5 (TSIPPKT *rpt, unsigned char *spktmask); +short rpt_0x8FAD (TSIPPKT *rpt, unsigned short *COUNT, double *FracSec, + unsigned char *Hour, unsigned char *Minute, unsigned char *Second, + unsigned char *Day, unsigned char *Month, unsigned short *Year, + unsigned char *Status, unsigned char *Flags); + +/**/ +/* prototypes for command-encode primitives with suffix convention: */ +/* c = clear, s = set, q = query, e = enable, d = disable */ +void cmd_0x1F (TSIPPKT *cmd); +void cmd_0x26 (TSIPPKT *cmd); +void cmd_0x2F (TSIPPKT *cmd); +void cmd_0x35s (TSIPPKT *cmd, unsigned char pos_code, unsigned char vel_code, + unsigned char time_code, unsigned char opts_code); +void cmd_0x3C (TSIPPKT *cmd, unsigned char sv_prn); +void cmd_0x3Ds (TSIPPKT *cmd, unsigned char baud_out, unsigned char baud_inp, + unsigned char char_code, unsigned char stopbitcode, + unsigned char output_mode, unsigned char input_mode); +void cmd_0xBBq (TSIPPKT *cmd, unsigned char subcode) ; + +/* prototypes 8E commands */ +void cmd_0x8E0Bq (TSIPPKT *cmd); +void cmd_0x8E41q (TSIPPKT *cmd); +void cmd_0x8E42q (TSIPPKT *cmd); +void cmd_0x8E4Aq (TSIPPKT *cmd); +void cmd_0x8E4As (TSIPPKT *cmd, unsigned char PPSOnOff, unsigned char TimeBase, + unsigned char Polarity, double PPSOffset, float Uncertainty); +void cmd_0x8E4Bq (TSIPPKT *cmd); +void cmd_0x8E4Ds (TSIPPKT *cmd, unsigned long AutoOutputMask); +void cmd_0x8EADq (TSIPPKT *cmd); + +/* header/source border XXXXXXXXXXXXXXXXXXXXXXXXXX */ + +/* Trimble parse functions */ +static int parse0x8FAD P((TSIPPKT *, struct peer *)); +static int parse0x8F0B P((TSIPPKT *, struct peer *)); +#ifdef TRIMBLE_OUTPUT_FUNC +static int parseany P((TSIPPKT *, struct peer *)); +static void TranslateTSIPReportToText P((TSIPPKT *, char *)); +#endif /* TRIMBLE_OUTPUT_FUNC */ +static int parse0x5C P((TSIPPKT *, struct peer *)); +static int parse0x4F P((TSIPPKT *, struct peer *)); +static void tsip_input_proc P((TSIPPKT *, int)); + +/* Trimble helper functions */ +static void bPutFloat P((float *, unsigned char *)); +static void bPutDouble P((double *, unsigned char *)); +static void bPutULong P((unsigned long *, unsigned char *)); +static int print_msg_table_header P((int rptcode, char *HdrStr, int force)); +static char * show_time P((float time_of_week)); + +/* RIPE NCC functions */ +static void ripencc_control P((int, struct refclockstat *, struct + refclockstat *, struct peer *)); +static int ripencc_ppsapi P((struct peer *, int, int)); +static int ripencc_get_pps_ts P((struct ripencc_unit *, l_fp *)); +static int ripencc_start P((int, struct peer *)); +static void ripencc_shutdown P((int, struct peer *)); +static void ripencc_poll P((int, struct peer *)); +static void ripencc_send P((struct peer *, TSIPPKT spt)); +static void ripencc_receive P((struct recvbuf *)); + +/* fill in reflock structure for our clock */ +struct refclock refclock_ripencc = { + ripencc_start, /* start up driver */ + ripencc_shutdown, /* shut down driver */ + ripencc_poll, /* transmit poll message */ + ripencc_control, /* control function */ + noentry, /* initialize driver */ + noentry, /* debug info */ + NOFLAGS /* clock flags */ +}; + +/* + * Tables to compute the ddd of year form icky dd/mm timecode. Viva la + * leap. + */ +static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + +/* + * ripencc_start - open the GPS devices and initialize data for processing + */ +static int +ripencc_start(int unit, struct peer *peer) +{ + register struct ripencc_unit *up; + struct refclockproc *pp; + char device[40]; + int fd; + struct termios tio; + TSIPPKT spt; + + /* + * Open serial port + */ + (void)snprintf(device, sizeof(device), DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_RAW))) + return (0); + + /* from refclock_palisade.c */ + if (tcgetattr(fd, &tio) < 0) { + msyslog(LOG_ERR, "Palisade(%d) tcgetattr(fd, &tio): %m",unit); + return (0); + } + + /* + * set flags + */ + tio.c_cflag |= (PARENB|PARODD); + tio.c_iflag &= ~ICRNL; + if (tcsetattr(fd, TCSANOW, &tio) == -1) { + msyslog(LOG_ERR, "Palisade(%d) tcsetattr(fd, &tio): %m",unit); + return (0); + } + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct ripencc_unit *) + emalloc(sizeof(struct ripencc_unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct ripencc_unit)); + pp = peer->procptr; + pp->io.clock_recv = ripencc_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, REFID_LEN); + up->pollcnt = 2; + up->unit = unit; + up->leapdelta = 0; + up->utcflags = 0; + + /* + * Initialize the Clock + */ + + /* query software versions */ + cmd_0x1F(&spt); + ripencc_send(peer, spt); + + /* query receiver health */ + cmd_0x26(&spt); + ripencc_send(peer, spt); + + /* query serial numbers */ + cmd_0x8E42q(&spt); + ripencc_send(peer, spt); + + /* query manuf params */ + cmd_0x8E41q(&spt); + ripencc_send(peer, spt); + + /* i/o opts */ /* trimble manual page A30 */ + cmd_0x35s(&spt, + 0x1C, /* position */ + 0x00, /* velocity */ + 0x05, /* timing */ + 0x0a); /* auxilary */ + ripencc_send(peer, spt); + + /* turn off port A */ + cmd_0x3Ds (&spt, + 0x0B, /* baud_out */ + 0x0B, /* baud_inp */ + 0x07, /* char_code */ + 0x07, /* stopbitcode */ + 0x01, /* output_mode */ + 0x00); /* input_mode */ + ripencc_send(peer, spt); + + /* set i/o options */ + cmd_0x8E4As (&spt, + 0x01, /* PPS on */ + 0x01, /* Timebase UTC */ + 0x00, /* polarity positive */ + 0., /* 100 ft. cable XXX make flag */ + 1e-6 * GPS_C); /* turn of biasuncert. > (1us) */ + ripencc_send(peer,spt); + + /* all outomatic packet output off */ + cmd_0x8E4Ds(&spt, + 0x00000000); /* AutoOutputMask */ + ripencc_send(peer, spt); + + cmd_0xBBq (&spt, + 0x00); /* query primary configuration */ + ripencc_send(peer,spt); + + + /* query PPS parameters */ + cmd_0x8E4Aq (&spt); /* query PPS params */ + ripencc_send(peer,spt); + + /* query survey limit */ + cmd_0x8E4Bq (&spt); /* query survey limit */ + ripencc_send(peer,spt); + +#ifdef DEBUG_NCC + if (debug) + printf("ripencc_start: success\n"); +#endif /* DEBUG_NCC */ + + /* + * Start the PPSAPI interface if it is there. Default to use + * the assert edge and do not enable the kernel hardpps. + */ + if (time_pps_create(fd, &up->handle) < 0) { + up->handle = 0; + msyslog(LOG_ERR, "refclock_ripencc: time_pps_create failed: %m"); + return (1); + } + + return(ripencc_ppsapi(peer, 0, 0)); +} + +/* + * ripencc_control - fudge control + */ +static void +ripencc_control( + int unit, /* unit (not used) */ + struct refclockstat *in, /* input parameters (not used) */ + struct refclockstat *out, /* output parameters (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + +#ifdef DEBUG_NCC + msyslog(LOG_INFO,"%s()",__FUNCTION__); +#endif /* DEBUG_NCC */ + + pp = peer->procptr; + ripencc_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2, + pp->sloppyclockflag & CLK_FLAG3); +} + + +/* + * Initialize PPSAPI + */ +int +ripencc_ppsapi( + struct peer *peer, /* peer structure pointer */ + int enb_clear, /* clear enable */ + int enb_hardpps /* hardpps enable */ + ) +{ + struct refclockproc *pp; + struct ripencc_unit *up; + int capability; + + pp = peer->procptr; + up = (struct ripencc_unit *)pp->unitptr; + if (time_pps_getcap(up->handle, &capability) < 0) { + msyslog(LOG_ERR, + "refclock_ripencc: time_pps_getcap failed: %m"); + return (0); + } + memset(&up->pps_params, 0, sizeof(pps_params_t)); + if (enb_clear) + up->pps_params.mode = capability & PPS_CAPTURECLEAR; + else + up->pps_params.mode = capability & PPS_CAPTUREASSERT; + if (!up->pps_params.mode) { + msyslog(LOG_ERR, + "refclock_ripencc: invalid capture edge %d", + !enb_clear); + return (0); + } + up->pps_params.mode |= PPS_TSFMT_TSPEC; + if (time_pps_setparams(up->handle, &up->pps_params) < 0) { + msyslog(LOG_ERR, + "refclock_ripencc: time_pps_setparams failed: %m"); + return (0); + } + if (enb_hardpps) { + if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS, + up->pps_params.mode & ~PPS_TSFMT_TSPEC, + PPS_TSFMT_TSPEC) < 0) { + msyslog(LOG_ERR, + "refclock_ripencc: time_pps_kcbind failed: %m"); + return (0); + } + pps_enable = 1; + } + peer->precision = PPS_PRECISION; + +#if DEBUG_NCC + if (debug) { + time_pps_getparams(up->handle, &up->pps_params); + printf( + "refclock_ripencc: capability 0x%x version %d mode 0x%x kern %d\n", + capability, up->pps_params.api_version, + up->pps_params.mode, enb_hardpps); + } +#endif /* DEBUG_NCC */ + + return (1); +} + +/* + * This function is called every 64 seconds from ripencc_receive + * It will fetch the pps time + * + * Return 0 on failure and 1 on success. + */ +static int +ripencc_get_pps_ts( + struct ripencc_unit *up, + l_fp *tsptr + ) +{ + pps_info_t pps_info; + struct timespec timeout, ts; + double dtemp; + l_fp tstmp; + +#ifdef DEBUG_PPS + msyslog(LOG_INFO,"ripencc_get_pps_ts\n"); +#endif /* DEBUG_PPS */ + + + /* + * Convert the timespec nanoseconds field to ntp l_fp units. + */ + if (up->handle == 0) + return (0); + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t)); + if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info, + &timeout) < 0) + return (0); + if (up->pps_params.mode & PPS_CAPTUREASSERT) { + if (pps_info.assert_sequence == + up->pps_info.assert_sequence) + return (0); + ts = up->pps_info.assert_timestamp; + } else if (up->pps_params.mode & PPS_CAPTURECLEAR) { + if (pps_info.clear_sequence == + up->pps_info.clear_sequence) + return (0); + ts = up->pps_info.clear_timestamp; + } else { + return (0); + } + if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec)) + return (0); + up->ts = ts; + + tstmp.l_ui = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec * FRAC / 1e9; + tstmp.l_uf = (u_int32)dtemp; + +#ifdef DEBUG_PPS + msyslog(LOG_INFO,"ts.tv_sec: %d\n",(int)ts.tv_sec); + msyslog(LOG_INFO,"ts.tv_nsec: %ld\n",ts.tv_nsec); +#endif /* DEBUG_PPS */ + + *tsptr = tstmp; + return (1); +} + +/* + * ripencc_shutdown - shut down a GPS clock + */ +static void +ripencc_shutdown(int unit, struct peer *peer) +{ + register struct ripencc_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct ripencc_unit *)pp->unitptr; + + if (up->handle != 0) + time_pps_destroy(up->handle); + + io_closeclock(&pp->io); + + free(up); +} + +/* + * ripencc_poll - called by the transmit procedure + */ +static void +ripencc_poll(int unit, struct peer *peer) +{ + register struct ripencc_unit *up; + struct refclockproc *pp; + TSIPPKT spt; + +#ifdef DEBUG_NCC + if (debug) + fprintf(stderr, "ripencc_poll(%d)\n", unit); +#endif /* DEBUG_NCC */ + pp = peer->procptr; + up = (struct ripencc_unit *)pp->unitptr; + if (up->pollcnt == 0) + refclock_report(peer, CEVNT_TIMEOUT); + else + up->pollcnt--; + + pp->polls++; + up->polled = 1; + + /* poll for UTC superpacket */ + cmd_0x8EADq (&spt); + ripencc_send(peer,spt); +} + +/* + * ripencc_send - send message to clock + * use the structures being created by the trimble functions! + * makes the code more readable/clean + */ +static void +ripencc_send(struct peer *peer, TSIPPKT spt) +{ + unsigned char *ip, *op; + unsigned char obuf[512]; + +#ifdef DEBUG_RAW + { + register struct ripencc_unit *up; + register struct refclockproc *pp; + + pp = peer->procptr; + up = (struct ripencc_unit *)pp->unitptr; + if (debug) + printf("ripencc_send(%d, %02X)\n", up->unit, cmd); + } +#endif /* DEBUG_RAW */ + + ip = spt.buf; + op = obuf; + + *op++ = 0x10; + *op++ = spt.code; + + while (spt.len--) { + if (op-obuf > sizeof(obuf)-5) { + msyslog(LOG_ERR, "ripencc_send obuf overflow!"); + refclock_report(peer, CEVNT_FAULT); + return; + } + + if (*ip == 0x10) /* byte stuffing */ + *op++ = 0x10; + *op++ = *ip++; + } + + *op++ = 0x10; + *op++ = 0x03; + +#ifdef DEBUG_RAW + if (debug) { /* print raw packet */ + unsigned char *cp; + int i; + + printf("ripencc_send: len %d\n", op-obuf); + for (i=1, cp=obuf; cp<op; i++, cp++) { + printf(" %02X", *cp); + if (i%10 == 0) + printf("\n"); + } + printf("\n"); + } +#endif /* DEBUG_RAW */ + + if (write(peer->procptr->io.fd, obuf, op-obuf) == -1) { + refclock_report(peer, CEVNT_FAULT); + } +} + +/* + * ripencc_receive() + * + * called when a packet is received on the serial port + * takes care of further processing + * + */ +static void +ripencc_receive(struct recvbuf *rbufp) +{ + register struct ripencc_unit *up; + register struct refclockproc *pp; + struct peer *peer; + static TSIPPKT rpt; /* structure for current incoming TSIP report */ + TSIPPKT spt; /* send packet */ + int ns_since_pps; + int i; + char *cp; + /* Use these variables to hold data until we decide its worth keeping */ + char rd_lastcode[BMAX]; + l_fp rd_tmp; + u_short rd_lencode; + + /* msyslog(LOG_INFO, "%s",__FUNCTION__); */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct ripencc_unit *)pp->unitptr; + rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); + +#ifdef DEBUG_RAW + if (debug) + fprintf(stderr, "ripencc_receive(%d)\n", up->unit); +#endif /* DEBUG_RAW */ + +#ifdef DEBUG_RAW + if (debug) { /* print raw packet */ + int i; + unsigned char *cp; + + printf("ripencc_receive: len %d\n", rbufp->recv_length); + for (i=1, cp=(char*)&rbufp->recv_space; i <= rbufp->recv_length; i++, cp++) { + printf(" %02X", *cp); + if (i%10 == 0) + printf("\n"); + } + printf("\n"); + } +#endif /* DEBUG_RAW */ + + cp = (char*) &rbufp->recv_space; + i=rbufp->recv_length; + + while (i--) { /* loop over received chars */ + + tsip_input_proc(&rpt, (unsigned char) *cp++); + + if (rpt.status != TSIP_PARSED_FULL) + continue; + + switch (rpt.code) { + + case 0x8F: /* superpacket */ + + switch (rpt.buf[0]) { + + case 0xAD: /* UTC Time */ + /* + * When polling on port B the timecode + * is the time of the previous PPS. + * If we completed receiving the packet + * less than 150ms after the turn of the second, + * it may have the code of the previous second. + * We do not trust that and simply poll again + * without even parsing it. + * + * More elegant would be to re-schedule the poll, + * but I do not know (yet) how to do that cleanly. + * + */ + /* BLA ns_since_pps = ncc_tstmp(rbufp, &trtmp); */ +/* if (up->polled && ns_since_pps > -1 && ns_since_pps < 150) { */ + + ns_since_pps=200; + if (up->polled && ns_since_pps < 150) { + msyslog(LOG_INFO, "%s(): up->polled",__FUNCTION__); + ripencc_poll(up->unit, peer); + break; + } + + /* + * Parse primary utc time packet + * and fill refclock structure + * from results. + */ + if (parse0x8FAD(&rpt, peer) < 0) { + msyslog(LOG_INFO, "%s(): parse0x8FAD < 0",__FUNCTION__); + refclock_report(peer, CEVNT_BADREPLY); + break; + } + /* + * If the PPSAPI is working, rather use its + * timestamps. + * assume that the PPS occurs on the second + * so blow any msec + */ + if (ripencc_get_pps_ts(up, &rd_tmp) == 1) { + pp->lastrec = up->tstamp = rd_tmp; + pp->nsec = 0; + } + else + msyslog(LOG_INFO, "%s(): ripencc_get_pps_ts returns failure\n",__FUNCTION__); + + + if (!up->polled) { + msyslog(LOG_INFO, "%s(): unrequested packet\n",__FUNCTION__); + /* unrequested packet */ + break; + } + + /* we have been polled ! */ + up->polled = 0; + up->pollcnt = 2; + + /* poll for next packet */ + cmd_0x8E0Bq(&spt); + ripencc_send(peer,spt); + + if (ns_since_pps < 0) { /* no PPS */ + msyslog(LOG_INFO, "%s(): ns_since_pps < 0",__FUNCTION__); + refclock_report(peer, CEVNT_BADTIME); + break; + } + + /* + * Process the new sample in the median filter and determine the + * reference clock offset and dispersion. + */ + if (!refclock_process(pp)) { + msyslog(LOG_INFO, "%s(): !refclock_process",__FUNCTION__); + refclock_report(peer, CEVNT_BADTIME); + break; + } + + refclock_receive(peer); + break; + + case 0x0B: /* comprehensive time packet */ + parse0x8F0B(&rpt, peer); + break; + + default: /* other superpackets */ +#ifdef DEBUG_NCC + msyslog(LOG_INFO, "%s(): calling parseany",__FUNCTION__); +#endif /* DEBUG_NCC */ +#ifdef TRIMBLE_OUTPUT_FUNC + parseany(&rpt, peer); +#endif /* TRIMBLE_OUTPUT_FUNC */ + break; + } + break; + + case 0x4F: /* UTC parameters, for leap info */ + parse0x4F(&rpt, peer); + break; + + case 0x5C: /* sat tracking data */ + parse0x5C(&rpt, peer); + break; + + default: /* other packets */ +#ifdef TRIMBLE_OUTPUT_FUNC + parseany(&rpt, peer); +#endif /* TRIMBLE_OUTPUT_FUNC */ + break; + } + rpt.status = TSIP_PARSED_EMPTY; + } +} + +/* + * All trimble functions that are directly referenced from driver code + * (so not from parseany) + */ + +void cmd_0x1F (TSIPPKT *cmd) +/* request software versions */ +{ + cmd->len = 0; + cmd->code = 0x1F; +} + +void cmd_0x26 (TSIPPKT *cmd) +/* request receiver health */ +{ + cmd->len = 0; + cmd->code = 0x26; +} + + + + +void cmd_0x2F (TSIPPKT *cmd) +/* request UTC params */ +{ + cmd->len = 0; + cmd->code = 0x2F; +} + +void cmd_0x35s (TSIPPKT *cmd, unsigned char pos_code, unsigned char vel_code, + unsigned char time_code, unsigned char opts_code) +/* set serial I/O options */ +{ + cmd->buf[0] = pos_code; + cmd->buf[1] = vel_code; + cmd->buf[2] = time_code; + cmd->buf[3] = opts_code; + cmd->len = 4; + cmd->code = 0x35; +} +void cmd_0x3C (TSIPPKT *cmd, unsigned char sv_prn) +/* request tracking status */ +{ + cmd->buf[0] = sv_prn; + cmd->len = 1; + cmd->code = 0x3C; +} + + +void cmd_0x3Ds (TSIPPKT *cmd, + unsigned char baud_out, unsigned char baud_inp, + unsigned char char_code, unsigned char stopbitcode, + unsigned char output_mode, unsigned char input_mode) +/* set Channel A configuration for dual-port operation */ +{ + cmd->buf[0] = baud_out; /* XMT baud rate */ + cmd->buf[1] = baud_inp; /* RCV baud rate */ + cmd->buf[2] = char_code; /* parity and #bits per byte */ + cmd->buf[3] = stopbitcode; /* number of stop bits code */ + cmd->buf[4] = output_mode; /* Ch. A transmission mode */ + cmd->buf[5] = input_mode; /* Ch. A reception mode */ + cmd->len = 6; + cmd->code = 0x3D; +} + + +/* query primary configuration */ +void cmd_0xBBq (TSIPPKT *cmd, + unsigned char subcode) +{ + + cmd->len = 1; + cmd->code = 0xBB; + cmd->buf[0] = subcode; +} + + +/**** Superpackets ****/ +void cmd_0x8E0Bq (TSIPPKT *cmd) +/* 8E-0B to query 8F-0B controls */ +{ + + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0x0B; +} + + +void cmd_0x8E41q (TSIPPKT *cmd) +/* 8F-41 to query board serial number */ +{ + + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0x41; +} + + +void cmd_0x8E42q (TSIPPKT *cmd) +/* 8F-42 to query product serial number */ +{ + + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0x42; +} +void cmd_0x8E4Aq (TSIPPKT *cmd) +/* 8F-4A to query PPS parameters */ +{ + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0x4A; +} + + +/* set i/o options */ +void cmd_0x8E4As (TSIPPKT *cmd, + unsigned char PPSOnOff, + unsigned char TimeBase, + unsigned char Polarity, + double PPSOffset, + float Uncertainty) +{ + cmd->len = 16; + cmd->code = 0x8E; + cmd->buf[0] = 0x4A; + cmd->buf[1] = PPSOnOff; + cmd->buf[2] = TimeBase; + cmd->buf[3] = Polarity; + bPutDouble (&PPSOffset, &cmd->buf[4]); + bPutFloat (&Uncertainty, &cmd->buf[12]); +} +void cmd_0x8E4Bq (TSIPPKT *cmd) +/* 8F-4B query survey limit */ +{ + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0x4B; +} + + +/* poll for UTC superpacket */ +void cmd_0x8EADq (TSIPPKT *cmd) +/* 8E-AD to query 8F-AD controls */ +{ + cmd->len = 1; + cmd->code = 0x8E; + cmd->buf[0] = 0xAD; +} + +/* all outomatic packet output off */ +void cmd_0x8E4Ds (TSIPPKT *cmd, + unsigned long AutoOutputMask) +{ + cmd->len = 5; + cmd->code = 0x8E; + cmd->buf[0] = 0x4D; + bPutULong (&AutoOutputMask, &cmd->buf[1]); +} + + + + +/* for DOS machines, reverse order of bytes as they come through the + * serial port. */ +#ifdef BYTESWAP +static short bGetShort (unsigned char *bp) +{ + short outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 1; + *optr-- = *bp++; + *optr = *bp; + return outval; +} + +#ifdef TRIMBLE_OUTPUT_FUNC +static unsigned short bGetUShort (unsigned char *bp) +{ + unsigned short outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 1; + *optr-- = *bp++; + *optr = *bp; + return outval; +} + +static long bGetLong (unsigned char *bp) +{ + long outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 3; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr = *bp; + return outval; +} + +static unsigned long bGetULong (unsigned char *bp) +{ + unsigned long outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 3; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr = *bp; + return outval; +} +#endif /* TRIMBLE_OUTPUT_FUNC */ + +static float bGetSingle (unsigned char *bp) +{ + float outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 3; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr = *bp; + return outval; +} + +static double bGetDouble (unsigned char *bp) +{ + double outval; + unsigned char *optr; + + optr = (unsigned char*)&outval + 7; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr-- = *bp++; + *optr = *bp; + return outval; +} + +#else /* not BYTESWAP */ + +#define bGetShort(bp) (*(short*)(bp)) +#define bGetLong(bp) (*(long*)(bp)) +#define bGetULong(bp) (*(unsigned long*)(bp)) +#define bGetSingle(bp) (*(float*)(bp)) +#define bGetDouble(bp) (*(double*)(bp)) + +#endif /* BYTESWAP */ +/* + * Byte-reversal is necessary for little-endian (Intel-based) machines. + * TSIP streams are Big-endian (Motorola-based). + */ +#ifdef BYTESWAP + +void +bPutFloat (float *in, unsigned char *out) +{ + unsigned char *inptr; + + inptr = (unsigned char*)in + 3; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out = *inptr; +} + +static void +bPutULong (unsigned long *in, unsigned char *out) +{ + unsigned char *inptr; + + inptr = (unsigned char*)in + 3; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out = *inptr; +} + +static void +bPutDouble (double *in, unsigned char *out) +{ + unsigned char *inptr; + + inptr = (unsigned char*)in + 7; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out++ = *inptr--; + *out = *inptr; +} + +#else /* not BYTESWAP */ + +void bPutShort (short a, unsigned char *cmdbuf) {*(short*) cmdbuf = a;} +void bPutULong (long a, unsigned char *cmdbuf) {*(long*) cmdbuf = a;} +void bPutFloat (float a, unsigned char *cmdbuf) {*(float*) cmdbuf = a;} +void bPutDouble (double a, unsigned char *cmdbuf){*(double*) cmdbuf = a;} + +#endif /* BYTESWAP */ + +/* + * Parse primary utc time packet + * and fill refclock structure + * from results. + * + * 0 = success + * -1 = errors + */ + +static int +parse0x8FAD(rpt, peer) + TSIPPKT *rpt; + struct peer *peer; +{ + register struct refclockproc *pp; + register struct ripencc_unit *up; + + unsigned day, month, year; /* data derived from received timecode */ + unsigned hour, minute, second; + unsigned char trackstat, utcflags; + + static char logbuf[1024]; /* logging string buffer */ + int i; + unsigned char *buf; + + buf = rpt->buf; + pp = peer->procptr; + + if (rpt->len != 22) + return (-1); + + if (bGetShort(&buf[1]) != 0) { +#ifdef DEBUG_NCC + if (debug) + printf("parse0x8FAD: event count != 0\n"); +#endif /* DEBUG_NCC */ + return(-1); + } + + + if (bGetDouble(&buf[3]) != 0.0) { +#ifdef DEBUG_NCC + if (debug) + printf("parse0x8FAD: fracsecs != 0\n"); +#endif /* DEBUG_NCC */ + return(-1); + } + + hour = (unsigned int) buf[11]; + minute = (unsigned int) buf[12]; + second = (unsigned int) buf[13]; + day = (unsigned int) buf[14]; + month = (unsigned int) buf[15]; + year = bGetShort(&buf[16]); + trackstat = buf[18]; + utcflags = buf[19]; + + + sprintf(logbuf, "U1 %d.%d.%d %02d:%02d:%02d %d %02x", + day, month, year, hour, minute, second, trackstat, utcflags); + +#ifdef DEBUG_NCC + if (debug) + puts(logbuf); +#endif /* DEBUG_NCC */ + + record_clock_stats(&peer->srcadr, logbuf); + + if (!utcflags & UTCF_UTC_AVAIL) + return(-1); + + /* poll for UTC parameters once and then if UTC flag changed */ + up = (struct ripencc_unit *) pp->unitptr; + if (utcflags != up->utcflags) { + TSIPPKT spt; /* local structure for send packet */ + cmd_0x2F (&spt); /* request UTC params */ + ripencc_send(peer,spt); + up->utcflags = utcflags; + } + + /* + * If we hit the leap second, we choose to skip this sample + * rather than rely on other code to be perfectly correct. + * No offense, just defense ;-). + */ + if (second == 60) + return(-1); + + /* now check and convert the time we received */ + + pp->year = year; + if (month < 1 || month > 12 || day < 1 || day > 31) + return(-1); + + if (pp->year % 4) { + if (day > day1tab[month - 1]) + return(-1); + for (i = 0; i < month - 1; i++) + day += day1tab[i]; + } else { + if (day > day2tab[month - 1]) + return(-1); + for (i = 0; i < month - 1; i++) + day += day2tab[i]; + } + pp->day = day; + pp->hour = hour; + pp->minute = minute; + pp-> second = second; + pp->nsec = 0; + + if ((utcflags&UTCF_LEAP_PNDG) && up->leapdelta != 0) + pp-> leap = (up->leapdelta > 0 ? LEAP_ADDSECOND : LEAP_DELSECOND); + else + pp-> leap = LEAP_NOWARNING; + + return (0); +} + +/* + * Parse comprehensive time packet + * + * 0 = success + * -1 = errors + */ + +int parse0x8F0B(rpt, peer) + TSIPPKT *rpt; + struct peer *peer; +{ + register struct refclockproc *pp; + + unsigned day, month, year; /* data derived from received timecode */ + unsigned hour, minute, second; + unsigned utcoff; + unsigned char mode; + double bias, rate; + float biasunc, rateunc; + double lat, lon, alt; + short lat_deg, lon_deg; + float lat_min, lon_min; + unsigned char north_south, east_west; + char sv[9]; + + static char logbuf[1024]; /* logging string buffer */ + unsigned char b; + int i; + unsigned char *buf; + double tow; + + buf = rpt->buf; + pp = peer->procptr; + + if (rpt->len != 74) + return (-1); + + if (bGetShort(&buf[1]) != 0) + return(-1);; + + tow = bGetDouble(&buf[3]); + + if (tow == -1.0) { + return(-1); + } + else if ((tow >= 604800.0) || (tow < 0.0)) { + return(-1); + } + else + { + if (tow < 604799.9) tow = tow + .00000001; + second = (unsigned int) fmod(tow, 60.); + minute = (unsigned int) fmod(tow/60., 60.); + hour = (unsigned int )fmod(tow / 3600., 24.); + } + + + day = (unsigned int) buf[11]; + month = (unsigned int) buf[12]; + year = bGetShort(&buf[13]); + mode = buf[15]; + utcoff = bGetShort(&buf[16]); + bias = bGetDouble(&buf[18]) / GPS_C * 1e9; /* ns */ + rate = bGetDouble(&buf[26]) / GPS_C * 1e9; /* ppb */ + biasunc = bGetSingle(&buf[34]) / GPS_C * 1e9; /* ns */ + rateunc = bGetSingle(&buf[38]) / GPS_C * 1e9; /* ppb */ + lat = bGetDouble(&buf[42]) * R2D; + lon = bGetDouble(&buf[50]) * R2D; + alt = bGetDouble(&buf[58]); + + if (lat < 0.0) { + north_south = 'S'; + lat = -lat; + } + else { + north_south = 'N'; + } + lat_deg = (short)lat; + lat_min = (lat - lat_deg) * 60.0; + + if (lon < 0.0) { + east_west = 'W'; + lon = -lon; + } + else { + east_west = 'E'; + } + + lon_deg = (short)lon; + lon_min = (lon - lon_deg) * 60.0; + + for (i=0; i<8; i++) { + sv[i] = buf[i + 66]; + if (sv[i]) { + TSIPPKT spt; /* local structure for sendpacket */ + b = (unsigned char) (sv[i]<0 ? -sv[i] : sv[i]); + /* request tracking status */ + cmd_0x3C (&spt, b); + ripencc_send(peer,spt); + } + } + + + sprintf(logbuf, "C1 %02d%02d%04d %02d%02d%02d %d %7.0f %.1f %.0f %.1f %d %02d%09.6f %c %02d%09.6f %c %.0f %d %d %d %d %d %d %d %d", + day, month, year, hour, minute, second, mode, bias, biasunc, rate, rateunc, utcoff, + lat_deg, lat_min, north_south, lon_deg, lon_min, east_west, alt, + sv[0], sv[1], sv[2], sv[3], sv[4], sv[5], sv[6], sv[7]); + +#ifdef DEBUG_NCC + if (debug) + puts(logbuf); +#endif /* DEBUG_NCC */ + + record_clock_stats(&peer->srcadr, logbuf); + + return (0); +} + +#ifdef TRIMBLE_OUTPUT_FUNC +/* + * Parse any packet using Trimble machinery + */ +int parseany(rpt, peer) + TSIPPKT *rpt; + struct peer *peer; +{ + static char logbuf[1024]; /* logging string buffer */ + + TranslateTSIPReportToText (rpt, logbuf); /* anything else */ +#ifdef DEBUG_NCC + if (debug) + puts(&logbuf[1]); +#endif /* DEBUG_NCC */ + record_clock_stats(&peer->srcadr, &logbuf[1]); + return(0); +} +#endif /* TRIMBLE_OUTPUT_FUNC */ + + +/* + * Parse UTC Parameter Packet + * + * See the IDE for documentation! + * + * 0 = success + * -1 = errors + */ + +int parse0x4F(rpt, peer) + TSIPPKT *rpt; + struct peer *peer; +{ + register struct ripencc_unit *up; + + double a0; + float a1, tot; + int dt_ls, wn_t, wn_lsf, dn, dt_lsf; + + static char logbuf[1024]; /* logging string buffer */ + unsigned char *buf; + + buf = rpt->buf; + + if (rpt->len != 26) + return (-1); + a0 = bGetDouble (buf); + a1 = bGetSingle (&buf[8]); + dt_ls = bGetShort (&buf[12]); + tot = bGetSingle (&buf[14]); + wn_t = bGetShort (&buf[18]); + wn_lsf = bGetShort (&buf[20]); + dn = bGetShort (&buf[22]); + dt_lsf = bGetShort (&buf[24]); + + sprintf(logbuf, "L1 %d %d %d %g %g %g %d %d %d", + dt_lsf - dt_ls, dt_ls, dt_lsf, a0, a1, tot, wn_t, wn_lsf, dn); + +#ifdef DEBUG_NCC + if (debug) + puts(logbuf); +#endif /* DEBUG_NCC */ + + record_clock_stats(&peer->srcadr, logbuf); + + up = (struct ripencc_unit *) peer->procptr->unitptr; + up->leapdelta = dt_lsf - dt_ls; + + return (0); +} + +/* + * Parse Tracking Status packet + * + * 0 = success + * -1 = errors + */ + +int parse0x5C(rpt, peer) + TSIPPKT *rpt; + struct peer *peer; +{ + unsigned char prn, channel, aqflag, ephstat; + float snr, azinuth, elevation; + + static char logbuf[1024]; /* logging string buffer */ + unsigned char *buf; + + buf = rpt->buf; + + if (rpt->len != 24) + return(-1); + + prn = buf[0]; + channel = (unsigned char)(buf[1] >> 3); + if (channel == 0x10) + channel = 2; + else + channel++; + aqflag = buf[2]; + ephstat = buf[3]; + snr = bGetSingle(&buf[4]); + elevation = bGetSingle(&buf[12]) * R2D; + azinuth = bGetSingle(&buf[16]) * R2D; + + sprintf(logbuf, "S1 %02d %d %d %02x %4.1f %5.1f %4.1f", + prn, channel, aqflag, ephstat, snr, azinuth, elevation); + +#ifdef DEBUG_NCC + if (debug) + puts(logbuf); +#endif /* DEBUG_NCC */ + + record_clock_stats(&peer->srcadr, logbuf); + + return (0); +} + +/******* Code below is from Trimble Tsipchat *************/ + +/* + * ************************************************************************* + * + * Trimble Navigation, Ltd. + * OEM Products Development Group + * P.O. Box 3642 + * 645 North Mary Avenue + * Sunnyvale, California 94088-3642 + * + * Corporate Headquarter: + * Telephone: (408) 481-8000 + * Fax: (408) 481-6005 + * + * Technical Support Center: + * Telephone: (800) 767-4822 (U.S. and Canada) + * (408) 481-6940 (outside U.S. and Canada) + * Fax: (408) 481-6020 + * BBS: (408) 481-7800 + * e-mail: trimble_support@trimble.com + * ftp://ftp.trimble.com/pub/sct/embedded/bin + * + * ************************************************************************* + * + * ------- BYTE-SWAPPING ------- + * TSIP is big-endian (Motorola) protocol. To use on little-endian (Intel) + * systems, the bytes of all multi-byte types (shorts, floats, doubles, etc.) + * must be reversed. This is controlled by the MACRO BYTESWAP; if defined, it + * assumes little-endian protocol. + * -------------------------------- + * + * T_PARSER.C and T_PARSER.H contains primitive functions that interpret + * reports received from the receiver. A second source file pair, + * T_FORMAT.C and T_FORMAT.H, contin the matching TSIP command formatters. + * + * The module is in very portable, basic C language. It can be used as is, or + * with minimal changes if a TSIP communications application is needed separate + * from TSIPCHAT. The construction of most argument lists avoid the use of + * structures, but the developer is encouraged to reconstruct them using such + * definitions to meet project requirements. Declarations of T_PARSER.C + * functions are included in T_PARSER.H to provide prototyping definitions. + * + * There are two types of functions: a serial input processing routine, + * tsip_input_proc() + * which assembles incoming bytes into a TSIPPKT structure, and the + * report parsers, rpt_0x??(). + * + * 1) The function tsip_input_proc() accumulates bytes from the receiver, + * strips control bytes (DLE), and checks if the report end sequence (DLE ETX) + * has been received. rpt.status is defined as TSIP_PARSED_FULL (== 1) + * if a complete packet is available. + * + * 2) The functions rpt_0x??() are report string interpreters patterned after + * the document called "Trimble Standard Interface Protocol". It should be + * noted that if the report buffer is sent into the receiver with the wrong + * length (byte count), the rpt_0x??() returns the Boolean equivalence for + * TRUE. + * + * ************************************************************************* + * + */ + + +/**/ +static void tsip_input_proc ( + TSIPPKT *rpt, + int inbyte) +/* reads bytes until serial buffer is empty or a complete report + * has been received; end of report is signified by DLE ETX. + */ +{ + unsigned char newbyte; + + if (inbyte < 0 || inbyte > 0xFF) return; + + newbyte = (unsigned char)(inbyte); + switch (rpt->status) + { + case TSIP_PARSED_DLE_1: + switch (newbyte) + { + case 0: + case ETX: + /* illegal TSIP IDs */ + rpt->len = 0; + rpt->status = TSIP_PARSED_EMPTY; + break; + case DLE: + /* try normal message start again */ + rpt->len = 0; + rpt->status = TSIP_PARSED_DLE_1; + break; + default: + /* legal TSIP ID; start message */ + rpt->code = newbyte; + rpt->len = 0; + rpt->status = TSIP_PARSED_DATA; + break; + } + break; + case TSIP_PARSED_DATA: + switch (newbyte) { + case DLE: + /* expect DLE or ETX next */ + rpt->status = TSIP_PARSED_DLE_2; + break; + default: + /* normal data byte */ + rpt->buf[rpt->len] = newbyte; + rpt->len++; + /* no change in rpt->status */ + break; + } + break; + case TSIP_PARSED_DLE_2: + switch (newbyte) { + case DLE: + /* normal data byte */ + rpt->buf[rpt->len] = newbyte; + rpt->len++; + rpt->status = TSIP_PARSED_DATA; + break; + case ETX: + /* end of message; return TRUE here. */ + rpt->status = TSIP_PARSED_FULL; + break; + default: + /* error: treat as TSIP_PARSED_DLE_1; start new report packet */ + rpt->code = newbyte; + rpt->len = 0; + rpt->status = TSIP_PARSED_DATA; + } + break; + case TSIP_PARSED_FULL: + case TSIP_PARSED_EMPTY: + default: + switch (newbyte) { + case DLE: + /* normal message start */ + rpt->len = 0; + rpt->status = TSIP_PARSED_DLE_1; + break; + default: + /* error: ignore newbyte */ + rpt->len = 0; + rpt->status = TSIP_PARSED_EMPTY; + } + break; + } + if (rpt->len > MAX_RPTBUF) { + /* error: start new report packet */ + rpt->status = TSIP_PARSED_EMPTY; + rpt->len = 0; + } +} + +#ifdef TRIMBLE_OUTPUT_FUNC + +/**/ +short rpt_0x3D (TSIPPKT *rpt, + unsigned char *tx_baud_index, + unsigned char *rx_baud_index, + unsigned char *char_format_index, + unsigned char *stop_bits, + unsigned char *tx_mode_index, + unsigned char *rx_mode_index) +/* Channel A configuration for dual port operation */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 6) return TRUE; + *tx_baud_index = buf[0]; + *rx_baud_index = buf[1]; + *char_format_index = buf[2]; + *stop_bits = (unsigned char)((buf[3] == 0x07) ? 1 : 2); + *tx_mode_index = buf[4]; + *rx_mode_index = buf[5]; + return FALSE; +} + +/**/ +short rpt_0x40 (TSIPPKT *rpt, + unsigned char *sv_prn, + short *week_num, + float *t_zc, + float *eccentricity, + float *t_oa, + float *i_0, + float *OMEGA_dot, + float *sqrt_A, + float *OMEGA_0, + float *omega, + float *M_0) +/* almanac data for specified satellite */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 39) return TRUE; + *sv_prn = buf[0]; + *t_zc = bGetSingle (&buf[1]); + *week_num = bGetShort (&buf[5]); + *eccentricity = bGetSingle (&buf[7]); + *t_oa = bGetSingle (&buf[11]); + *i_0 = bGetSingle (&buf[15]); + *OMEGA_dot = bGetSingle (&buf[19]); + *sqrt_A = bGetSingle (&buf[23]); + *OMEGA_0 = bGetSingle (&buf[27]); + *omega = bGetSingle (&buf[31]); + *M_0 = bGetSingle (&buf[35]); + return FALSE; +} + +short rpt_0x41 (TSIPPKT *rpt, + float *time_of_week, + float *UTC_offset, + short *week_num) +/* GPS time */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 10) return TRUE; + *time_of_week = bGetSingle (buf); + *week_num = bGetShort (&buf[4]); + *UTC_offset = bGetSingle (&buf[6]); + return FALSE; +} + +short rpt_0x42 (TSIPPKT *rpt, + float pos_ECEF[3], + float *time_of_fix) +/* position in ECEF, single precision */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 16) return TRUE; + pos_ECEF[0] = bGetSingle (buf); + pos_ECEF[1]= bGetSingle (&buf[4]); + pos_ECEF[2]= bGetSingle (&buf[8]); + *time_of_fix = bGetSingle (&buf[12]); + return FALSE; +} + +short rpt_0x43 (TSIPPKT *rpt, + float ECEF_vel[3], + float *freq_offset, + float *time_of_fix) +/* velocity in ECEF, single precision */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 20) return TRUE; + ECEF_vel[0] = bGetSingle (buf); + ECEF_vel[1] = bGetSingle (&buf[4]); + ECEF_vel[2] = bGetSingle (&buf[8]); + *freq_offset = bGetSingle (&buf[12]); + *time_of_fix = bGetSingle (&buf[16]); + return FALSE; +} + +short rpt_0x45 (TSIPPKT *rpt, + unsigned char *major_nav_version, + unsigned char *minor_nav_version, + unsigned char *nav_day, + unsigned char *nav_month, + unsigned char *nav_year, + unsigned char *major_dsp_version, + unsigned char *minor_dsp_version, + unsigned char *dsp_day, + unsigned char *dsp_month, + unsigned char *dsp_year) +/* software versions */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 10) return TRUE; + *major_nav_version = buf[0]; + *minor_nav_version = buf[1]; + *nav_day = buf[2]; + *nav_month = buf[3]; + *nav_year = buf[4]; + *major_dsp_version = buf[5]; + *minor_dsp_version = buf[6]; + *dsp_day = buf[7]; + *dsp_month = buf[8]; + *dsp_year = buf[9]; + return FALSE; +} + +short rpt_0x46 (TSIPPKT *rpt, + unsigned char *status1, + unsigned char *status2) +/* receiver health and status */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 2) return TRUE; + *status1 = buf[0]; + *status2 = buf[1]; + return FALSE; +} + +short rpt_0x47 (TSIPPKT *rpt, + unsigned char *nsvs, unsigned char *sv_prn, + float *snr) +/* signal levels for all satellites tracked */ +{ + short isv; + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 1 + 5*buf[0]) return TRUE; + *nsvs = buf[0]; + for (isv = 0; isv < (*nsvs); isv++) { + sv_prn[isv] = buf[5*isv + 1]; + snr[isv] = bGetSingle (&buf[5*isv + 2]); + } + return FALSE; +} + +short rpt_0x48 (TSIPPKT *rpt, + unsigned char *message) +/* GPS system message */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 22) return TRUE; + memcpy (message, buf, 22); + message[22] = 0; + return FALSE; +} + +short rpt_0x49 (TSIPPKT *rpt, + unsigned char *sv_health) +/* health for all satellites from almanac health page */ +{ + short i; + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 32) return TRUE; + for (i = 0; i < 32; i++) sv_health [i]= buf[i]; + return FALSE; +} + +short rpt_0x4A (TSIPPKT *rpt, + float *lat, + float *lon, + float *alt, + float *clock_bias, + float *time_of_fix) +/* position in lat-lon-alt, single precision */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 20) return TRUE; + *lat = bGetSingle (buf); + *lon = bGetSingle (&buf[4]); + *alt = bGetSingle (&buf[8]); + *clock_bias = bGetSingle (&buf[12]); + *time_of_fix = bGetSingle (&buf[16]); + return FALSE; +} + +short rpt_0x4A_2 (TSIPPKT *rpt, + float *alt, float *dummy , unsigned char *alt_flag) +/* reference altitude parameters */ +{ + unsigned char *buf; + + buf = rpt->buf; + + if (rpt->len != 9) return TRUE; + *alt = bGetSingle (buf); + *dummy = bGetSingle (&buf[4]); + *alt_flag = buf[8]; + return FALSE; +} + +short rpt_0x4B (TSIPPKT *rpt, + unsigned char *machine_id, + unsigned char *status3, + unsigned char *status4) +/* machine ID code, status */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 3) return TRUE; + *machine_id = buf[0]; + *status3 = buf[1]; + *status4 = buf[2]; + return FALSE; +} + +short rpt_0x4C (TSIPPKT *rpt, + unsigned char *dyn_code, + float *el_mask, + float *snr_mask, + float *dop_mask, + float *dop_switch) +/* operating parameters and masks */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 17) return TRUE; + *dyn_code = buf[0]; + *el_mask = bGetSingle (&buf[1]); + *snr_mask = bGetSingle (&buf[5]); + *dop_mask = bGetSingle (&buf[9]); + *dop_switch = bGetSingle (&buf[13]); + return FALSE; +} + +short rpt_0x4D (TSIPPKT *rpt, + float *osc_offset) +/* oscillator offset */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 4) return TRUE; + *osc_offset = bGetSingle (buf); + return FALSE; +} + +short rpt_0x4E (TSIPPKT *rpt, + unsigned char *response) +/* yes/no response to command to set GPS time */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 1) return TRUE; + *response = buf[0]; + return FALSE; +} + +short rpt_0x4F (TSIPPKT *rpt, + double *a0, + float *a1, + float *time_of_data, + short *dt_ls, + short *wn_t, + short *wn_lsf, + short *dn, + short *dt_lsf) +/* UTC data */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 26) return TRUE; + *a0 = bGetDouble (buf); + *a1 = bGetSingle (&buf[8]); + *dt_ls = bGetShort (&buf[12]); + *time_of_data = bGetSingle (&buf[14]); + *wn_t = bGetShort (&buf[18]); + *wn_lsf = bGetShort (&buf[20]); + *dn = bGetShort (&buf[22]); + *dt_lsf = bGetShort (&buf[24]); + return FALSE; +} + +/**/ +short rpt_0x54 (TSIPPKT *rpt, + float *clock_bias, + float *freq_offset, + float *time_of_fix) +/* clock offset and frequency offset in 1-SV (0-D) mode */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 12) return TRUE; + *clock_bias = bGetSingle (buf); + *freq_offset = bGetSingle (&buf[4]); + *time_of_fix = bGetSingle (&buf[8]); + return FALSE; +} + +short rpt_0x55 (TSIPPKT *rpt, + unsigned char *pos_code, + unsigned char *vel_code, + unsigned char *time_code, + unsigned char *aux_code) +/* I/O serial options */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 4) return TRUE; + *pos_code = buf[0]; + *vel_code = buf[1]; + *time_code = buf[2]; + *aux_code = buf[3]; + return FALSE; +} + +short rpt_0x56 (TSIPPKT *rpt, + float vel_ENU[3], float *freq_offset, float *time_of_fix) +/* velocity in east-north-up coordinates */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 20) return TRUE; + /* east */ + vel_ENU[0] = bGetSingle (buf); + /* north */ + vel_ENU[1] = bGetSingle (&buf[4]); + /* up */ + vel_ENU[2] = bGetSingle (&buf[8]); + *freq_offset = bGetSingle (&buf[12]); + *time_of_fix = bGetSingle (&buf[16]); + return FALSE; +} + +short rpt_0x57 (TSIPPKT *rpt, + unsigned char *source_code, unsigned char *diag_code, + short *week_num, + float *time_of_fix) +/* info about last computed fix */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 8) return TRUE; + *source_code = buf[0]; + *diag_code = buf[1]; + *time_of_fix = bGetSingle (&buf[2]); + *week_num = bGetShort (&buf[6]); + return FALSE; +} + +short rpt_0x58 (TSIPPKT *rpt, + unsigned char *op_code, unsigned char *data_type, unsigned char *sv_prn, + unsigned char *data_length, unsigned char *data_packet) +/* GPS system data or acknowledgment of GPS system data load */ +{ + unsigned char *buf, *buf4; + short dl; + ALM_INFO* alminfo; + ION_INFO* ioninfo; + UTC_INFO* utcinfo; + NAV_INFO* navinfo; + + buf = rpt->buf; + + if (buf[0] == 2) { + if (rpt->len < 4) return TRUE; + if (rpt->len != 4+buf[3]) return TRUE; + } + else if (rpt->len != 3) { + return TRUE; + } + *op_code = buf[0]; + *data_type = buf[1]; + *sv_prn = buf[2]; + if (*op_code == 2) { + dl = buf[3]; + *data_length = (unsigned char)dl; + buf4 = &buf[4]; + switch (*data_type) { + case 2: + /* Almanac */ + if (*data_length != sizeof (ALM_INFO)) return TRUE; + alminfo = (ALM_INFO*)data_packet; + alminfo->t_oa_raw = buf4[0]; + alminfo->SV_health = buf4[1]; + alminfo->e = bGetSingle(&buf4[2]); + alminfo->t_oa = bGetSingle(&buf4[6]); + alminfo->i_0 = bGetSingle(&buf4[10]); + alminfo->OMEGADOT = bGetSingle(&buf4[14]); + alminfo->sqrt_A = bGetSingle(&buf4[18]); + alminfo->OMEGA_0 = bGetSingle(&buf4[22]); + alminfo->omega = bGetSingle(&buf4[26]); + alminfo->M_0 = bGetSingle(&buf4[30]); + alminfo->a_f0 = bGetSingle(&buf4[34]); + alminfo->a_f1 = bGetSingle(&buf4[38]); + alminfo->Axis = bGetSingle(&buf4[42]); + alminfo->n = bGetSingle(&buf4[46]); + alminfo->OMEGA_n = bGetSingle(&buf4[50]); + alminfo->ODOT_n = bGetSingle(&buf4[54]); + alminfo->t_zc = bGetSingle(&buf4[58]); + alminfo->weeknum = bGetShort(&buf4[62]); + alminfo->wn_oa = bGetShort(&buf4[64]); + break; + + case 3: + /* Almanac health page */ + if (*data_length != sizeof (ALH_PARMS) + 3) return TRUE; + + /* this record is returned raw */ + memcpy (data_packet, buf4, dl); + break; + + case 4: + /* Ionosphere */ + if (*data_length != sizeof (ION_INFO) + 8) return TRUE; + ioninfo = (ION_INFO*)data_packet; + ioninfo->alpha_0 = bGetSingle (&buf4[8]); + ioninfo->alpha_1 = bGetSingle (&buf4[12]); + ioninfo->alpha_2 = bGetSingle (&buf4[16]); + ioninfo->alpha_3 = bGetSingle (&buf4[20]); + ioninfo->beta_0 = bGetSingle (&buf4[24]); + ioninfo->beta_1 = bGetSingle (&buf4[28]); + ioninfo->beta_2 = bGetSingle (&buf4[32]); + ioninfo->beta_3 = bGetSingle (&buf4[36]); + break; + + case 5: + /* UTC */ + if (*data_length != sizeof (UTC_INFO) + 13) return TRUE; + utcinfo = (UTC_INFO*)data_packet; + utcinfo->A_0 = bGetDouble (&buf4[13]); + utcinfo->A_1 = bGetSingle (&buf4[21]); + utcinfo->delta_t_LS = bGetShort (&buf4[25]); + utcinfo->t_ot = bGetSingle(&buf4[27]); + utcinfo->WN_t = bGetShort (&buf4[31]); + utcinfo->WN_LSF = bGetShort (&buf4[33]); + utcinfo->DN = bGetShort (&buf4[35]); + utcinfo->delta_t_LSF = bGetShort (&buf4[37]); + break; + + case 6: + /* Ephemeris */ + if (*data_length != sizeof (NAV_INFO) - 1) return TRUE; + + navinfo = (NAV_INFO*)data_packet; + + navinfo->sv_number = buf4[0]; + navinfo->t_ephem = bGetSingle (&buf4[1]); + navinfo->ephclk.weeknum = bGetShort (&buf4[5]); + + navinfo->ephclk.codeL2 = buf4[7]; + navinfo->ephclk.L2Pdata = buf4[8]; + navinfo->ephclk.SVacc_raw = buf4[9]; + navinfo->ephclk.SV_health = buf4[10]; + navinfo->ephclk.IODC = bGetShort (&buf4[11]); + navinfo->ephclk.T_GD = bGetSingle (&buf4[13]); + navinfo->ephclk.t_oc = bGetSingle (&buf4[17]); + navinfo->ephclk.a_f2 = bGetSingle (&buf4[21]); + navinfo->ephclk.a_f1 = bGetSingle (&buf4[25]); + navinfo->ephclk.a_f0 = bGetSingle (&buf4[29]); + navinfo->ephclk.SVacc = bGetSingle (&buf4[33]); + + navinfo->ephorb.IODE = buf4[37]; + navinfo->ephorb.fit_interval = buf4[38]; + navinfo->ephorb.C_rs = bGetSingle (&buf4[39]); + navinfo->ephorb.delta_n = bGetSingle (&buf4[43]); + navinfo->ephorb.M_0 = bGetDouble (&buf4[47]); + navinfo->ephorb.C_uc = bGetSingle (&buf4[55]); + navinfo->ephorb.e = bGetDouble (&buf4[59]); + navinfo->ephorb.C_us = bGetSingle (&buf4[67]); + navinfo->ephorb.sqrt_A = bGetDouble (&buf4[71]); + navinfo->ephorb.t_oe = bGetSingle (&buf4[79]); + navinfo->ephorb.C_ic = bGetSingle (&buf4[83]); + navinfo->ephorb.OMEGA_0 = bGetDouble (&buf4[87]); + navinfo->ephorb.C_is = bGetSingle (&buf4[95]); + navinfo->ephorb.i_0 = bGetDouble (&buf4[99]); + navinfo->ephorb.C_rc = bGetSingle (&buf4[107]); + navinfo->ephorb.omega = bGetDouble (&buf4[111]); + navinfo->ephorb.OMEGADOT=bGetSingle (&buf4[119]); + navinfo->ephorb.IDOT = bGetSingle (&buf4[123]); + navinfo->ephorb.Axis = bGetDouble (&buf4[127]); + navinfo->ephorb.n = bGetDouble (&buf4[135]); + navinfo->ephorb.r1me2 = bGetDouble (&buf4[143]); + navinfo->ephorb.OMEGA_n=bGetDouble (&buf4[151]); + navinfo->ephorb.ODOT_n = bGetDouble (&buf4[159]); + break; + } + } + return FALSE; +} + +short rpt_0x59 (TSIPPKT *rpt, + unsigned char *code_type, + unsigned char status_code[32]) +/* satellite enable/disable or health heed/ignore list */ +{ + short iprn; + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 33) return TRUE; + *code_type = buf[0]; + for (iprn = 0; iprn < 32; iprn++) + status_code[iprn] = buf[iprn + 1]; + return FALSE; +} + +short rpt_0x5A (TSIPPKT *rpt, + unsigned char *sv_prn, + float *sample_length, + float *signal_level, + float *code_phase, + float *Doppler, + double *time_of_fix) +/* raw measurement data - code phase/Doppler */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 25) return TRUE; + *sv_prn = buf[0]; + *sample_length = bGetSingle (&buf[1]); + *signal_level = bGetSingle (&buf[5]); + *code_phase = bGetSingle (&buf[9]); + *Doppler = bGetSingle (&buf[13]); + *time_of_fix = bGetDouble (&buf[17]); + return FALSE; +} + +short rpt_0x5B (TSIPPKT *rpt, + unsigned char *sv_prn, + unsigned char *sv_health, + unsigned char *sv_iode, + unsigned char *fit_interval_flag, + float *time_of_collection, + float *time_of_eph, + float *sv_accy) +/* satellite ephorb status */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 16) return TRUE; + *sv_prn = buf[0]; + *time_of_collection = bGetSingle (&buf[1]); + *sv_health = buf[5]; + *sv_iode = buf[6]; + *time_of_eph = bGetSingle (&buf[7]); + *fit_interval_flag = buf[11]; + *sv_accy = bGetSingle (&buf[12]); + return FALSE; +} + +short rpt_0x5C (TSIPPKT *rpt, + unsigned char *sv_prn, + unsigned char *slot, + unsigned char *chan, + unsigned char *acq_flag, + unsigned char *eph_flag, + float *signal_level, + float *time_of_last_msmt, + float *elev, + float *azim, + unsigned char *old_msmt_flag, + unsigned char *integer_msec_flag, + unsigned char *bad_data_flag, + unsigned char *data_collect_flag) +/* satellite tracking status */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 24) return TRUE; + *sv_prn = buf[0]; + *slot = (unsigned char)((buf[1] & 0x07) + 1); + *chan = (unsigned char)(buf[1] >> 3); + if (*chan == 0x10) *chan = 2; + else (*chan)++; + *acq_flag = buf[2]; + *eph_flag = buf[3]; + *signal_level = bGetSingle (&buf[4]); + *time_of_last_msmt = bGetSingle (&buf[8]); + *elev = bGetSingle (&buf[12]); + *azim = bGetSingle (&buf[16]); + *old_msmt_flag = buf[20]; + *integer_msec_flag = buf[21]; + *bad_data_flag = buf[22]; + *data_collect_flag = buf[23]; + return FALSE; +} + +/**/ +short rpt_0x6D (TSIPPKT *rpt, + unsigned char *manual_mode, + unsigned char *nsvs, + unsigned char *ndim, + unsigned char sv_prn[], + float *pdop, + float *hdop, + float *vdop, + float *tdop) +/* over-determined satellite selection for position fixes, PDOP, fix mode */ +{ + short islot; + unsigned char *buf; + buf = rpt->buf; + + *nsvs = (unsigned char)((buf[0] & 0xF0) >> 4); + if ((*nsvs)>8) return TRUE; + if (rpt->len != 17 + (*nsvs) ) return TRUE; + + *manual_mode = (unsigned char)(buf[0] & 0x08); + *ndim = (unsigned char)((buf[0] & 0x07)); + *pdop = bGetSingle (&buf[1]); + *hdop = bGetSingle (&buf[5]); + *vdop = bGetSingle (&buf[9]); + *tdop = bGetSingle (&buf[13]); + for (islot = 0; islot < (*nsvs); islot++) + sv_prn[islot] = buf[islot + 17]; + return FALSE; +} + +/**/ +short rpt_0x82 (TSIPPKT *rpt, + unsigned char *diff_mode) +/* differential fix mode */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 1) return TRUE; + *diff_mode = buf[0]; + return FALSE; +} + +short rpt_0x83 (TSIPPKT *rpt, + double ECEF_pos[3], + double *clock_bias, + float *time_of_fix) +/* position, ECEF double precision */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 36) return TRUE; + ECEF_pos[0] = bGetDouble (buf); + ECEF_pos[1] = bGetDouble (&buf[8]); + ECEF_pos[2] = bGetDouble (&buf[16]); + *clock_bias = bGetDouble (&buf[24]); + *time_of_fix = bGetSingle (&buf[32]); + return FALSE; +} + +short rpt_0x84 (TSIPPKT *rpt, + double *lat, + double *lon, + double *alt, + double *clock_bias, + float *time_of_fix) +/* position, lat-lon-alt double precision */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 36) return TRUE; + *lat = bGetDouble (buf); + *lon = bGetDouble (&buf[8]); + *alt = bGetDouble (&buf[16]); + *clock_bias = bGetDouble (&buf[24]); + *time_of_fix = bGetSingle (&buf[32]); + return FALSE; +} + +short rpt_Paly0xBB(TSIPPKT *rpt, + TSIP_RCVR_CFG *TsipxBB) +{ + + unsigned char *buf; + buf = rpt->buf; + + /* Palisade is inconsistent with other TSIP, which has a kength of 40 */ + /* if (rpt->len != 40) return TRUE; */ + if (rpt->len != 43) return TRUE; + + TsipxBB->bSubcode = buf[0]; + TsipxBB->operating_mode = buf[1] ; + TsipxBB->dyn_code = buf[3] ; + TsipxBB->elev_mask = bGetSingle (&buf[5]); + TsipxBB->cno_mask = bGetSingle (&buf[9]); + TsipxBB->dop_mask = bGetSingle (&buf[13]); + TsipxBB->dop_switch = bGetSingle (&buf[17]); + return FALSE; +} + +short rpt_0xBC (TSIPPKT *rpt, + unsigned char *port_num, + unsigned char *in_baud, + unsigned char *out_baud, + unsigned char *data_bits, + unsigned char *parity, + unsigned char *stop_bits, + unsigned char *flow_control, + unsigned char *protocols_in, + unsigned char *protocols_out, + unsigned char *reserved) +/* Receiver serial port configuration */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 10) return TRUE; + *port_num = buf[0]; + *in_baud = buf[1]; + *out_baud = buf[2]; + *data_bits = buf[3]; + *parity = buf[4]; + *stop_bits = buf[5]; + *flow_control = buf[6]; + *protocols_in = buf[7]; + *protocols_out = buf[8]; + *reserved = buf[9]; + + return FALSE; +} + +/**** Superpackets ****/ + +short rpt_0x8F0B(TSIPPKT *rpt, + unsigned short *event, + double *tow, + unsigned char *date, + unsigned char *month, + short *year, + unsigned char *dim_mode, + short *utc_offset, + double *bias, + double *drift, + float *bias_unc, + float *dr_unc, + double *lat, + double *lon, + double *alt, + char sv_id[8]) +{ + short local_index; + unsigned char *buf; + + buf = rpt->buf; + if (rpt->len != 74) return TRUE; + *event = bGetShort(&buf[1]); + *tow = bGetDouble(&buf[3]); + *date = buf[11]; + *month = buf[12]; + *year = bGetShort(&buf[13]); + *dim_mode = buf[15]; + *utc_offset = bGetShort(&buf[16]); + *bias = bGetDouble(&buf[18]); + *drift = bGetDouble(&buf[26]); + *bias_unc = bGetSingle(&buf[34]); + *dr_unc = bGetSingle(&buf[38]); + *lat = bGetDouble(&buf[42]); + *lon = bGetDouble(&buf[50]); + *alt = bGetDouble(&buf[58]); + + for (local_index=0; local_index<8; local_index++) sv_id[local_index] = buf[local_index + 66]; + return FALSE; +} + +short rpt_0x8F14 (TSIPPKT *rpt, + short *datum_idx, + double datum_coeffs[5]) +/* datum index and coefficients */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 43) return TRUE; + *datum_idx = bGetShort(&buf[1]); + datum_coeffs[0] = bGetDouble (&buf[3]); + datum_coeffs[1] = bGetDouble (&buf[11]); + datum_coeffs[2] = bGetDouble (&buf[19]); + datum_coeffs[3] = bGetDouble (&buf[27]); + datum_coeffs[4] = bGetDouble (&buf[35]); + return FALSE; +} + + +short rpt_0x8F15 (TSIPPKT *rpt, + short *datum_idx, + double datum_coeffs[5]) +/* datum index and coefficients */ +{ + unsigned char *buf; + buf = rpt->buf; + + if (rpt->len != 43) return TRUE; + *datum_idx = bGetShort(&buf[1]); + datum_coeffs[0] = bGetDouble (&buf[3]); + datum_coeffs[1] = bGetDouble (&buf[11]); + datum_coeffs[2] = bGetDouble (&buf[19]); + datum_coeffs[3] = bGetDouble (&buf[27]); + datum_coeffs[4] = bGetDouble (&buf[35]); + return FALSE; +} + + +#define MAX_LONG (2147483648.) /* 2**31 */ + +short rpt_0x8F20 (TSIPPKT *rpt, + unsigned char *info, + double *lat, + double *lon, + double *alt, + double vel_enu[], + double *time_of_fix, + short *week_num, + unsigned char *nsvs, + unsigned char sv_prn[], + short sv_IODC[], + short *datum_index) +{ + short + isv; + unsigned char + *buf, prnx, iode; + unsigned long + ulongtemp; + long + longtemp; + double + vel_scale; + + buf = rpt->buf; + + if (rpt->len != 56) return TRUE; + + vel_scale = (buf[24]&1)? 0.020 : 0.005; + vel_enu[0] = bGetShort (buf+2)*vel_scale; + vel_enu[1] = bGetShort (buf+4)*vel_scale; + vel_enu[2] = bGetShort (buf+6)*vel_scale; + + *time_of_fix = bGetULong (buf+8)*.001; + + longtemp = bGetLong (buf+12); + *lat = longtemp*(GPS_PI/MAX_LONG); + + ulongtemp = bGetULong (buf+16); + *lon = ulongtemp*(GPS_PI/MAX_LONG); + if (*lon > GPS_PI) *lon -= 2.0*GPS_PI; + + *alt = bGetLong (buf+20)*.001; + /* 25 blank; 29 = UTC */ + (*datum_index) = (short)((short)buf[26]-1); + *info = buf[27]; + *nsvs = buf[28]; + *week_num = bGetShort (&buf[30]); + for (isv = 0; isv < 8; isv++) { + prnx = buf[32+2*isv]; + sv_prn[isv] = (unsigned char)(prnx&0x3F); + iode = buf[33+2*isv]; + sv_IODC[isv] = (short)(iode | ((prnx>>6)<<8)); + } + return FALSE; +} + +short rpt_0x8F41 (TSIPPKT *rpt, + unsigned char *bSearchRange, + unsigned char *bBoardOptions, + unsigned long *iiSerialNumber, + unsigned char *bBuildYear, + unsigned char *bBuildMonth, + unsigned char *bBuildDay, + unsigned char *bBuildHour, + float *fOscOffset, + unsigned short *iTestCodeId) +{ + if(rpt->len != 17) return FALSE; + *bSearchRange = rpt->buf[1]; + *bBoardOptions = rpt->buf[2]; + *iiSerialNumber = bGetLong(&rpt->buf[3]); + *bBuildYear = rpt->buf[7]; + *bBuildMonth = rpt->buf[8]; + *bBuildDay = rpt->buf[9]; + *bBuildHour = rpt->buf[10]; + *fOscOffset = bGetSingle(&rpt->buf[11]); + *iTestCodeId = bGetShort(&rpt->buf[15]); +/* Tsipx8E41Data = *Tsipx8E41; */ + return TRUE; +} + +short rpt_0x8F42 (TSIPPKT *rpt, + unsigned char *bProdOptionsPre, + unsigned char *bProdNumberExt, + unsigned short *iCaseSerialNumberPre, + unsigned long *iiCaseSerialNumber, + unsigned long *iiProdNumber, + unsigned short *iPremiumOptions, + unsigned short *iMachineID, + unsigned short *iKey) +{ + if(rpt->len != 19) return FALSE; + *bProdOptionsPre = rpt->buf[1]; + *bProdNumberExt = rpt->buf[2]; + *iCaseSerialNumberPre = bGetShort(&rpt->buf[3]); + *iiCaseSerialNumber = bGetLong(&rpt->buf[5]); + *iiProdNumber = bGetLong(&rpt->buf[9]); + *iPremiumOptions = bGetShort(&rpt->buf[13]); + *iMachineID = bGetShort(&rpt->buf[15]); + *iKey = bGetShort(&rpt->buf[17]); + return TRUE; +} + +short rpt_0x8F45(TSIPPKT *rpt, + unsigned char *bSegMask) +{ + if(rpt->len != 2) return FALSE; + *bSegMask = rpt->buf[1]; + return TRUE; +} + +short rpt_0x8F4A_16(TSIPPKT *rpt, + unsigned char *pps_enabled, + unsigned char *pps_timebase, + unsigned char *pos_polarity, + double *pps_offset, + float *bias_unc_threshold) +/* Stinger PPS definition */ +{ + unsigned char + *buf; + + buf = rpt->buf; + if (rpt->len != 16) return TRUE; + *pps_enabled = buf[1]; + *pps_timebase = buf[2]; + *pos_polarity = buf[3]; + *pps_offset = bGetDouble(&buf[4]); + *bias_unc_threshold = bGetSingle(&buf[12]); + return FALSE; +} + +short rpt_0x8F4B(TSIPPKT *rpt, + unsigned long *decorr_max) +{ + unsigned char + *buf; + + buf = rpt->buf; + if (rpt->len != 5) return TRUE; + *decorr_max = bGetLong(&buf[1]); + return FALSE; +} + +short rpt_0x8F4D(TSIPPKT *rpt, + unsigned long *event_mask) +{ + unsigned char + *buf; + + buf = rpt->buf; + if (rpt->len != 5) return TRUE; + *event_mask = bGetULong (&buf[1]); + return FALSE; +} + +short rpt_0x8FA5(TSIPPKT *rpt, + unsigned char *spktmask) +{ + unsigned char + *buf; + + buf = rpt->buf; + if (rpt->len != 5) return TRUE; + spktmask[0] = buf[1]; + spktmask[1] = buf[2]; + spktmask[2] = buf[3]; + spktmask[3] = buf[4]; + return FALSE; +} + +short rpt_0x8FAD (TSIPPKT *rpt, + unsigned short *COUNT, + double *FracSec, + unsigned char *Hour, + unsigned char *Minute, + unsigned char *Second, + unsigned char *Day, + unsigned char *Month, + unsigned short *Year, + unsigned char *Status, + unsigned char *Flags) +{ + + if (rpt->len != 22) return TRUE; + + *COUNT = bGetUShort(&rpt->buf[1]); + *FracSec = bGetDouble(&rpt->buf[3]); + *Hour = rpt->buf[11]; + *Minute = rpt->buf[12]; + *Second = rpt->buf[13]; + *Day = rpt->buf[14]; + *Month = rpt->buf[15]; + *Year = bGetUShort(&rpt->buf[16]); + *Status = rpt->buf[18]; + *Flags = rpt->buf[19]; + return FALSE; +} + + +/* + * ************************************************************************* + * + * Trimble Navigation, Ltd. + * OEM Products Development Group + * P.O. Box 3642 + * 645 North Mary Avenue + * Sunnyvale, California 94088-3642 + * + * Corporate Headquarter: + * Telephone: (408) 481-8000 + * Fax: (408) 481-6005 + * + * Technical Support Center: + * Telephone: (800) 767-4822 (U.S. and Canada) + * (408) 481-6940 (outside U.S. and Canada) + * Fax: (408) 481-6020 + * BBS: (408) 481-7800 + * e-mail: trimble_support@trimble.com + * ftp://ftp.trimble.com/pub/sct/embedded/bin + * + * ************************************************************************* + * + * T_REPORT.C consists of a primary function TranslateTSIPReportToText() + * called by main(). + * + * This function takes a character buffer that has been received as a report + * from a TSIP device and interprets it. The character buffer has been + * assembled using tsip_input_proc() in T_PARSER.C. + * + * A large case statement directs processing to one of many mid-level + * functions. The mid-level functions specific to the current report + * code passes the report buffer to the appropriate report decoder + * rpt_0x?? () in T_PARSER.C, which converts the byte stream in rpt.buf + * to data values approporaite for use. + * + * ************************************************************************* + * + */ + + +#define GOOD_PARSE 0 +#define BADID_PARSE 1 +#define BADLEN_PARSE 2 +#define BADDATA_PARSE 3 + +#define B_TSIP 0x02 +#define B_NMEA 0x04 + + +/* pbuf is the pointer to the current location of the text output */ +static char + *pbuf; + +/* keep track of whether the message has been successfully parsed */ +static short + parsed; + + +/* convert time of week into day-hour-minute-second and print */ +char* show_time (float time_of_week) +{ + short days, hours, minutes; + float seconds; + double tow = 0; + static char timestring [80]; + + if (time_of_week == -1.0) + { + sprintf(timestring, " <No time yet> "); + } + else if ((time_of_week >= 604800.0) || (time_of_week < 0.0)) + { + sprintf(timestring, " <Bad time> "); + } + else + { + if (time_of_week < 604799.9) + tow = time_of_week + .00000001; + seconds = (float)fmod(tow, 60.); + minutes = (short) fmod(tow/60., 60.); + hours = (short)fmod(tow / 3600., 24.); + days = (short)(tow / 86400.0); + sprintf(timestring, " %s %02d:%02d:%05.2f ", + dayname[days], hours, minutes, seconds); + } + return timestring; +} + +/**/ +/* 0x3D */ +static void rpt_chan_A_config (TSIPPKT *rpt) +{ + unsigned char + tx_baud_index, rx_baud_index, + char_format_index, stop_bits, + tx_mode_index, rx_mode_index, + databits, parity; + int + i, nbaud; + + /* unload rptbuf */ + if (rpt_0x3D (rpt, + &tx_baud_index, &rx_baud_index, &char_format_index, + &stop_bits, &tx_mode_index, &rx_mode_index)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nChannel A Configuration"); + + nbaud = sizeof(old_baudnum); + + for (i = 0; i < nbaud; ++i) if (tx_baud_index == old_baudnum[i]) break; + pbuf += sprintf(pbuf, "\n Transmit speed: %s at %s", + old_output_ch[tx_mode_index], st_baud_text_app[i]); + + for (i = 0; i < nbaud; ++i) if (rx_baud_index == old_baudnum[i]) break; + pbuf += sprintf(pbuf, "\n Receive speed: %s at %s", + old_input_ch[rx_mode_index], st_baud_text_app[i]); + + databits = (unsigned char)((char_format_index & 0x03) + 5); + + parity = (unsigned char)(char_format_index >> 2); + if (parity > 4) parity = 2; + + pbuf += sprintf(pbuf, "\n Character format (bits/char, parity, stop bits): %d-%s-%d", + databits, old_parity_text[parity], stop_bits); +} + +/**/ +/* 0x40 */ +static void rpt_almanac_data_page (TSIPPKT *rpt) +{ + unsigned char + sv_prn; + short + week_num; + float + t_zc, + eccentricity, + t_oa, + i_0, + OMEGA_dot, + sqrt_A, + OMEGA_0, + omega, + M_0; + + /* unload rptbuf */ + if (rpt_0x40 (rpt, + &sv_prn, &week_num, &t_zc, &eccentricity, &t_oa, + &i_0, &OMEGA_dot, &sqrt_A, &OMEGA_0, &omega, &M_0)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nAlmanac for SV %02d", sv_prn); + pbuf += sprintf(pbuf, "\n Captured:%15.0f %s", + t_zc, show_time (t_zc)); + pbuf += sprintf(pbuf, "\n week:%15d", week_num); + pbuf += sprintf(pbuf, "\n Eccentricity:%15g", eccentricity); + pbuf += sprintf(pbuf, "\n T_oa:%15.0f %s", + t_oa, show_time (t_oa)); + pbuf += sprintf(pbuf, "\n i 0:%15g", i_0); + pbuf += sprintf(pbuf, "\n OMEGA dot:%15g", OMEGA_dot); + pbuf += sprintf(pbuf, "\n sqrt A:%15g", sqrt_A); + pbuf += sprintf(pbuf, "\n OMEGA 0:%15g", OMEGA_0); + pbuf += sprintf(pbuf, "\n omega:%15g", omega); + pbuf += sprintf(pbuf, "\n M 0:%15g", M_0); +} + +/* 0x41 */ +static void rpt_GPS_time (TSIPPKT *rpt) +{ + float + time_of_week, UTC_offset; + short + week_num; + + /* unload rptbuf */ + if (rpt_0x41 (rpt, &time_of_week, &UTC_offset, &week_num)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nGPS time:%s GPS week: %d UTC offset %.1f", + show_time(time_of_week), week_num, UTC_offset); + +} + +/* 0x42 */ +static void rpt_single_ECEF_position (TSIPPKT *rpt) +{ + float + ECEF_pos[3], time_of_fix; + + /* unload rptbuf */ + if (rpt_0x42 (rpt, ECEF_pos, &time_of_fix)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nSXYZ: %15.0f %15.0f %15.0f %s", + ECEF_pos[0], ECEF_pos[1], ECEF_pos[2], + show_time(time_of_fix)); +} + +/* 0x43 */ +static void rpt_single_ECEF_velocity (TSIPPKT *rpt) +{ + + float + ECEF_vel[3], freq_offset, time_of_fix; + + /* unload rptbuf */ + if (rpt_0x43 (rpt, ECEF_vel, &freq_offset, &time_of_fix)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nVelECEF: %11.3f %11.3f %11.3f %12.3f%s", + ECEF_vel[0], ECEF_vel[1], ECEF_vel[2], freq_offset, + show_time(time_of_fix)); +} + +/* 0x45 */ +static void rpt_SW_version (TSIPPKT *rpt) { + unsigned char + major_nav_version, minor_nav_version, + nav_day, nav_month, nav_year, + major_dsp_version, minor_dsp_version, + dsp_day, dsp_month, dsp_year; + + /* unload rptbuf */ + if (rpt_0x45 (rpt, + &major_nav_version, &minor_nav_version, + &nav_day, &nav_month, &nav_year, + &major_dsp_version, &minor_dsp_version, + &dsp_day, &dsp_month, &dsp_year)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, +"\nFW Versions: Nav Proc %2d.%02d %2d/%2d/%2d Sig Proc %2d.%02d %2d/%2d/%2d", + major_nav_version, minor_nav_version, nav_day, nav_month, nav_year, + major_dsp_version, minor_dsp_version, dsp_day, dsp_month, dsp_year); +} + +/* 0x46 */ +static void rpt_rcvr_health (TSIPPKT *rpt) +{ + unsigned char + status1, status2; + static char + *sc_text[] = { + "Doing position fixes", + "Don't have GPS time yet", + "Waiting for almanac collection", + "DOP too high ", + "No satellites available", + "Only 1 satellite available", + "Only 2 satellites available", + "Only 3 satellites available", + "No satellites usable ", + "Only 1 satellite usable", + "Only 2 satellites usable", + "Only 3 satellites usable", + "Chosen satellite unusable"}; + + + /* unload rptbuf */ + if (rpt_0x46 (rpt, &status1, &status2)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nRcvr status1: %s (%02Xh); ", + sc_text[rpt->buf[0]], status1); + + pbuf += sprintf(pbuf, "status2: %s, %s (%02Xh)", + (status2 & 0x01)?"No BBRAM":"BBRAM OK", + (status2 & 0x10)?"No Ant":"Ant OK", + status2); +} + +/* 0x47 */ +static void rpt_SNR_all_SVs (TSIPPKT *rpt) +{ + unsigned char + nsvs, sv_prn[12]; + short + isv; + float + snr[12]; + + /* unload rptbuf */ + if (rpt_0x47 (rpt, &nsvs, sv_prn, snr)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nSNR for satellites: %d", nsvs); + for (isv = 0; isv < nsvs; isv++) + { + pbuf += sprintf(pbuf, "\n SV %02d %6.2f", + sv_prn[isv], snr[isv]); + } +} + +/* 0x48 */ +static void rpt_GPS_system_message (TSIPPKT *rpt) +{ + unsigned char + message[23]; + + /* unload rptbuf */ + if (rpt_0x48 (rpt, message)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nGPS message: %s", message); +} + +/* 0x49 */ +static void rpt_almanac_health_page (TSIPPKT *rpt) +{ + short + iprn; + unsigned char + sv_health [32]; + + /* unload rptbuf */ + if (rpt_0x49 (rpt, sv_health)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nAlmanac health page:"); + for (iprn = 0; iprn < 32; iprn++) + { + if (!(iprn%5)) *pbuf++ = '\n'; + pbuf += sprintf(pbuf, " SV%02d %2X", + (iprn+1) , sv_health[iprn]); + } +} + +/* 0x4A */ +static void rpt_single_lla_position (TSIPPKT *rpt) { + short + lat_deg, lon_deg; + float + lat, lon, + alt, clock_bias, time_of_fix; + double lat_min, lon_min; + unsigned char + north_south, east_west; + + if (rpt_0x4A (rpt, + &lat, &lon, &alt, &clock_bias, &time_of_fix)) + { + parsed = BADLEN_PARSE; + return; + } + + /* convert from radians to degrees */ + lat *= (float)R2D; + north_south = 'N'; + if (lat < 0.0) + { + north_south = 'S'; + lat = -lat; + } + lat_deg = (short)lat; + lat_min = (lat - lat_deg) * 60.0; + + lon *= (float)R2D; + east_west = 'E'; + if (lon < 0.0) + { + east_west = 'W'; + lon = -lon; + } + lon_deg = (short)lon; + lon_min = (lon - lon_deg) * 60.0; + + pbuf += sprintf(pbuf, "\nSLLA: %4d: %06.3f %c%5d:%06.3f %c%10.2f %12.2f%s", + lat_deg, lat_min, north_south, + lon_deg, lon_min, east_west, + alt, clock_bias, + show_time(time_of_fix)); +} + +/* 0x4A */ +static void rpt_ref_alt (TSIPPKT *rpt) { + + float + alt, dummy; + unsigned char + alt_flag; + + if (rpt_0x4A_2 (rpt, + &alt, &dummy, &alt_flag)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nReference Alt: %.1f m; %s", + alt, alt_flag?"ON":"OFF"); +} + +/* 0x4B */ +static void rpt_rcvr_id_and_status (TSIPPKT *rpt) +{ + + unsigned char + machine_id, status3, status4; + + /* unload rptbuf */ + if (rpt_0x4B (rpt, &machine_id, &status3, &status4)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nRcvr Machine ID: %d; Status3 = %s, %s (%02Xh)", + machine_id, + (status3 & 0x02)?"No RTC":"RTC OK", + (status3 & 0x08)?"No Alm":"Alm OK", + status3); +} + +/* 0x4C */ +static void rpt_operating_parameters (TSIPPKT *rpt) +{ + unsigned char + dyn_code; + float + el_mask, snr_mask, dop_mask, dop_switch; + + /* unload rptbuf */ + if (rpt_0x4C (rpt, &dyn_code, &el_mask, + &snr_mask, &dop_mask, &dop_switch)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nOperating Parameters:"); + pbuf += sprintf(pbuf, "\n Dynamics code = %d %s", + dyn_code, dyn_text[dyn_code]); + pbuf += sprintf(pbuf, "\n Elevation mask = %.2fø", el_mask * R2D); + pbuf += sprintf(pbuf, "\n SNR mask = %.2f", snr_mask); + pbuf += sprintf(pbuf, "\n DOP mask = %.2f", dop_mask); + pbuf += sprintf(pbuf, "\n DOP switch = %.2f", dop_switch); +} + +/* 0x4D */ +static void rpt_oscillator_offset (TSIPPKT *rpt) +{ + float + osc_offset; + + /* unload rptbuf */ + if (rpt_0x4D (rpt, &osc_offset)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nOscillator offset: %.2f Hz = %.3f PPM", + osc_offset, osc_offset/1575.42); +} + +/* 0x4E */ +static void rpt_GPS_time_set_response (TSIPPKT *rpt) +{ + + unsigned char + response; + + /* unload rptbuf */ + if (rpt_0x4E (rpt, &response)) + { + parsed = BADLEN_PARSE; + return; + } + + switch (response) + { + case 'Y': + pbuf += sprintf(pbuf, "\nTime set accepted"); + break; + + case 'N': + pbuf += sprintf(pbuf, "\nTime set rejected or not required"); + break; + + default: + parsed = BADDATA_PARSE; + } +} + +/* 0x4F */ +static void rpt_UTC_offset (TSIPPKT *rpt) +{ + double + a0; + float + a1, time_of_data; + short + dt_ls, wn_t, wn_lsf, dn, dt_lsf; + + /* unload rptbuf */ + if (rpt_0x4F (rpt, &a0, &a1, &time_of_data, + &dt_ls, &wn_t, &wn_lsf, &dn, &dt_lsf)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nUTC Correction Data"); + pbuf += sprintf(pbuf, "\n A_0 = %g ", a0); + pbuf += sprintf(pbuf, "\n A_1 = %g ", a1); + pbuf += sprintf(pbuf, "\n delta_t_LS = %d ", dt_ls); + pbuf += sprintf(pbuf, "\n t_ot = %.0f ", time_of_data); + pbuf += sprintf(pbuf, "\n WN_t = %d ", wn_t ); + pbuf += sprintf(pbuf, "\n WN_LSF = %d ", wn_lsf ); + pbuf += sprintf(pbuf, "\n DN = %d ", dn ); + pbuf += sprintf(pbuf, "\n delta_t_LSF = %d ", dt_lsf ); +} + +/**/ +/* 0x54 */ +static void rpt_1SV_bias (TSIPPKT *rpt) +{ + float + clock_bias, freq_offset, time_of_fix; + + /* unload rptbuf */ + if (rpt_0x54 (rpt, &clock_bias, &freq_offset, &time_of_fix)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf (pbuf, "\nTime Fix Clock Bias: %6.2f m Freq Bias: %6.2f m/s%s", + clock_bias, freq_offset, show_time (time_of_fix)); +} + +/* 0x55 */ +static void rpt_io_opt (TSIPPKT *rpt) +{ + unsigned char + pos_code, vel_code, time_code, aux_code; + + /* unload rptbuf */ + if (rpt_0x55 (rpt, + &pos_code, &vel_code, &time_code, &aux_code)) { + parsed = BADLEN_PARSE; + return; + } + /* rptbuf unloaded */ + + pbuf += sprintf(pbuf, "\nI/O Options: %2X %2X %2X %2X", + pos_code, vel_code, time_code, aux_code); + + if (pos_code & 0x01) { + pbuf += sprintf(pbuf, "\n ECEF XYZ position output"); + } + + if (pos_code & 0x02) { + pbuf += sprintf(pbuf, "\n LLA position output"); + } + + pbuf += sprintf(pbuf, (pos_code & 0x04)? + "\n MSL altitude output (Geoid height) ": + "\n WGS-84 altitude output"); + + pbuf += sprintf(pbuf, (pos_code & 0x08)? + "\n MSL altitude input": + "\n WGS-84 altitude input"); + + pbuf += sprintf(pbuf, (pos_code & 0x10)? + "\n Double precision": + "\n Single precision"); + + if (pos_code & 0x20) { + pbuf += sprintf(pbuf, "\n All Enabled Superpackets"); + } + + if (vel_code & 0x01) { + pbuf += sprintf(pbuf, "\n ECEF XYZ velocity output"); + } + + if (vel_code & 0x02) { + pbuf += sprintf(pbuf, "\n ENU velocity output"); + } + + pbuf += sprintf(pbuf, (time_code & 0x01)? + "\n Time tags in UTC": + "\n Time tags in GPS time"); + + if (time_code & 0x02) { + pbuf += sprintf(pbuf, "\n Fixes delayed to integer seconds"); + } + + if (time_code & 0x04) { + pbuf += sprintf(pbuf, "\n Fixes sent only on request"); + } + + if (time_code & 0x08) { + pbuf += sprintf(pbuf, "\n Synchronized measurements"); + } + + if (time_code & 0x10) { + pbuf += sprintf(pbuf, "\n Minimize measurement propagation"); + } + + pbuf += sprintf(pbuf, (time_code & 0x20) ? + "\n PPS output at all times" : + "\n PPS output during fixes"); + + if (aux_code & 0x01) { + pbuf += sprintf(pbuf, "\n Raw measurement output"); + } + + if (aux_code & 0x02) { + pbuf += sprintf(pbuf, "\n Code-phase smoothed before output"); + } + + if (aux_code & 0x04) { + pbuf += sprintf(pbuf, "\n Additional fix status"); + } + + pbuf += sprintf(pbuf, (aux_code & 0x08)? + "\n Signal Strength Output as dBHz" : + "\n Signal Strength Output as AMU"); +} + +/* 0x56 */ +static void rpt_ENU_velocity (TSIPPKT *rpt) +{ + float + vel_ENU[3], freq_offset, time_of_fix; + + /* unload rptbuf */ + if (rpt_0x56 (rpt, vel_ENU, &freq_offset, &time_of_fix)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nVel ENU: %11.3f %11.3f %11.3f %12.3f%s", + vel_ENU[0], vel_ENU[1], vel_ENU[2], freq_offset, + show_time (time_of_fix)); +} + +/* 0x57 */ +static void rpt_last_fix_info (TSIPPKT *rpt) +{ + unsigned char + source_code, diag_code; + short + week_num; + float + time_of_fix; + + /* unload rptbuf */ + if (rpt_0x57 (rpt, &source_code, &diag_code, &week_num, &time_of_fix)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n source code %d; diag code: %2Xh", + source_code, diag_code); + pbuf += sprintf(pbuf, "\n Time of last fix:%s", show_time(time_of_fix)); + pbuf += sprintf(pbuf, "\n Week of last fix: %d", week_num); +} + +/* 0x58 */ +static void rpt_GPS_system_data (TSIPPKT *rpt) +{ + unsigned char + iprn, + op_code, data_type, sv_prn, + data_length, data_packet[250]; + ALM_INFO + *almanac; + ALH_PARMS + *almh; + UTC_INFO + *utc; + ION_INFO + *ionosphere; + EPHEM_CLOCK + *cdata; + EPHEM_ORBIT + *edata; + NAV_INFO + *nav_data; + unsigned char + curr_t_oa; + unsigned short + curr_wn_oa; + static char + *datname[] = + {"", "", "Almanac Orbit", + "Health Page & Ref Time", "Ionosphere", "UTC ", + "Ephemeris"}; + + /* unload rptbuf */ + if (rpt_0x58 (rpt, &op_code, &data_type, &sv_prn, + &data_length, data_packet)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nSystem data [%d]: %s SV%02d", + data_type, datname[data_type], sv_prn); + switch (op_code) + { + case 1: + pbuf += sprintf(pbuf, " Acknowledgment"); + break; + case 2: + pbuf += sprintf(pbuf, " length = %d bytes", data_length); + switch (data_type) { + case 2: + /* Almanac */ + if (sv_prn == 0 || sv_prn > 32) { + pbuf += sprintf(pbuf, " Binary PRN invalid"); + return; + } + almanac = (ALM_INFO*)data_packet; + pbuf += sprintf(pbuf, "\n t_oa_raw = % -12d SV_hlth = % -12d ", + almanac->t_oa_raw , almanac->SV_health ); + pbuf += sprintf(pbuf, "\n e = % -12g t_oa = % -12g ", + almanac->e , almanac->t_oa ); + pbuf += sprintf(pbuf, "\n i_0 = % -12g OMEGADOT = % -12g ", + almanac->i_0 , almanac->OMEGADOT ); + pbuf += sprintf(pbuf, "\n sqrt_A = % -12g OMEGA_0 = % -12g ", + almanac->sqrt_A , almanac->OMEGA_0 ); + pbuf += sprintf(pbuf, "\n omega = % -12g M_0 = % -12g ", + almanac->omega , almanac->M_0 ); + pbuf += sprintf(pbuf, "\n a_f0 = % -12g a_f1 = % -12g ", + almanac->a_f0 , almanac->a_f1 ); + pbuf += sprintf(pbuf, "\n Axis = % -12g n = % -12g ", + almanac->Axis , almanac->n ); + pbuf += sprintf(pbuf, "\n OMEGA_n = % -12g ODOT_n = % -12g ", + almanac->OMEGA_n , almanac->ODOT_n ); + pbuf += sprintf(pbuf, "\n t_zc = % -12g weeknum = % -12d ", + almanac->t_zc , almanac->weeknum ); + pbuf += sprintf(pbuf, "\n wn_oa = % -12d", almanac->wn_oa ); + break; + + case 3: + /* Almanac health page */ + almh = (ALH_PARMS*)data_packet; + pbuf += sprintf(pbuf, "\n t_oa = %d, wn_oa&0xFF = %d ", + almh->t_oa, almh->WN_a); + pbuf += sprintf(pbuf, "\nAlmanac health page:"); + for (iprn = 0; iprn < 32; iprn++) { + if (!(iprn%5)) *pbuf++ = '\n'; + pbuf += sprintf(pbuf, " SV%02d %2X", + (iprn+1) , almh->SV_health[iprn]); + } + curr_t_oa = data_packet[34]; + curr_wn_oa = (unsigned short)((data_packet[35]<<8) + data_packet[36]); + pbuf += sprintf(pbuf, "\n current t_oa = %d, wn_oa = %d ", + curr_t_oa, curr_wn_oa); + break; + + case 4: + /* Ionosphere */ + ionosphere = (ION_INFO*)data_packet; + pbuf += sprintf(pbuf, "\n alpha_0 = % -12g alpha_1 = % -12g ", + ionosphere->alpha_0, ionosphere->alpha_1); + pbuf += sprintf(pbuf, "\n alpha_2 = % -12g alpha_3 = % -12g ", + ionosphere->alpha_2, ionosphere->alpha_3); + pbuf += sprintf(pbuf, "\n beta_0 = % -12g beta_1 = % -12g ", + ionosphere->beta_0, ionosphere->beta_1); + pbuf += sprintf(pbuf, "\n beta_2 = % -12g beta_3 = % -12g ", + ionosphere->beta_2, ionosphere->beta_3); + break; + + case 5: + /* UTC */ + utc = (UTC_INFO*)data_packet; + pbuf += sprintf(pbuf, "\n A_0 = %g ", utc->A_0); + pbuf += sprintf(pbuf, "\n A_1 = %g ", utc->A_1); + pbuf += sprintf(pbuf, "\n delta_t_LS = %d ", utc->delta_t_LS); + pbuf += sprintf(pbuf, "\n t_ot = %.0f ", utc->t_ot ); + pbuf += sprintf(pbuf, "\n WN_t = %d ", utc->WN_t ); + pbuf += sprintf(pbuf, "\n WN_LSF = %d ", utc->WN_LSF ); + pbuf += sprintf(pbuf, "\n DN = %d ", utc->DN ); + pbuf += sprintf(pbuf, "\n delta_t_LSF = %d ", utc->delta_t_LSF ); + break; + + case 6: /* Ephemeris */ + if (sv_prn == 0 || sv_prn > 32) { + pbuf += sprintf(pbuf, " Binary PRN invalid"); + return; + } + nav_data = (NAV_INFO*)data_packet; + + pbuf += sprintf(pbuf, "\n SV_PRN = % -12d . t_ephem = % -12g . ", + nav_data->sv_number , nav_data->t_ephem ); + cdata = &(nav_data->ephclk); + pbuf += sprintf(pbuf, + "\n weeknum = % -12d . codeL2 = % -12d . L2Pdata = % -12d", + cdata->weeknum , cdata->codeL2 , cdata->L2Pdata ); + pbuf += sprintf(pbuf, + "\n SVacc_raw = % -12d .SV_health = % -12d . IODC = % -12d", + cdata->SVacc_raw, cdata->SV_health, cdata->IODC ); + pbuf += sprintf(pbuf, + "\n T_GD = % -12g . t_oc = % -12g . a_f2 = % -12g", + cdata->T_GD, cdata->t_oc, cdata->a_f2 ); + pbuf += sprintf(pbuf, + "\n a_f1 = % -12g . a_f0 = % -12g . SVacc = % -12g", + cdata->a_f1, cdata->a_f0, cdata->SVacc ); + edata = &(nav_data->ephorb); + pbuf += sprintf(pbuf, + "\n IODE = % -12d .fit_intvl = % -12d . C_rs = % -12g", + edata->IODE, edata->fit_interval, edata->C_rs ); + pbuf += sprintf(pbuf, + "\n delta_n = % -12g . M_0 = % -12g . C_uc = % -12g", + edata->delta_n, edata->M_0, edata->C_uc ); + pbuf += sprintf(pbuf, + "\n ecc = % -12g . C_us = % -12g . sqrt_A = % -12g", + edata->e, edata->C_us, edata->sqrt_A ); + pbuf += sprintf(pbuf, + "\n t_oe = % -12g . C_ic = % -12g . OMEGA_0 = % -12g", + edata->t_oe, edata->C_ic, edata->OMEGA_0 ); + pbuf += sprintf(pbuf, + "\n C_is = % -12g . i_0 = % -12g . C_rc = % -12g", + edata->C_is, edata->i_0, edata->C_rc ); + pbuf += sprintf(pbuf, + "\n omega = % -12g . OMEGADOT = % -12g . IDOT = % -12g", + edata->omega, edata->OMEGADOT, edata->IDOT ); + pbuf += sprintf(pbuf, + "\n Axis = % -12g . n = % -12g . r1me2 = % -12g", + edata->Axis, edata->n, edata->r1me2 ); + pbuf += sprintf(pbuf, + "\n OMEGA_n = % -12g . ODOT_n = % -12g", + edata->OMEGA_n, edata->ODOT_n ); + break; + } + } +} + + +/* 0x59: */ +static void rpt_SVs_enabled (TSIPPKT *rpt) +{ + unsigned char + numsvs, + code_type, + status_code[32]; + short + iprn; + + /* unload rptbuf */ + if (rpt_0x59 (rpt, &code_type, status_code)) + { + parsed = BADLEN_PARSE; + return; + } + switch (code_type) + { + case 3: pbuf += sprintf(pbuf, "\nSVs Disabled:\n"); break; + case 6: pbuf += sprintf(pbuf, "\nSVs with Health Ignored:\n"); break; + default: return; + } + numsvs = 0; + for (iprn=0; iprn<32; iprn++) + { + if (status_code[iprn]) + { + pbuf += sprintf(pbuf, " %02d", iprn+1); + numsvs++; + } + } + if (numsvs == 0) pbuf += sprintf(pbuf, "None"); +} + + +/* 0x5A */ +static void rpt_raw_msmt (TSIPPKT *rpt) +{ + unsigned char + sv_prn; + float + sample_length, signal_level, code_phase, Doppler; + double + time_of_fix; + + /* unload rptbuf */ + if (rpt_0x5A (rpt, &sv_prn, &sample_length, &signal_level, + &code_phase, &Doppler, &time_of_fix)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n %02d %5.0f %7.1f %10.2f %10.2f %12.3f %s", + sv_prn, sample_length, signal_level, code_phase, Doppler, time_of_fix, + show_time ((float)time_of_fix)); +} + +/* 0x5B */ +static void rpt_SV_ephemeris_status (TSIPPKT *rpt) +{ + unsigned char + sv_prn, sv_health, sv_iode, fit_interval_flag; + float + time_of_collection, time_of_eph, sv_accy; + + /* unload rptbuf */ + if (rpt_0x5B (rpt, &sv_prn, &sv_health, &sv_iode, &fit_interval_flag, + &time_of_collection, &time_of_eph, &sv_accy)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n SV%02d %s %2Xh %2Xh ", + sv_prn, show_time (time_of_collection), sv_health, sv_iode); + /* note: cannot use show_time twice in same call */ + pbuf += sprintf(pbuf, "%s %1d %4.1f", + show_time (time_of_eph), fit_interval_flag, sv_accy); +} + +/* 0x5C */ +static void rpt_SV_tracking_status (TSIPPKT *rpt) +{ + unsigned char + sv_prn, chan, slot, acq_flag, eph_flag, + old_msmt_flag, integer_msec_flag, bad_data_flag, + data_collect_flag; + float + signal_level, time_of_last_msmt, + elev, azim; + + /* unload rptbuf */ + if (rpt_0x5C (rpt, + &sv_prn, &slot, &chan, &acq_flag, &eph_flag, + &signal_level, &time_of_last_msmt, &elev, &azim, + &old_msmt_flag, &integer_msec_flag, &bad_data_flag, + &data_collect_flag)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, +"\n SV%2d %1d %1d %1d %4.1f %s %5.1f %5.1f", + sv_prn, chan, + acq_flag, eph_flag, signal_level, + show_time(time_of_last_msmt), + elev*R2D, azim*R2D); +} + +/**/ +/* 0x6D */ +static void rpt_allSV_selection (TSIPPKT *rpt) +{ + unsigned char + manual_mode, nsvs, sv_prn[8], ndim; + short + islot; + float + pdop, hdop, vdop, tdop; + + /* unload rptbuf */ + if (rpt_0x6D (rpt, + &manual_mode, &nsvs, &ndim, sv_prn, + &pdop, &hdop, &vdop, &tdop)) + { + parsed = BADLEN_PARSE; + return; + } + + switch (ndim) + { + case 0: + pbuf += sprintf(pbuf, "\nMode: Searching, %d-SV:", nsvs); + break; + case 1: + pbuf += sprintf(pbuf, "\nMode: One-SV Timing:"); + break; + case 3: case 4: + pbuf += sprintf(pbuf, "\nMode: %c-%dD, %d-SV:", + manual_mode ? 'M' : 'A', ndim - 1, nsvs); + break; + case 5: + pbuf += sprintf(pbuf, "\nMode: Timing, %d-SV:", nsvs); + break; + default: + pbuf += sprintf(pbuf, "\nMode: Unknown = %d:", ndim); + break; + } + + for (islot = 0; islot < nsvs; islot++) + { + if (sv_prn[islot]) pbuf += sprintf(pbuf, " %02d", sv_prn[islot]); + } + if (ndim == 3 || ndim == 4) + { + pbuf += sprintf(pbuf, "; DOPs: P %.1f H %.1f V %.1f T %.1f", + pdop, hdop, vdop, tdop); + } +} + +/**/ +/* 0x82 */ +static void rpt_DGPS_position_mode (TSIPPKT *rpt) +{ + unsigned char + diff_mode; + + /* unload rptbuf */ + if (rpt_0x82 (rpt, &diff_mode)) { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nFix is%s DGPS-corrected (%s mode) (%d)", + (diff_mode&1) ? "" : " not", + (diff_mode&2) ? "auto" : "manual", + diff_mode); +} + +/* 0x83 */ +static void rpt_double_ECEF_position (TSIPPKT *rpt) +{ + + double + ECEF_pos[3], clock_bias; + float + time_of_fix; + + /* unload rptbuf */ + if (rpt_0x83 (rpt, ECEF_pos, &clock_bias, &time_of_fix)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nDXYZ:%12.2f %13.2f %13.2f %12.2f%s", + ECEF_pos[0], ECEF_pos[1], ECEF_pos[2], clock_bias, + show_time(time_of_fix)); +} + +/* 0x84 */ +static void rpt_double_lla_position (TSIPPKT *rpt) +{ + short + lat_deg, lon_deg; + double + lat, lon, lat_min, lon_min, + alt, clock_bias; + float + time_of_fix; + unsigned char + north_south, east_west; + + /* unload rptbuf */ + if (rpt_0x84 (rpt, + &lat, &lon, &alt, &clock_bias, &time_of_fix)) + { + parsed = BADLEN_PARSE; + return; + } + + lat *= R2D; + lon *= R2D; + if (lat < 0.0) { + north_south = 'S'; + lat = -lat; + } else { + north_south = 'N'; + } + lat_deg = (short)lat; + lat_min = (lat - lat_deg) * 60.0; + + if (lon < 0.0) { + east_west = 'W'; + lon = -lon; + } else { + east_west = 'E'; + } + lon_deg = (short)lon; + lon_min = (lon - lon_deg) * 60.0; + pbuf += sprintf(pbuf, "\nDLLA: %2d:%08.5f %c; %3d:%08.5f %c; %10.2f %12.2f%s", + lat_deg, lat_min, north_south, + lon_deg, lon_min, east_west, + alt, clock_bias, + show_time(time_of_fix)); +} + +/* 0xBB */ +static void rpt_complete_rcvr_config (TSIPPKT *rpt) +{ + TSIP_RCVR_CFG TsipxBB ; + /* unload rptbuf */ + if (rpt_Paly0xBB (rpt, &TsipxBB)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n operating mode: %s", + NavModeText0xBB[TsipxBB.operating_mode]); + pbuf += sprintf(pbuf, "\n dynamics: %s", + dyn_text[TsipxBB.dyn_code]); + pbuf += sprintf(pbuf, "\n elev angle mask: %g deg", + TsipxBB.elev_mask * R2D); + pbuf += sprintf(pbuf, "\n SNR mask: %g AMU", + TsipxBB.cno_mask); + pbuf += sprintf(pbuf, "\n DOP mask: %g", + TsipxBB.dop_mask); + pbuf += sprintf(pbuf, "\n DOP switch: %g", + TsipxBB.dop_switch); + return ; +} + +/* 0xBC */ +static void rpt_rcvr_serial_port_config (TSIPPKT *rpt) +{ + unsigned char + port_num, in_baud, out_baud, data_bits, parity, stop_bits, flow_control, + protocols_in, protocols_out, reserved; + unsigned char known; + + /* unload rptbuf */ + if (rpt_0xBC (rpt, &port_num, &in_baud, &out_baud, &data_bits, &parity, + &stop_bits, &flow_control, &protocols_in, &protocols_out, &reserved)) { + parsed = BADLEN_PARSE; + return; + } + /* rptbuf unloaded */ + + pbuf += sprintf(pbuf, "\n RECEIVER serial port %s config:", + rcvr_port_text[port_num]); + + pbuf += sprintf(pbuf, "\n I/O Baud %s/%s, %d - %s - %d", + st_baud_text_app[in_baud], + st_baud_text_app[out_baud], + data_bits+5, + parity_text[parity], + stop_bits=1); + pbuf += sprintf(pbuf, "\n Input protocols: "); + known = FALSE; + if (protocols_in&B_TSIP) + { + pbuf += sprintf(pbuf, "%s ", protocols_in_text[1]); + known = TRUE; + } + if (known == FALSE) pbuf += sprintf(pbuf, "No known"); + + pbuf += sprintf(pbuf, "\n Output protocols: "); + known = FALSE; + if (protocols_out&B_TSIP) + { + pbuf += sprintf(pbuf, "%s ", protocols_out_text[1]); + known = TRUE; + } + if (protocols_out&B_NMEA) + { + pbuf += sprintf(pbuf, "%s ", protocols_out_text[2]); + known = TRUE; + } + if (known == FALSE) pbuf += sprintf(pbuf, "No known"); + reserved = reserved; + + } + +/* 0x8F */ +/* 8F0B */ +static void rpt_8F0B(TSIPPKT *rpt) +{ + const char + *oprtng_dim[7] = { + "horizontal (2-D)", + "full position (3-D)", + "single satellite (0-D)", + "automatic", + "N/A", + "N/A", + "overdetermined clock"}; + char + sv_id[8]; + unsigned char + month, + date, + dim_mode, + north_south, + east_west; + unsigned short + event; + short + utc_offset, + year, + local_index; + short + lat_deg, + lon_deg; + float + bias_unc, + dr_unc; + double + tow, + bias, + drift, + lat, + lon, + alt, + lat_min, + lon_min; + int + numfix, + numnotfix; + + if (rpt_0x8F0B(rpt, + &event, + &tow, + &date, + &month, + &year, + &dim_mode, + &utc_offset, + &bias, + &drift, + &bias_unc, + &dr_unc, + &lat, + &lon, + &alt, + sv_id)) + { + parsed = BADLEN_PARSE; + return; + } + + if (event == 0) + { + pbuf += sprintf(pbuf, "\nNew partial+full meas"); + } + else + { + pbuf += sprintf(pbuf, "\nEvent count: %5d", event); + } + + pbuf += sprintf(pbuf, "\nGPS time : %s %2d/%2d/%2d (DMY)", + show_time(tow), date, month, year); + pbuf += sprintf(pbuf, "\nMode : %s", oprtng_dim[dim_mode]); + pbuf += sprintf(pbuf, "\nUTC offset: %2d", utc_offset); + pbuf += sprintf(pbuf, "\nClock Bias: %6.2f m", bias); + pbuf += sprintf(pbuf, "\nFreq bias : %6.2f m/s", drift); + pbuf += sprintf(pbuf, "\nBias unc : %6.2f m", bias_unc); + pbuf += sprintf(pbuf, "\nFreq unc : %6.2f m/s", dr_unc); + + lat *= R2D; /* convert from radians to degrees */ + lon *= R2D; + if (lat < 0.0) + { + north_south = 'S'; + lat = -lat; + } + else + { + north_south = 'N'; + } + + lat_deg = (short)lat; + lat_min = (lat - lat_deg) * 60.0; + if (lon < 0.0) + { + east_west = 'W'; + lon = -lon; + } + else + { + east_west = 'E'; + } + + lon_deg = (short)lon; + lon_min = (lon - lon_deg) * 60.0; + pbuf += sprintf(pbuf, "\nPosition :"); + pbuf += sprintf(pbuf, " %4d %6.3f %c", lat_deg, lat_min, north_south); + pbuf += sprintf(pbuf, " %5d %6.3f %c", lon_deg, lon_min, east_west); + pbuf += sprintf(pbuf, " %10.2f", alt); + + numfix = numnotfix = 0; + for (local_index=0; local_index<8; local_index++) + { + if (sv_id[local_index] < 0) numnotfix++; + if (sv_id[local_index] > 0) numfix++; + } + if (numfix > 0) + { + pbuf += sprintf(pbuf, "\nSVs used in fix : "); + for (local_index=0; local_index<8; local_index++) + { + if (sv_id[local_index] > 0) + { + pbuf += sprintf(pbuf, "%2d ", sv_id[local_index]); + } + } + } + if (numnotfix > 0) + { + pbuf += sprintf(pbuf, "\nOther SVs tracked: "); + for (local_index=0; local_index<8; local_index++) + { + if (sv_id[local_index] < 0) + { + pbuf += sprintf(pbuf, "%2d ", sv_id[local_index]); + } + } + } +} + +/* 0x8F14 */ +static void rpt_8F14 (TSIPPKT *rpt) +/* Datum parameters */ +{ + double + datum_coeffs[5]; + short + datum_idx; + + /* unload rptbuf */ + if (rpt_0x8F14 (rpt, &datum_idx, datum_coeffs)) + { + parsed = BADLEN_PARSE; + return; + } + + if (datum_idx == -1) + { + pbuf += sprintf(pbuf, "\nUser-Entered Datum:"); + pbuf += sprintf(pbuf, "\n dx = %6.1f", datum_coeffs[0]); + pbuf += sprintf(pbuf, "\n dy = %6.1f", datum_coeffs[1]); + pbuf += sprintf(pbuf, "\n dz = %6.1f", datum_coeffs[2]); + pbuf += sprintf(pbuf, "\n a-axis = %10.3f", datum_coeffs[3]); + pbuf += sprintf(pbuf, "\n e-squared = %16.14f", datum_coeffs[4]); + } + else if (datum_idx == 0) + { + pbuf += sprintf(pbuf, "\nWGS-84 datum, Index 0 "); + } + else + { + pbuf += sprintf(pbuf, "\nStandard Datum, Index %3d ", datum_idx); + } +} + +/* 0x8F15 */ +static void rpt_8F15 (TSIPPKT *rpt) +/* Datum parameters */ +{ + double + datum_coeffs[5]; + short + datum_idx; + + /* unload rptbuf */ + if (rpt_0x8F15 (rpt, &datum_idx, datum_coeffs)) { + parsed = BADLEN_PARSE; + return; + } + + if (datum_idx == -1) + { + pbuf += sprintf(pbuf, "\nUser-Entered Datum:"); + pbuf += sprintf(pbuf, "\n dx = %6.1f", datum_coeffs[0]); + pbuf += sprintf(pbuf, "\n dy = %6.1f", datum_coeffs[1]); + pbuf += sprintf(pbuf, "\n dz = %6.1f", datum_coeffs[2]); + pbuf += sprintf(pbuf, "\n a-axis = %10.3f", datum_coeffs[3]); + pbuf += sprintf(pbuf, "\n e-squared = %16.14f", datum_coeffs[4]); + } + else if (datum_idx == 0) + { + pbuf += sprintf(pbuf, "\nWGS-84 datum, Index 0 "); + } + else + { + pbuf += sprintf(pbuf, "\nStandard Datum, Index %3d ", datum_idx); + } +} + +/* 0x8F20 */ +#define INFO_DGPS 0x02 +#define INFO_2D 0x04 +#define INFO_ALTSET 0x08 +#define INFO_FILTERED 0x10 +static void rpt_8F20 (TSIPPKT *rpt) +{ + unsigned char + info, nsvs, sv_prn[32]; + short + week_num, datum_index, sv_IODC[32]; + double + lat, lon, alt, time_of_fix; + double + londeg, latdeg, vel[3]; + short + isv; + char + datum_string[20]; + + /* unload rptbuf */ + if (rpt_0x8F20 (rpt, + &info, &lat, &lon, &alt, vel, + &time_of_fix, + &week_num, &nsvs, sv_prn, sv_IODC, &datum_index)) + { + parsed = BADLEN_PARSE; + return; + } + pbuf += sprintf(pbuf, + "\nFix at: %04d:%3s:%02d:%02d:%06.3f GPS (=UTC+%2ds) FixType: %s%s%s", + week_num, + dayname[(short)(time_of_fix/86400.0)], + (short)fmod(time_of_fix/3600., 24.), + (short)fmod(time_of_fix/60., 60.), + fmod(time_of_fix, 60.), + (char)rpt->buf[29], /* UTC offset */ + (info & INFO_DGPS)?"Diff":"", + (info & INFO_2D)?"2D":"3D", + (info & INFO_FILTERED)?"-Filtrd":""); + + if (datum_index > 0) + { + sprintf(datum_string, "Datum%3d", datum_index); + } + else if (datum_index) + { + sprintf(datum_string, "Unknown "); + } + else + { + sprintf(datum_string, "WGS-84"); + } + + /* convert from radians to degrees */ + latdeg = R2D * fabs(lat); + londeg = R2D * fabs(lon); + pbuf += sprintf(pbuf, + "\n Pos: %4d:%09.6f %c %5d:%09.6f %c %10.2f m HAE (%s)", + (short)latdeg, fmod (latdeg, 1.)*60.0, + (lat<0.0)?'S':'N', + (short)londeg, fmod (londeg, 1.)*60.0, + (lon<0.0)?'W':'E', + alt, + datum_string); + pbuf += sprintf(pbuf, + "\n Vel: %9.3f E %9.3f N %9.3f U (m/sec)", + vel[0], vel[1], vel[2]); + + pbuf += sprintf(pbuf, + "\n SVs: "); + for (isv = 0; isv < nsvs; isv++) { + pbuf += sprintf(pbuf, " %02d", sv_prn[isv]); + } + pbuf += sprintf(pbuf, " (IODEs:"); + for (isv = 0; isv < nsvs; isv++) { + pbuf += sprintf(pbuf, " %02X", sv_IODC[isv]&0xFF); + } + pbuf += sprintf(pbuf, ")"); +} + +/* 0x8F41 */ +static void rpt_8F41(TSIPPKT *rpt) +{ + unsigned char + bSearchRange, + bBoardOptions, + bBuildYear, + bBuildMonth, + bBuildDay, + bBuildHour; + float + fOscOffset; + unsigned short + iTestCodeId; + unsigned long + iiSerialNumber; + + if (!rpt_0x8F41(rpt, + &bSearchRange, + &bBoardOptions, + &iiSerialNumber, + &bBuildYear, + &bBuildMonth, + &bBuildDay, + &bBuildHour, + &fOscOffset, + &iTestCodeId)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n search range: %d", + bSearchRange); + pbuf += sprintf(pbuf, "\n board options: %d", + bBoardOptions); + pbuf += sprintf(pbuf, "\n board serial #: %ld", + iiSerialNumber); + pbuf += sprintf(pbuf, "\n build date/hour: %02d/%02d/%02d %02d:00", + bBuildDay, bBuildMonth, bBuildYear, bBuildHour); + pbuf += sprintf(pbuf, "\n osc offset: %.3f PPM (%.0f Hz)", + fOscOffset/1575.42, fOscOffset); + pbuf += sprintf(pbuf, "\n test code: %d", + iTestCodeId); +} + +/* 0x8F42 */ +static void rpt_8F42(TSIPPKT *rpt) +{ + unsigned char + bProdOptionsPre, + bProdNumberExt; + unsigned short + iCaseSerialNumberPre, + iPremiumOptions, + iMachineID, + iKey; + unsigned long + iiCaseSerialNumber, + iiProdNumber; + + if (!rpt_0x8F42(rpt, + &bProdOptionsPre, + &bProdNumberExt, + &iCaseSerialNumberPre, + &iiCaseSerialNumber, + &iiProdNumber, + &iPremiumOptions, + &iMachineID, + &iKey)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nProduct ID 8F42"); + pbuf += sprintf(pbuf, "\n extension: %d", bProdNumberExt); + pbuf += sprintf(pbuf, "\n case serial # prefix: %d", iCaseSerialNumberPre); + pbuf += sprintf(pbuf, "\n case serial #: %ld", iiCaseSerialNumber); + pbuf += sprintf(pbuf, "\n prod. #: %ld", iiProdNumber); + pbuf += sprintf(pbuf, "\n premium options: %Xh", iPremiumOptions); + pbuf += sprintf(pbuf, "\n machine ID: %d", iMachineID); + pbuf += sprintf(pbuf, "\n key: %Xh", iKey); +} + +/* 0x8F45 */ +static void rpt_8F45(TSIPPKT *rpt) +{ + unsigned char bSegMask; + + if (!rpt_0x8F45(rpt, + &bSegMask)) + { + parsed = BADLEN_PARSE; + return; + } + pbuf += sprintf(pbuf, "\nCleared Segment Mask: %Xh", bSegMask); +} + +static void rpt_8F4A(TSIPPKT *rpt) +/* Stinger PPS def */ +{ + unsigned char + pps_enabled, + pps_timebase, + pps_polarity; + float + bias_unc_threshold; + double + pps_offset; + + if (rpt_0x8F4A_16 (rpt, + &pps_enabled, + &pps_timebase, + &pps_polarity, + &pps_offset, + &bias_unc_threshold)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nPPS is %s", pps_enabled?"enabled":"disabled"); + pbuf += sprintf(pbuf, "\n timebase: %s", PPSTimeBaseText[pps_timebase]); + pbuf += sprintf(pbuf, "\n polarity: %s", PPSPolarityText[pps_polarity]); + pbuf += sprintf(pbuf, "\n offset: %.1f ns, ", pps_offset*1.e9); + pbuf += sprintf(pbuf, "\n biasunc: %.1f ns", bias_unc_threshold/GPS_C*1.e9); +} + +static void rpt_8F4B(TSIPPKT *rpt) +/* fast-SA decorrolation time for self-survey */ +{ + unsigned long + decorr_max; + + if (rpt_0x8F4B(rpt, &decorr_max)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, + "\nMax # of position fixes for self-survey : %ld", + decorr_max); +} + +static void rpt_8F4D(TSIPPKT *rpt) +{ + static char + *linestart; + unsigned long + OutputMask; + static unsigned long + MaskBit[] = { + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, + 0x00000100L, 0x00000800L, 0x00001000L, + 0x40000000L, 0x80000000L}; + int + ichoice, + numchoices; + + if (rpt_0x8F4D(rpt, &OutputMask)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nAuto-Report Mask: %02X %02X %02X %02X", + (unsigned char)(OutputMask>>24), + (unsigned char)(OutputMask>>16), + (unsigned char)(OutputMask>>8), + (unsigned char)OutputMask); + + numchoices = sizeof(MaskText)/sizeof(char*); + pbuf += sprintf(pbuf, "\nAuto-Reports scheduled for Output:"); + linestart = pbuf; + for (ichoice=0; ichoice<numchoices; ichoice++) + { + if (OutputMask&MaskBit[ichoice]) + { + pbuf += sprintf(pbuf, "%s %s", + (pbuf==linestart)?"\n ":",", + MaskText[ichoice]); + if (pbuf-linestart > 60) linestart = pbuf; + } + } + + pbuf += sprintf(pbuf, "\nAuto-Reports NOT scheduled for Output:"); + linestart = pbuf; + for (ichoice=0; ichoice<numchoices; ichoice++) + { + if (OutputMask&MaskBit[ichoice]) continue; + pbuf += sprintf(pbuf, "%s %s", + (pbuf==linestart)?"\n ":",", + MaskText[ichoice]); + if (pbuf-linestart > 60) linestart = pbuf; + } +} + +static void rpt_8FA5(TSIPPKT *rpt) +{ + unsigned char + spktmask[4]; + + if (rpt_0x8FA5(rpt, spktmask)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\nSuperpacket auto-output mask: %02X %02X %02X %02X", + spktmask[0], spktmask[1], spktmask[2], spktmask[3]); + + if (spktmask[0]&0x01) pbuf+= sprintf (pbuf, "\n PPS 8F-0B"); + if (spktmask[0]&0x02) pbuf+= sprintf (pbuf, "\n Event 8F-0B"); + if (spktmask[0]&0x10) pbuf+= sprintf (pbuf, "\n PPS 8F-AD"); + if (spktmask[0]&0x20) pbuf+= sprintf (pbuf, "\n Event 8F-AD"); + if (spktmask[2]&0x01) pbuf+= sprintf (pbuf, "\n ppos Fix 8F-20"); +} + +static void rpt_8FAD (TSIPPKT *rpt) +{ + unsigned short + Count, + Year; + double + FracSec; + unsigned char + Hour, + Minute, + Second, + Day, + Month, + Status, + Flags; + static char* Status8FADText[] = { + "CODE_DOING_FIXES", + "CODE_GOOD_1_SV", + "CODE_APPX_1SV", + "CODE_NEED_TIME", + "CODE_NEED_INITIALIZATION", + "CODE_PDOP_HIGH", + "CODE_BAD_1SV", + "CODE_0SVS", + "CODE_1SV", + "CODE_2SVS", + "CODE_3SVS", + "CODE_NO_INTEGRITY", + "CODE_DCORR_GEN", + "CODE_OVERDET_CLK", + "Invalid Status"}, + *LeapStatusText[] = { + " UTC Avail", " ", " ", " ", + " Scheduled", " Pending", " Warning", " In Progress"}; + int i; + + if (rpt_0x8FAD (rpt, + &Count, + &FracSec, + &Hour, + &Minute, + &Second, + &Day, + &Month, + &Year, + &Status, + &Flags)) + { + parsed = BADLEN_PARSE; + return; + } + + pbuf += sprintf(pbuf, "\n8FAD Count: %d Status: %s", + Count, Status8FADText[Status]); + + pbuf += sprintf(pbuf, "\n Leap Flags:"); + if (Flags) + { + for (i=0; i<8; i++) + { + if (Flags&(1<<i)) pbuf += sprintf(pbuf, LeapStatusText[i]); + } + } + else + { + pbuf += sprintf(pbuf, " UTC info not available"); + } + + pbuf += sprintf(pbuf, "\n %02d/%02d/%04d (DMY) %02d:%02d:%02d.%09ld UTC", + Day, Month, Year, Hour, Minute, Second, (long)(FracSec*1.e9)); +} + + +int print_msg_table_header (int rptcode, char *HdrStr, int force) +{ + /* force header is to help auto-output function */ + /* last_rptcode is to determine whether to print a header */ + /* for the first occurrence of a series of reports */ + static int + last_rptcode = 0; + int + numchars; + + numchars = 0; + if (force || rptcode!=last_rptcode) + { + /* supply a header in console output */ + switch (rptcode) + { + case 0x5A: + numchars = sprintf(HdrStr, "\nRaw Measurement Data"); + numchars += sprintf(HdrStr+numchars, + "\n SV Sample SNR Code Phase Doppler Seconds Time of Meas"); + break; + + case 0x5B: + numchars = sprintf(HdrStr, "\nEphemeris Status"); + numchars += sprintf(HdrStr+numchars, + "\n SV Time collected Health IODE t oe Fit URA"); + break; + + case 0x5C: + numchars = sprintf(HdrStr, "\nTracking Info"); + numchars += sprintf(HdrStr+numchars, + "\n SV C Acq Eph SNR Time of Meas Elev Azim "); + break; + + } + } + last_rptcode = rptcode; + return (short)numchars; +} + +static void unknown_rpt (TSIPPKT *rpt) +{ + int i; + + /* app-specific rpt packets */ + if (parsed == BADLEN_PARSE) + { + pbuf += sprintf(pbuf, "\nTSIP report packet ID %2Xh, length %d: Bad length", + rpt->code, rpt->len); + } + if (parsed == BADID_PARSE) + { + pbuf += sprintf(pbuf, + "\nTSIP report packet ID %2Xh, length %d: translation not supported", + rpt->code, rpt->len); + } + + if (parsed == BADDATA_PARSE) + { + pbuf += sprintf(pbuf, + "\nTSIP report packet ID %2Xh, length %d: data content incorrect", + rpt->code, rpt->len); + } + + for (i = 0; i < rpt->len; i++) { + if ((i % 20) == 0) *pbuf++ = '\n'; + pbuf += sprintf(pbuf, " %02X", rpt->buf[i]); + } +} +/**/ +/* +** main subroutine, called from ProcessInputBytesWhileWaitingForKBHit() +*/ +void TranslateTSIPReportToText (TSIPPKT *rpt, char *TextOutputBuffer) +{ + + /* pbuf is the pointer to the current location of the text output */ + pbuf = TextOutputBuffer; + + /* keep track of whether the message has been successfully parsed */ + parsed = GOOD_PARSE; + + /* print a header if this is the first of a series of messages */ + pbuf += print_msg_table_header (rpt->code, pbuf, FALSE); + + /* process incoming TSIP report according to code */ + switch (rpt->code) + { + case 0x3D: rpt_chan_A_config (rpt); break; + case 0x40: rpt_almanac_data_page (rpt); break; + case 0x41: rpt_GPS_time (rpt); break; + case 0x42: rpt_single_ECEF_position (rpt); break; + case 0x43: rpt_single_ECEF_velocity (rpt); break; + case 0x45: rpt_SW_version (rpt); break; + case 0x46: rpt_rcvr_health (rpt); break; + case 0x47: rpt_SNR_all_SVs (rpt); break; + case 0x48: rpt_GPS_system_message (rpt); break; + case 0x49: rpt_almanac_health_page (rpt); break; + case 0x4A: switch (rpt->len) { + /* + ** special case (=slip-up) in the TSIP protocol; + ** parsing method depends on length + */ + case 20: rpt_single_lla_position (rpt); break; + case 9: rpt_ref_alt (rpt); break; + } break; + case 0x4B: rpt_rcvr_id_and_status (rpt);break; + case 0x4C: rpt_operating_parameters (rpt); break; + case 0x4D: rpt_oscillator_offset (rpt); break; + case 0x4E: rpt_GPS_time_set_response (rpt); break; + case 0x4F: rpt_UTC_offset (rpt); break; + case 0x54: rpt_1SV_bias (rpt); break; + case 0x55: rpt_io_opt (rpt); break; + case 0x56: rpt_ENU_velocity (rpt); break; + case 0x57: rpt_last_fix_info (rpt); break; + case 0x58: rpt_GPS_system_data (rpt); break; + case 0x59: rpt_SVs_enabled (rpt); break; + case 0x5A: rpt_raw_msmt (rpt); break; + case 0x5B: rpt_SV_ephemeris_status (rpt); break; + case 0x5C: rpt_SV_tracking_status (rpt); break; + case 0x6D: rpt_allSV_selection (rpt); break; + case 0x82: rpt_DGPS_position_mode (rpt); break; + case 0x83: rpt_double_ECEF_position (rpt); break; + case 0x84: rpt_double_lla_position (rpt); break; + case 0xBB: rpt_complete_rcvr_config (rpt); break; + case 0xBC: rpt_rcvr_serial_port_config (rpt); break; + + case 0x8F: switch (rpt->buf[0]) + { + /* superpackets; parsed according to subcodes */ + case 0x0B: rpt_8F0B(rpt); break; + case 0x14: rpt_8F14(rpt); break; + case 0x15: rpt_8F15(rpt); break; + case 0x20: rpt_8F20(rpt); break; + case 0x41: rpt_8F41(rpt); break; + case 0x42: rpt_8F42(rpt); break; + case 0x45: rpt_8F45(rpt); break; + case 0x4A: rpt_8F4A(rpt); break; + case 0x4B: rpt_8F4B(rpt); break; + case 0x4D: rpt_8F4D(rpt); break; + case 0xA5: rpt_8FA5(rpt); break; + case 0xAD: rpt_8FAD(rpt); break; + default: parsed = BADID_PARSE; break; + } + break; + + default: parsed = BADID_PARSE; break; + } + + if (parsed != GOOD_PARSE) + { + /* + **The message has TSIP structure (DLEs, etc.) + ** but could not be parsed by above routines + */ + unknown_rpt (rpt); + } + + /* close TextOutputBuffer */ + pbuf = '\0'; +} + +#endif /* TRIMBLE_OUTPUT_FUNC */ + +#else /* defined(REFCLOCK) && defined(CLOCK_RIPENCC) */ +int refclock_ripencc_bs; +#endif /* defined(REFCLOCK) && defined(CLOCK_RIPENCC) */ + diff --git a/ntpd/refclock_shm.c b/ntpd/refclock_shm.c new file mode 100644 index 0000000..4ab0ede --- /dev/null +++ b/ntpd/refclock_shm.c @@ -0,0 +1,305 @@ +/* + * refclock_shm - clock driver for utc via shared memory + * - under construction - + * To add new modes: Extend or union the shmTime-struct. Do not + * extend/shrink size, because otherwise existing implementations + * will specify wrong size of shared memory-segment + * PB 18.3.97 + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_SHM) + +#include "ntpd.h" +#undef fileno +#include "ntp_io.h" +#undef fileno +#include "ntp_refclock.h" +#undef fileno +#include "ntp_unixtime.h" +#undef fileno +#include "ntp_stdlib.h" + +#undef fileno +#include <ctype.h> +#undef fileno + +#ifndef SYS_WINNT +# include <sys/ipc.h> +# include <sys/shm.h> +# include <assert.h> +# include <unistd.h> +# include <stdio.h> +#endif + +/* + * This driver supports a reference clock attached thru shared memory + */ + +/* + * SHM interface definitions + */ +#define PRECISION (-1) /* precision assumed (0.5 s) */ +#define REFID "SHM" /* reference ID */ +#define DESCRIPTION "SHM/Shared memory interface" + +#define NSAMPLES 3 /* stages of median filter */ + +/* + * Function prototypes + */ +static int shm_start (int, struct peer *); +static void shm_shutdown (int, struct peer *); +static void shm_poll (int unit, struct peer *); + +/* + * Transfer vector + */ +struct refclock refclock_shm = { + shm_start, /* start up driver */ + shm_shutdown, /* shut down driver */ + shm_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* initialize driver (not used) */ + noentry, /* not used */ + NOFLAGS /* not used */ +}; +struct shmTime { + int mode; /* 0 - if valid set + * use values, + * clear valid + * 1 - if valid set + * if count before and after read of values is equal, + * use values + * clear valid + */ + int count; + time_t clockTimeStampSec; + int clockTimeStampUSec; + time_t receiveTimeStampSec; + int receiveTimeStampUSec; + int leap; + int precision; + int nsamples; + int valid; + int dummy[10]; +}; + +struct shmTime *getShmTime(int); + +struct shmTime *getShmTime (int unit) { +#ifndef SYS_WINNT + int shmid=0; + + assert (unit<10); /* MAXUNIT is 4, so should never happen */ + shmid=shmget (0x4e545030+unit, sizeof (struct shmTime), + IPC_CREAT|(unit<2?0700:0777)); + if (shmid==-1) { /*error */ + msyslog(LOG_ERR,"SHM shmget (unit %d): %s",unit,strerror(errno)); + return 0; + } + else { /* no error */ + struct shmTime *p=(struct shmTime *)shmat (shmid, 0, 0); + if ((int)(long)p==-1) { /* error */ + msyslog(LOG_ERR,"SHM shmat (unit %d): %s",unit,strerror(errno)); + return 0; + } + return p; + } +#else + char buf[10]; + LPSECURITY_ATTRIBUTES psec=0; + HANDLE shmid=0; + SECURITY_DESCRIPTOR sd; + SECURITY_ATTRIBUTES sa; + sprintf (buf,"NTP%d",unit); + if (unit>=2) { /* world access */ + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + msyslog(LOG_ERR,"SHM InitializeSecurityDescriptor (unit %d): %m",unit); + return 0; + } + if (!SetSecurityDescriptorDacl(&sd,1,0,0)) { + msyslog(LOG_ERR,"SHM SetSecurityDescriptorDacl (unit %d): %m",unit); + return 0; + } + sa.nLength=sizeof (SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor=&sd; + sa.bInheritHandle=0; + psec=&sa; + } + shmid=CreateFileMapping ((HANDLE)0xffffffff, psec, PAGE_READWRITE, + 0, sizeof (struct shmTime),buf); + if (!shmid) { /*error*/ + char buf[1000]; + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, + 0, GetLastError (), 0, buf, sizeof (buf), 0); + msyslog(LOG_ERR,"SHM CreateFileMapping (unit %d): %s",unit,buf); + return 0; + } + else { + struct shmTime *p=(struct shmTime *) MapViewOfFile (shmid, + FILE_MAP_WRITE, 0, 0, sizeof (struct shmTime)); + if (p==0) { /*error*/ + char buf[1000]; + FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, + 0, GetLastError (), 0, buf, sizeof (buf), 0); + msyslog(LOG_ERR,"SHM MapViewOfFile (unit %d): %s",unit,buf); + return 0; + } + return p; + } +#endif +} +/* + * shm_start - attach to shared memory + */ +static int +shm_start( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + pp = peer->procptr; + pp->io.clock_recv = noentry; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = -1; + pp->unitptr = (caddr_t)getShmTime(unit); + + /* + * Initialize miscellaneous peer variables + */ + memcpy((char *)&pp->refid, REFID, 4); + if (pp->unitptr!=0) { + ((struct shmTime*)pp->unitptr)->precision=PRECISION; + peer->precision = ((struct shmTime*)pp->unitptr)->precision; + ((struct shmTime*)pp->unitptr)->valid=0; + ((struct shmTime*)pp->unitptr)->nsamples=NSAMPLES; + pp->clockdesc = DESCRIPTION; + return (1); + } + else { + return 0; + } +} + + +/* + * shm_shutdown - shut down the clock + */ +static void +shm_shutdown( + int unit, + struct peer *peer + ) +{ + register struct shmTime *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct shmTime *)pp->unitptr; +#ifndef SYS_WINNT + /* HMS: shmdt()wants char* or const void * */ + (void) shmdt (up); +#else + UnmapViewOfFile (up); +#endif +} + + +/* + * shm_poll - called by the transmit procedure + */ +static void +shm_poll( + int unit, + struct peer *peer + ) +{ + register struct shmTime *up; + struct refclockproc *pp; + + /* + * This is the main routine. It snatches the time from the shm + * board and tacks on a local timestamp. + */ + pp = peer->procptr; + up = (struct shmTime*)pp->unitptr; + if (up==0) { /* try to map again - this may succeed if meanwhile some- + body has ipcrm'ed the old (unaccessible) shared mem + segment */ + pp->unitptr = (caddr_t)getShmTime(unit); + up = (struct shmTime*)pp->unitptr; + } + if (up==0) { + refclock_report(peer, CEVNT_FAULT); + return; + } + if (up->valid) { + struct timeval tvr; + struct timeval tvt; + struct tm *t; + int ok=1; + switch (up->mode) { + case 0: { + tvr.tv_sec=up->receiveTimeStampSec; + tvr.tv_usec=up->receiveTimeStampUSec; + tvt.tv_sec=up->clockTimeStampSec; + tvt.tv_usec=up->clockTimeStampUSec; + } + break; + case 1: { + int cnt=up->count; + tvr.tv_sec=up->receiveTimeStampSec; + tvr.tv_usec=up->receiveTimeStampUSec; + tvt.tv_sec=up->clockTimeStampSec; + tvt.tv_usec=up->clockTimeStampUSec; + ok=(cnt==up->count); + } + break; + default: + msyslog (LOG_ERR, "SHM: bad mode found in shared memory: %d",up->mode); + } + up->valid=0; + if (ok) { + TVTOTS(&tvr,&pp->lastrec); + pp->lastrec.l_ui += JAN_1970; + /* pp->lasttime = current_time; */ + pp->polls++; + t=gmtime (&tvt.tv_sec); + pp->day=t->tm_yday+1; + pp->hour=t->tm_hour; + pp->minute=t->tm_min; + pp->second=t->tm_sec; + pp->nsec=tvt.tv_usec * 1000; + peer->precision=up->precision; + pp->leap=up->leap; + } + else { + refclock_report(peer, CEVNT_FAULT); + msyslog (LOG_NOTICE, "SHM: access clash in shared memory"); + return; + } + } + else { + refclock_report(peer, CEVNT_TIMEOUT); + /* + msyslog (LOG_NOTICE, "SHM: no new value found in shared memory"); + */ + return; + } + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); +} + +#else +int refclock_shm_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_tpro.c b/ntpd/refclock_tpro.c new file mode 100644 index 0000000..3c42568 --- /dev/null +++ b/ntpd/refclock_tpro.c @@ -0,0 +1,216 @@ +/* + * refclock_tpro - clock driver for the KSI/Odetics TPRO-S IRIG-B reader + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_TPRO) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "sys/tpro.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the KSI/Odetecs TPRO-S IRIG-B reader and TPRO- + * SAT GPS receiver for the Sun Microsystems SBus. It requires that the + * tpro.o device driver be installed and loaded. + */ + +/* + * TPRO interface definitions + */ +#define DEVICE "/dev/tpro%d" /* device name and unit */ +#define PRECISION (-20) /* precision assumed (1 us) */ +#define REFID "IRIG" /* reference ID */ +#define DESCRIPTION "KSI/Odetics TPRO/S IRIG Interface" /* WRU */ + +/* + * Unit control structure + */ +struct tprounit { + struct tproval tprodata; /* data returned from tpro read */ +}; + +/* + * Function prototypes + */ +static int tpro_start P((int, struct peer *)); +static void tpro_shutdown P((int, struct peer *)); +static void tpro_poll P((int unit, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_tpro = { + tpro_start, /* start up driver */ + tpro_shutdown, /* shut down driver */ + tpro_poll, /* transmit poll message */ + noentry, /* not used (old tpro_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old tpro_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * tpro_start - open the TPRO device and initialize data for processing + */ +static int +tpro_start( + int unit, + struct peer *peer + ) +{ + register struct tprounit *up; + struct refclockproc *pp; + char device[20]; + int fd; + + /* + * Open TPRO device + */ + (void)sprintf(device, DEVICE, unit); + fd = open(device, O_RDONLY | O_NDELAY, 0777); + if (fd == -1) { + msyslog(LOG_ERR, "tpro_start: open of %s: %m", device); + return (0); + } + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct tprounit *) emalloc(sizeof(struct tprounit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct tprounit)); + pp = peer->procptr; + pp->io.clock_recv = noentry; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous peer variables + */ + peer->precision = PRECISION; + peer->burst = NSTAGE; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * tpro_shutdown - shut down the clock + */ +static void +tpro_shutdown( + int unit, + struct peer *peer + ) +{ + register struct tprounit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct tprounit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * tpro_poll - called by the transmit procedure + */ +static void +tpro_poll( + int unit, + struct peer *peer + ) +{ + register struct tprounit *up; + struct refclockproc *pp; + struct tproval *tp; + + /* + * This is the main routine. It snatches the time from the TPRO + * board and tacks on a local timestamp. + */ + pp = peer->procptr; + up = (struct tprounit *)pp->unitptr; + + tp = &up->tprodata; + if (read(pp->io.fd, (char *)tp, sizeof(struct tproval)) < 0) { + refclock_report(peer, CEVNT_FAULT); + return; + } + get_systime(&pp->lastrec); + pp->polls++; + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. Note: we + * can't use the sec/usec conversion produced by the driver, + * since the year may be suspect. All format error checking is + * done by the sprintf() and sscanf() routines. + * + * Note that the refclockproc usec member has now become nsec. + * We could either multiply the read-in usec value by 1000 or + * we could pad the written string appropriately and read the + * resulting value in already scaled. + */ + sprintf(pp->a_lastcode, + "%1x%1x%1x %1x%1x:%1x%1x:%1x%1x.%1x%1x%1x%1x%1x%1x %1x", + tp->day100, tp->day10, tp->day1, tp->hour10, tp->hour1, + tp->min10, tp->min1, tp->sec10, tp->sec1, tp->ms100, + tp->ms10, tp->ms1, tp->usec100, tp->usec10, tp->usec1, + tp->status); + pp->lencode = strlen(pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("tpro: time %s timecode %d %s\n", + ulfptoa(&pp->lastrec, 6), pp->lencode, + pp->a_lastcode); +#endif + if (sscanf(pp->a_lastcode, "%3d %2d:%2d:%2d.%6ld", &pp->day, + &pp->hour, &pp->minute, &pp->second, &pp->nsec) + != 5) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->nsec *= 1000; /* Convert usec to nsec */ + if (!tp->status & 0x3) + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + refclock_receive(peer); + pp->lastref = pp->lastrec; + record_clock_stats(&peer->srcadr, pp->a_lastcode); + refclock_receive(peer); + peer->burst = NSTAGE; +} + +#else +int refclock_tpro_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_trak.c b/ntpd/refclock_trak.c new file mode 100644 index 0000000..3a4a54e --- /dev/null +++ b/ntpd/refclock_trak.c @@ -0,0 +1,359 @@ +/* + * refclock_trak - clock driver for the TRAK 8810 GPS Station Clock + * + * Tomoaki TSURUOKA <tsuruoka@nc.fukuoka-u.ac.jp> + * original version Dec, 1993 + * revised version Sep, 1994 for ntp3.4e or later + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_TRAK) && defined(PPS) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_unixtime.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +/* + * This driver supports the TRAK 8810/8820 GPS Station Clock. The claimed + * accuracy at the 1-pps output is 200-300 ns relative to the broadcast + * signal; however, in most cases the actual accuracy is limited by the + * precision of the timecode and the latencies of the serial interface + * and operating system. + * + * For best accuracy, this radio requires the LDISC_ACTS line + * discipline, which captures a timestamp at the '*' on-time character + * of the timecode. Using this discipline the jitter is in the order of + * 1 ms and systematic error about 0.5 ms. If unavailable, the buffer + * timestamp is used, which is captured at the \r ending the timecode + * message. This introduces a systematic error of 23 character times, or + * about 24 ms at 9600 bps, together with a jitter well over 8 ms on Sun + * IPC-class machines. + * + * Using the memus, the radio should be set for 9600 bps, one stop bit + * and no parity. It should be set to operate in computer (no echo) + * mode. The timecode format includes neither the year nor leap-second + * warning. No provisions are included in this preliminary version of + * the driver to read and record detailed internal radio status. + * + * In operation, this driver sends a RQTS\r request to the radio at + * initialization in order to put it in continuous time output mode. The + * radio then sends the following message once each second: + * + * *RQTS U,ddd:hh:mm:ss.0,q<cr><lf> + * + * on-time = '*' * ddd = day of year + * hh:mm:ss = hours, minutes, seconds + * q = quality indicator (phase error), 0-6: + * 0 > 20 us + * 6 > 10 us + * 5 > 1 us + * 4 > 100 ns + * 3 > 10 ns + * 2 < 10 ns + * + * The alarm condition is indicated by '0' at Q, which means the radio + * has a phase error than 20 usec relative to the broadcast time. The + * absence of year, DST and leap-second warning in this format is also + * alarming. + * + * The continuous time mode is disabled using the RQTX<cr> request, + * following which the radio sends a RQTX DONE<cr><lf> response. In the + * normal mode, other control and status requests are effective, + * including the leap-second status request RQLS<cr>. The radio responds + * wtih RQLS yy,mm,dd<cr><lf>, where yy,mm,dd are the year, month and + * day. Presumably, this gives the epoch of the next leap second, + * RQLS 00,00,00 if none is specified in the GPS message. Specified in + * this form, the information is generally useless and is ignored by + * the driver. + * + * Fudge Factors + * + * There are no special fudge factors other than the generic. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/trak%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference ID */ +#define DESCRIPTION "TRACK 8810/8820 Station Clock" /* WRU */ + +#define LENTRAK 24 /* timecode length */ +#define C_CTO "RQTS\r" /* start continuous time output */ + +/* + * Unit control structure + */ +struct trakunit { + int polled; /* poll message flag */ + l_fp tstamp; /* timestamp of last poll */ +}; + +/* + * Function prototypes + */ +static int trak_start P((int, struct peer *)); +static void trak_shutdown P((int, struct peer *)); +static void trak_receive P((struct recvbuf *)); +static void trak_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_trak = { + trak_start, /* start up driver */ + trak_shutdown, /* shut down driver */ + trak_poll, /* transmit poll message */ + noentry, /* not used (old trak_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old trak_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * trak_start - open the devices and initialize data for processing + */ +static int +trak_start( + int unit, + struct peer *peer + ) +{ + register struct trakunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. The LDISC_ACTS line discipline inserts a + * timestamp following the "*" on-time character of the + * timecode. + */ + (void)sprintf(device, DEVICE, unit); + if ( +#ifdef PPS + !(fd = refclock_open(device, SPEED232, LDISC_CLK)) +#else + !(fd = refclock_open(device, SPEED232, 0)) +#endif /* PPS */ + ) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct trakunit *) + emalloc(sizeof(struct trakunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct trakunit)); + pp = peer->procptr; + pp->io.clock_recv = trak_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->polled = 0; + + /* + * Start continuous time output. If something breaks, fold the + * tent and go home. + */ + if (write(pp->io.fd, C_CTO, sizeof(C_CTO)) != sizeof(C_CTO)) { + refclock_report(peer, CEVNT_FAULT); + (void) close(fd); + free(up); + return (0); + } + return (1); +} + + +/* + * trak_shutdown - shut down the clock + */ +static void +trak_shutdown( + int unit, + struct peer *peer + ) +{ + register struct trakunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct trakunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * trak_receive - receive data from the serial interface + */ +static void +trak_receive( + struct recvbuf *rbufp + ) +{ + register struct trakunit *up; + struct refclockproc *pp; + struct peer *peer; + l_fp trtmp; + char *dpt, *dpend; + char qchar; +#ifdef PPS + struct ppsclockev ppsev; + int request; +#ifdef HAVE_CIOGETEV + request = CIOGETEV; +#endif +#ifdef HAVE_TIOCGPPSEV + request = TIOCGPPSEV; +#endif +#endif /* PPS */ + + /* + * Initialize pointers and read the timecode and timestamp. We + * then chuck out everything, including runts, except one + * message each poll interval. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct trakunit *)pp->unitptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, + &pp->lastrec); + + /* + * We get a buffer and timestamp following the '*' on-time + * character. If a valid timestamp, we use that in place of the + * buffer timestamp and edit out the timestamp for prettyprint + * billboards. + */ + dpt = pp->a_lastcode; + dpend = dpt + pp->lencode; + if (*dpt == '*' && buftvtots(dpt + 1, &trtmp)) { + if (trtmp.l_i == pp->lastrec.l_i || trtmp.l_i == + pp->lastrec.l_i + 1) { + pp->lastrec = trtmp; + dpt += 9; + while (dpt < dpend) { + *(dpt - 8) = *dpt; + ++dpt; + } + } + } + if (up->polled == 0) return; + up->polled = 0; +#ifndef PPS + get_systime(&up->tstamp); +#endif + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("trak: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + if (pp->lencode < LENTRAK) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Timecode format: "*RQTS U,ddd:hh:mm:ss.0,q" + */ + if (sscanf(pp->a_lastcode, "*RQTS U,%3d:%2d:%2d:%2d.0,%c", + &pp->day, &pp->hour, &pp->minute, &pp->second, &qchar) != 5) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + /* + * Decode quality and leap characters. If unsynchronized, set + * the leap bits accordingly and exit. + */ + if (qchar == '0') { + pp->leap = LEAP_NOTINSYNC; + return; + } +#ifdef PPS + if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) { + ppsev.tv.tv_sec += (u_int32) JAN_1970; + TVTOTS(&ppsev.tv,&up->tstamp); + } +#endif /* PPS */ + /* record the last ppsclock event time stamp */ + pp->lastrec = up->tstamp; + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); +} + + +/* + * trak_poll - called by the transmit procedure + */ +static void +trak_poll( + int unit, + struct peer *peer + ) +{ + register struct trakunit *up; + struct refclockproc *pp; + + /* + * We don't really do anything here, except arm the receiving + * side to capture a sample and check for timeouts. + */ + pp = peer->procptr; + up = (struct trakunit *)pp->unitptr; + if (up->polled) + refclock_report(peer, CEVNT_TIMEOUT); + pp->polls++; + up->polled = 1; +} + +#else +int refclock_trak_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_true.c b/ntpd/refclock_true.c new file mode 100644 index 0000000..dd355d9 --- /dev/null +++ b/ntpd/refclock_true.c @@ -0,0 +1,873 @@ +/* + * refclock_true - clock driver for the Kinemetrics Truetime receivers + * Receiver Version 3.0C - tested plain, with CLKLDISC + * Developement work being done: + * - Properly handle varying satellite positions (more acurately) + * - Integrate GPSTM and/or OMEGA and/or TRAK and/or ??? drivers + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_TRUETIME) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* This should be an atom clock but those are very hard to build. + * + * The PCL720 from P C Labs has an Intel 8253 lookalike, as well as a bunch + * of TTL input and output pins, all brought out to the back panel. If you + * wire a PPS signal (such as the TTL PPS coming out of a GOES or other + * Kinemetrics/Truetime clock) to the 8253's GATE0, and then also wire the + * 8253's OUT0 to the PCL720's INPUT3.BIT0, then we can read CTR0 to get the + * number of uSecs since the last PPS upward swing, mediated by reading OUT0 + * to find out if the counter has wrapped around (this happens if more than + * 65535us (65ms) elapses between the PPS event and our being called.) + */ +#ifdef CLOCK_PPS720 +# undef min /* XXX */ +# undef max /* XXX */ +# include <machine/inline.h> +# include <sys/pcl720.h> +# include <sys/i8253.h> +# define PCL720_IOB 0x2a0 /* XXX */ +# define PCL720_CTR 0 /* XXX */ +#endif + +/* + * Support for Kinemetrics Truetime Receivers + * GOES + * GPS/TM-TMD + * XL-DC (a 151-602-210, reported by the driver as a GPS/TM-TMD) + * GPS-800 TCU (an 805-957 with the RS232 Talker/Listener module) + * OM-DC: getting stale ("OMEGA") + * + * Most of this code is originally from refclock_wwvb.c with thanks. + * It has been so mangled that wwvb is not a recognizable ancestor. + * + * Timcode format: ADDD:HH:MM:SSQCL + * A - control A (this is stripped before we see it) + * Q - Quality indication (see below) + * C - Carriage return + * L - Line feed + * + * Quality codes indicate possible error of + * 468-DC GOES Receiver: + * GPS-TM/TMD Receiver: (default quality codes for XL-DC) + * ? +/- 1 milliseconds # +/- 100 microseconds + * * +/- 10 microseconds . +/- 1 microsecond + * space less than 1 microsecond + * OM-DC OMEGA Receiver: (default quality codes for OMEGA) + * WARNING OMEGA navigation system is no longer existent + * > >+- 5 seconds + * ? >+/- 500 milliseconds # >+/- 50 milliseconds + * * >+/- 5 milliseconds . >+/- 1 millisecond + * A-H less than 1 millisecond. Character indicates which station + * is being received as follows: + * A = Norway, B = Liberia, C = Hawaii, D = North Dakota, + * E = La Reunion, F = Argentina, G = Australia, H = Japan. + * + * The carriage return start bit begins on 0 seconds and extends to 1 bit time. + * + * Notes on 468-DC and OMEGA receiver: + * + * Send the clock a 'R' or 'C' and once per second a timestamp will + * appear. Send a 'P' to get the satellite position once (GOES only.) + * + * Notes on the 468-DC receiver: + * + * Since the old east/west satellite locations are only historical, you can't + * set your clock propagation delay settings correctly and still use + * automatic mode. The manual says to use a compromise when setting the + * switches. This results in significant errors. The solution; use fudge + * time1 and time2 to incorporate corrections. If your clock is set for + * 50 and it should be 58 for using the west and 46 for using the east, + * use the line + * + * fudge 127.127.5.0 time1 +0.008 time2 -0.004 + * + * This corrects the 4 milliseconds advance and 8 milliseconds retard + * needed. The software will ask the clock which satellite it sees. + * + * Ntp.conf parameters: + * time1 - offset applied to samples when reading WEST satellite (default = 0) + * time2 - offset applied to samples when reading EAST satellite (default = 0) + * val1 - stratum to assign to this clock (default = 0) + * val2 - refid assigned to this clock (default = "TRUE", see below) + * flag1 - will silence the clock side of ntpd, just reading the clock + * without trying to write to it. (default = 0) + * flag2 - generate a debug file /tmp/true%d. + * flag3 - enable ppsclock streams module + * flag4 - use the PCL-720 (BSD/OS only) + */ + + +/* + * Definitions + */ +#define DEVICE "/dev/true%d" +#define SPEED232 B9600 /* 9600 baud */ + +/* + * Radio interface parameters + */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "TRUE" /* reference id */ +#define DESCRIPTION "Kinemetrics/TrueTime Receiver" + +/* + * Tags which station (satellite) we see + */ +#define GOES_WEST 0 /* Default to WEST satellite and apply time1 */ +#define GOES_EAST 1 /* until you discover otherwise */ + +/* + * used by the state machine + */ +enum true_event {e_Init, e_Huh, e_F18, e_F50, e_F51, e_Satellite, + e_Poll, e_Location, e_TS, e_Max}; +const char *events[] = {"Init", "Huh", "F18", "F50", "F51", "Satellite", + "Poll", "Location", "TS"}; +#define eventStr(x) (((int)x<(int)e_Max) ? events[(int)x] : "?") + +enum true_state {s_Base, s_InqTM, s_InqTCU, s_InqOmega, s_InqGOES, + s_Init, s_F18, s_F50, s_Start, s_Auto, s_Max}; +const char *states[] = {"Base", "InqTM", "InqTCU", "InqOmega", "InqGOES", + "Init", "F18", "F50", "Start", "Auto"}; +#define stateStr(x) (((int)x<(int)s_Max) ? states[(int)x] : "?") + +enum true_type {t_unknown, t_goes, t_tm, t_tcu, t_omega, t_Max}; +const char *types[] = {"unknown", "goes", "tm", "tcu", "omega"}; +#define typeStr(x) (((int)x<(int)t_Max) ? types[(int)x] : "?") + +/* + * unit control structure + */ +struct true_unit { + unsigned int pollcnt; /* poll message counter */ + unsigned int station; /* which station we are on */ + unsigned int polled; /* Hand in a time sample? */ + enum true_state state; /* state machine */ + enum true_type type; /* what kind of clock is it? */ + int unit; /* save an extra copy of this */ + FILE *debug; /* debug logging file */ +#ifdef CLOCK_PPS720 + int pcl720init; /* init flag for PCL 720 */ +#endif +}; + +/* + * Function prototypes + */ +static int true_start P((int, struct peer *)); +static void true_shutdown P((int, struct peer *)); +static void true_receive P((struct recvbuf *)); +static void true_poll P((int, struct peer *)); +static void true_send P((struct peer *, const char *)); +static void true_doevent P((struct peer *, enum true_event)); + +#ifdef CLOCK_PPS720 +static u_long true_sample720 P((void)); +#endif + +/* + * Transfer vector + */ +struct refclock refclock_true = { + true_start, /* start up driver */ + true_shutdown, /* shut down driver */ + true_poll, /* transmit poll message */ + noentry, /* not used (old true_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old true_buginfo) */ + NOFLAGS /* not used */ +}; + + +#if !defined(__STDC__) +# define true_debug (void) +#else +static void +true_debug(struct peer *peer, const char *fmt, ...) +{ + va_list ap; + int want_debugging, now_debugging; + struct refclockproc *pp; + struct true_unit *up; + + va_start(ap, fmt); + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + + want_debugging = (pp->sloppyclockflag & CLK_FLAG2) != 0; + now_debugging = (up->debug != NULL); + if (want_debugging != now_debugging) + { + if (want_debugging) { + char filename[40]; + int fd; + + snprintf(filename, sizeof(filename), "/tmp/true%d.debug", up->unit); + fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0600); + if (fd >= 0 && (up->debug = fdopen(fd, "r+"))) { +#ifdef HAVE_SETVBUF + static char buf[BUFSIZ]; + setvbuf(up->debug, buf, _IOLBF, BUFSIZ); +#else + setlinebuf(up->debug); +#endif + } + } else { + fclose(up->debug); + up->debug = NULL; + } + } + + if (up->debug) { + fprintf(up->debug, "true%d: ", up->unit); + vfprintf(up->debug, fmt, ap); + } +} +#endif /*STDC*/ + +/* + * true_start - open the devices and initialize data for processing + */ +static int +true_start( + int unit, + struct peer *peer + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + char device[40]; + int fd; + + /* + * Open serial port + */ + (void)snprintf(device, sizeof(device), DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct true_unit *) + emalloc(sizeof(struct true_unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct true_unit)); + pp = peer->procptr; + pp->io.clock_recv = true_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->pollcnt = 2; + up->type = t_unknown; + up->state = s_Base; + true_doevent(peer, e_Init); + return (1); +} + +/* + * true_shutdown - shut down the clock + */ +static void +true_shutdown( + int unit, + struct peer *peer + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * true_receive - receive data from the serial interface on a clock + */ +static void +true_receive( + struct recvbuf *rbufp + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + struct peer *peer; + u_short new_station; + char synced; + int i; + int lat, lon, off; /* GOES Satellite position */ + /* Use these variable to hold data until we decide its worth keeping */ + char rd_lastcode[BMAX]; + l_fp rd_tmp; + u_short rd_lencode; + + /* + * Get the clock this applies to and pointers to the data. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + + /* + * Read clock output. Automatically handles STREAMS, CLKLDISC. + */ + rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); + rd_lastcode[rd_lencode] = '\0'; + + /* + * There is a case where <cr><lf> generates 2 timestamps. + */ + if (rd_lencode == 0) + return; + pp->lencode = rd_lencode; + strcpy(pp->a_lastcode, rd_lastcode); + pp->lastrec = rd_tmp; + true_debug(peer, "receive(%s) [%d]\n", pp->a_lastcode, pp->lencode); + + up->pollcnt = 2; + record_clock_stats(&peer->srcadr, pp->a_lastcode); + + /* + * We get down to business, check the timecode format and decode + * its contents. This code decodes a multitude of different + * clock messages. Timecodes are processed if needed. All replies + * will be run through the state machine to tweak driver options + * and program the clock. + */ + + /* + * Clock misunderstood our last command? + */ + if (pp->a_lastcode[0] == '?' || + strcmp(pp->a_lastcode, "ERROR 05 NO SUCH FUNCTION") == 0) { + true_doevent(peer, e_Huh); + return; + } + + /* + * Timecode: "nnnnn+nnn-nnn" + * (from GOES clock when asked about satellite position) + */ + if ((pp->a_lastcode[5] == '+' || pp->a_lastcode[5] == '-') && + (pp->a_lastcode[9] == '+' || pp->a_lastcode[9] == '-') && + sscanf(pp->a_lastcode, "%5d%*c%3d%*c%3d", &lon, &lat, &off) == 3 + ) { + const char *label = "Botch!"; + + /* + * This is less than perfect. Call the (satellite) + * either EAST or WEST and adjust slop accodingly + * Perfectionists would recalculate the exact delay + * and adjust accordingly... + */ + if (lon > 7000 && lon < 14000) { + if (lon < 10000) { + new_station = GOES_EAST; + label = "EAST"; + } else { + new_station = GOES_WEST; + label = "WEST"; + } + + if (new_station != up->station) { + double dtemp; + + dtemp = pp->fudgetime1; + pp->fudgetime1 = pp->fudgetime2; + pp->fudgetime2 = dtemp; + up->station = new_station; + } + } + else { + /*refclock_report(peer, CEVNT_BADREPLY);*/ + label = "UNKNOWN"; + } + true_debug(peer, "GOES: station %s\n", label); + true_doevent(peer, e_Satellite); + return; + } + + /* + * Timecode: "Fnn" + * (from TM/TMD clock when it wants to tell us what it's up to.) + */ + if (sscanf(pp->a_lastcode, "F%2d", &i) == 1 && i > 0 && i < 80) { + switch (i) { + case 50: + true_doevent(peer, e_F50); + break; + case 51: + true_doevent(peer, e_F51); + break; + default: + true_debug(peer, "got F%02d - ignoring\n", i); + break; + } + return; + } + + /* + * Timecode: " TRUETIME Mk III" or " TRUETIME XL" + * (from a TM/TMD/XL clock during initialization.) + */ + if (strcmp(pp->a_lastcode, " TRUETIME Mk III") == 0 || + strncmp(pp->a_lastcode, " TRUETIME XL", 12) == 0) { + true_doevent(peer, e_F18); + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TM/TMD/XL: %s", pp->a_lastcode); + } + return; + } + + /* + * Timecode: "N03726428W12209421+000033" + * 1 2 + * 0123456789012345678901234 + * (from a TCU during initialization) + */ + if ((pp->a_lastcode[0] == 'N' || pp->a_lastcode[0] == 'S') && + (pp->a_lastcode[9] == 'W' || pp->a_lastcode[9] == 'E') && + pp->a_lastcode[18] == '+') { + true_doevent(peer, e_Location); + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TCU-800: %s", pp->a_lastcode); + } + return; + } + /* + * Timecode: "ddd:hh:mm:ssQ" + * (from all clocks supported by this driver.) + */ + if (pp->a_lastcode[3] == ':' && + pp->a_lastcode[6] == ':' && + pp->a_lastcode[9] == ':' && + sscanf(pp->a_lastcode, "%3d:%2d:%2d:%2d%c", + &pp->day, &pp->hour, &pp->minute, + &pp->second, &synced) == 5) { + + /* + * Adjust the synchronize indicator according to timecode + * say were OK, and then say not if we really are not OK + */ + if (synced == '>' || synced == '#' || synced == '?') + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + + true_doevent(peer, e_TS); + +#ifdef CLOCK_PPS720 + /* If it's taken more than 65ms to get here, we'll lose. */ + if ((pp->sloppyclockflag & CLK_FLAG4) && up->pcl720init) { + l_fp off; + +#ifdef CLOCK_ATOM + /* + * find out what time it really is. Include + * the count from the PCL720 + */ + if (!clocktime(pp->day, pp->hour, pp->minute, + pp->second, GMT, pp->lastrec.l_ui, + &pp->yearstart, &off.l_ui)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + off.l_uf = 0; +#endif + + pp->usec = true_sample720(); +#ifdef CLOCK_ATOM + TVUTOTSF(pp->usec, off.l_uf); +#endif + + /* + * Stomp all over the timestamp that was pulled out + * of the input stream. It's irrelevant since we've + * adjusted the input time to reflect now (via pp->usec) + * rather than when the data was collected. + */ + get_systime(&pp->lastrec); +#ifdef CLOCK_ATOM + /* + * Create a true offset for feeding to pps_sample() + */ + L_SUB(&off, &pp->lastrec); + + pps_sample(peer, &off); +#endif + true_debug(peer, "true_sample720: %luus\n", pp->usec); + } +#endif + + /* + * The clock will blurt a timecode every second but we only + * want one when polled. If we havn't been polled, bail out. + */ + if (!up->polled) + return; + + true_doevent(peer, e_Poll); + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + /* + * If clock is good we send a NOMINAL message so that + * any previous BAD messages are nullified + */ + pp->lastref = pp->lastrec; + refclock_receive(peer); + refclock_report(peer, CEVNT_NOMINAL); + + /* + * We have succedded in answering the poll. + * Turn off the flag and return + */ + up->polled = 0; + + return; + } + + /* + * No match to known timecodes, report failure and return + */ + refclock_report(peer, CEVNT_BADREPLY); + return; +} + + +/* + * true_send - time to send the clock a signal to cough up a time sample + */ +static void +true_send( + struct peer *peer, + const char *cmd + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + if (!(pp->sloppyclockflag & CLK_FLAG1)) { + register int len = strlen(cmd); + + true_debug(peer, "Send '%s'\n", cmd); + if (write(pp->io.fd, cmd, (unsigned)len) != len) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; + } +} + + +/* + * state machine for initializing and controlling a clock + */ +static void +true_doevent( + struct peer *peer, + enum true_event event + ) +{ + struct true_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + if (event != e_TS) { + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TRUE: clock %s, state %s, event %s", + typeStr(up->type), + stateStr(up->state), + eventStr(event)); + } + } + true_debug(peer, "clock %s, state %s, event %s\n", + typeStr(up->type), stateStr(up->state), eventStr(event)); + switch (up->type) { + case t_goes: + switch (event) { + case e_Init: /* FALLTHROUGH */ + case e_Satellite: + /* + * Switch back to on-second time codes and return. + */ + true_send(peer, "C"); + up->state = s_Start; + break; + case e_Poll: + /* + * After each poll, check the station (satellite). + */ + true_send(peer, "P"); + /* No state change needed. */ + break; + default: + break; + } + /* FALLTHROUGH */ + case t_omega: + switch (event) { + case e_Init: + true_send(peer, "C"); + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_tm: + switch (event) { + case e_Init: + true_send(peer, "F18\r"); + up->state = s_Init; + break; + case e_F18: + true_send(peer, "F50\r"); + up->state = s_F18; + break; + case e_F50: + true_send(peer, "F51\r"); + up->state = s_F50; + break; + case e_F51: + true_send(peer, "F08\r"); + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_tcu: + switch (event) { + case e_Init: + true_send(peer, "MD3\r"); /* GPS Synch'd Gen. */ + true_send(peer, "TSU\r"); /* UTC, not GPS. */ + true_send(peer, "AU\r"); /* Auto Timestamps. */ + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_unknown: + switch (up->state) { + case s_Base: + if (event != e_Init) + abort(); + true_send(peer, "P\r"); + up->state = s_InqGOES; + break; + case s_InqGOES: + switch (event) { + case e_Satellite: + up->type = t_goes; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: /*FALLTHROUGH*/ + case e_TS: + up->state = s_InqOmega; + true_send(peer, "C\r"); + break; + default: + abort(); + } + break; + case s_InqOmega: + switch (event) { + case e_TS: + up->type = t_omega; + up->state = s_Auto; /* Inq side-effect. */ + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + up->state = s_InqTM; + true_send(peer, "F18\r"); + break; + default: + abort(); + } + break; + case s_InqTM: + switch (event) { + case e_F18: + up->type = t_tm; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + true_send(peer, "PO\r"); + up->state = s_InqTCU; + break; + default: + abort(); + } + break; + case s_InqTCU: + switch (event) { + case e_Location: + up->type = t_tcu; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + up->state = s_Base; + sleep(1); /* XXX */ + break; + default: + abort(); + } + break; + /* + * An expedient hack to prevent lint complaints, + * these don't actually need to be used here... + */ + case s_Init: + case s_F18: + case s_F50: + case s_Start: + case s_Auto: + case s_Max: + msyslog(LOG_INFO, "TRUE: state %s is unexpected!", stateStr(up->state)); + } + break; + default: + abort(); + /* NOTREACHED */ + } + +#ifdef CLOCK_PPS720 + if ((pp->sloppyclockflag & CLK_FLAG4) && !up->pcl720init) { + /* Make counter trigger on gate0, count down from 65535. */ + pcl720_load(PCL720_IOB, PCL720_CTR, i8253_oneshot, 65535); + /* + * (These constants are OK since + * they represent hardware maximums.) + */ + NLOG(NLOG_CLOCKINFO) { + msyslog(LOG_NOTICE, "PCL-720 initialized"); + } + up->pcl720init++; + } +#endif + + +} + +/* + * true_poll - called by the transmit procedure + */ +static void +true_poll( + int unit, + struct peer *peer + ) +{ + struct true_unit *up; + struct refclockproc *pp; + + /* + * You don't need to poll this clock. It puts out timecodes + * once per second. If asked for a timestamp, take note. + * The next time a timecode comes in, it will be fed back. + */ + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + if (up->pollcnt > 0) + up->pollcnt--; + else { + true_doevent(peer, e_Init); + refclock_report(peer, CEVNT_TIMEOUT); + } + + /* + * polled every 64 seconds. Ask true_receive to hand in a + * timestamp. + */ + up->polled = 1; + pp->polls++; +} + +#ifdef CLOCK_PPS720 +/* + * true_sample720 - sample the PCL-720 + */ +static u_long +true_sample720(void) +{ + unsigned long f; + + /* We wire the PCL-720's 8253.OUT0 to bit 0 of connector 3. + * If it is not being held low now, we did not get called + * within 65535us. + */ + if (inb(pcl720_data_16_23(PCL720_IOB)) & 0x01) { + NLOG(NLOG_CLOCKINFO) { + msyslog(LOG_NOTICE, "PCL-720 out of synch"); + } + return (0); + } + f = (65536 - pcl720_read(PCL720_IOB, PCL720_CTR)); +#ifdef PPS720_DEBUG + msyslog(LOG_DEBUG, "PCL-720: %luus", f); +#endif + return (f); +} +#endif + +#else +int refclock_true_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_tt560.c b/ntpd/refclock_tt560.c new file mode 100644 index 0000000..f3d7bc1 --- /dev/null +++ b/ntpd/refclock_tt560.c @@ -0,0 +1,274 @@ +/* + * refclock_tt560 - clock driver for the TrueTime 560 IRIG-B decoder + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_TT560) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "sys/tt560_api.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the TrueTime 560 IRIG-B decoder for the PCI bus. + */ + +/* + * TT560 interface definitions + */ +#define DEVICE "/dev/tt560%d" /* device name and unit */ +#define PRECISION (-20) /* precision assumed (1 us) */ +#define REFID "IRIG" /* reference ID */ +#define DESCRIPTION "TrueTime 560 IRIG-B PCI Decoder" + +/* + * Unit control structure + */ +struct tt560unit { + tt_mem_space_t *tt_mem; /* mapped address of PCI board */ + time_freeze_reg_t tt560rawt; /* data returned from PCI board */ +}; + +typedef union byteswap_u +{ + unsigned int long_word; + unsigned char byte[4]; +} byteswap_t; + +/* + * Function prototypes + */ +static int tt560_start P((int, struct peer *)); +static void tt560_shutdown P((int, struct peer *)); +static void tt560_poll P((int unit, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_tt560 = { + tt560_start, /* clock_start */ + tt560_shutdown, /* clock_shutdown */ + tt560_poll, /* clock_poll */ + noentry, /* clock_control (not used) */ + noentry, /* clock_init (not used) */ + noentry, /* clock_buginfo (not used) */ + NOFLAGS /* clock_flags (not used) */ +}; + + +/* + * tt560_start - open the TT560 device and initialize data for processing + */ +static int +tt560_start( + int unit, + struct peer *peer + ) +{ + register struct tt560unit *up; + struct refclockproc *pp; + char device[20]; + int fd; + caddr_t membase; + + /* + * Open TT560 device + */ + (void)sprintf(device, DEVICE, unit); + fd = open(device, O_RDWR); + if (fd == -1) { + msyslog(LOG_ERR, "tt560_start: open of %s: %m", device); + return (0); + } + + /* + * Map the device registers into user space. + */ + membase = mmap ((caddr_t) 0, TTIME_MEMORY_SIZE, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, (off_t)0); + + if (membase == (caddr_t) -1) { + msyslog(LOG_ERR, "tt560_start: mapping of %s: %m", device); + (void) close(fd); + return (0); + } + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct tt560unit *) emalloc(sizeof(struct tt560unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct tt560unit)); + up->tt_mem = (tt_mem_space_t *)membase; + pp = peer->procptr; + pp->io.clock_recv = noentry; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous peer variables + */ + peer->precision = PRECISION; + peer->burst = NSTAGE; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * tt560_shutdown - shut down the clock + */ +static void +tt560_shutdown( + int unit, + struct peer *peer + ) +{ + register struct tt560unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct tt560unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * tt560_poll - called by the transmit procedure + */ +static void +tt560_poll( + int unit, + struct peer *peer + ) +{ + register struct tt560unit *up; + struct refclockproc *pp; + time_freeze_reg_t *tp; + tt_mem_space_t *mp; + + int i; + unsigned int *p_time_t, *tt_mem_t; + + /* + * This is the main routine. It snatches the time from the TT560 + * board and tacks on a local timestamp. + */ + pp = peer->procptr; + up = (struct tt560unit *)pp->unitptr; + mp = up->tt_mem; + tp = &up->tt560rawt; + + p_time_t = (unsigned int *)tp; + tt_mem_t = (unsigned int *)&mp->time_freeze_reg; + + *tt_mem_t = 0; /* update the time freeze register */ + /* and copy time stamp to memory */ + for (i=0; i < TIME_FREEZE_REG_LEN; i++) { + *p_time_t = byte_swap(*tt_mem_t); + p_time_t++; + tt_mem_t++; + } + + get_systime(&pp->lastrec); + pp->polls++; + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. Note: we + * can't use the sec/usec conversion produced by the driver, + * since the year may be suspect. All format error checking is + * done by the sprintf() and sscanf() routines. + */ + sprintf(pp->a_lastcode, + "%1x%1x%1x %1x%1x:%1x%1x:%1x%1x.%1x%1x%1x%1x%1x%1x %1x", + tp->hun_day, tp->tens_day, tp->unit_day, + tp->tens_hour, tp->unit_hour, + tp->tens_min, tp->unit_min, + tp->tens_sec, tp->unit_sec, + tp->hun_ms, tp->tens_ms, tp->unit_ms, + tp->hun_us, tp->tens_us, tp->unit_us, + tp->status); + pp->lencode = strlen(pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("tt560: time %s timecode %d %s\n", + ulfptoa(&pp->lastrec, 6), pp->lencode, + pp->a_lastcode); +#endif + if (sscanf(pp->a_lastcode, "%3d %2d:%2d:%2d.%6ld", + &pp->day, &pp->hour, &pp->minute, &pp->second, &pp->usec) + != 5) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + if ((tp->status & 0x6) != 0x6) + pp->leap = LEAP_NOTINSYNC; + else + pp->leap = LEAP_NOWARNING; + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + record_clock_stats(&peer->srcadr, pp->a_lastcode); + refclock_receive(peer); + peer->burst = NSTAGE; +} + +/****************************************************************** + * + * byte_swap + * + * Inputs: 32 bit integer + * + * Output: byte swapped 32 bit integer. + * + * This routine is used to compensate for the byte alignment + * differences between big-endian and little-endian integers. + * + ******************************************************************/ +static unsigned int +byte_swap(unsigned int input_num) +{ + byteswap_t byte_swap; + unsigned char temp; + + byte_swap.long_word = input_num; + + temp = byte_swap.byte[3]; + byte_swap.byte[3] = byte_swap.byte[0]; + byte_swap.byte[0] = temp; + + temp = byte_swap.byte[2]; + byte_swap.byte[2] = byte_swap.byte[1]; + byte_swap.byte[1] = temp; + + return (byte_swap.long_word); +} + +#else +int refclock_tt560_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_ulink.c b/ntpd/refclock_ulink.c new file mode 100644 index 0000000..1f5e78a --- /dev/null +++ b/ntpd/refclock_ulink.c @@ -0,0 +1,497 @@ +/* + * refclock_ulink - clock driver for Ultralink WWVB receiver + * + */ + +/*********************************************************************** + * * + * Copyright (c) David L. Mills 1992-1998 * + * * + * Permission to use, copy, modify, and distribute this software and * + * its documentation for any purpose and without fee is hereby * + * granted, provided that the above copyright notice appears in all * + * copies and that both the copyright notice and this permission * + * notice appear in supporting documentation, and that the name * + * University of Delaware not be used in advertising or publicity * + * pertaining to distribution of the software without specific, * + * written prior permission. The University of Delaware makes no * + * representations about the suitability this software for any * + * purpose. It is provided "as is" without express or implied * + * warranty. * + **********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_ULINK) + +#include <stdio.h> +#include <ctype.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +/* + * This driver supports ultralink Model 320,330,331,332 WWVB radios + * + * this driver was based on the refclock_wwvb.c driver + * in the ntp distribution. + * + * Fudge Factors + * + * fudge flag1 0 don't poll clock + * 1 send poll character + * + * revision history: + * 99/9/09 j.c.lang original edit's + * 99/9/11 j.c.lang changed timecode parse to + * match what the radio actually + * sends. + * 99/10/11 j.c.lang added support for continous + * time code mode (dipsw2) + * 99/11/26 j.c.lang added support for 320 decoder + * (taken from Dave Strout's + * Model 320 driver) + * 99/11/29 j.c.lang added fudge flag 1 to control + * clock polling + * 99/12/15 j.c.lang fixed 320 quality flag + * 01/02/21 s.l.smith fixed 33x quality flag + * added more debugging stuff + * updated 33x time code explanation + * + * Questions, bugs, ideas send to: + * Joseph C. Lang + * tcnojl1@earthlink.net + * + * Dave Strout + * dstrout@linuxfoundry.com + * + * + * on the Ultralink model 33X decoder Dip switch 2 controls + * polled or continous timecode + * set fudge flag1 if using polled (needed for model 320) + * dont set fudge flag1 if dip switch 2 is set on model 33x decoder +*/ + + +/* + * Interface definitions + */ +#define DEVICE "/dev/wwvb%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-10) /* precision assumed (about 10 ms) */ +#define REFID "WWVB" /* reference ID */ +#define DESCRIPTION "Ultralink WWVB Receiver" /* WRU */ + +#define LEN33X 32 /* timecode length Model 325 & 33X */ +#define LEN320 24 /* timecode length Model 320 */ + +/* + * unit control structure + */ +struct ulinkunit { + u_char tcswitch; /* timecode switch */ + l_fp laststamp; /* last receive timestamp */ +}; + +/* + * Function prototypes + */ +static int ulink_start P((int, struct peer *)); +static void ulink_shutdown P((int, struct peer *)); +static void ulink_receive P((struct recvbuf *)); +static void ulink_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_ulink = { + ulink_start, /* start up driver */ + ulink_shutdown, /* shut down driver */ + ulink_poll, /* transmit poll message */ + noentry, /* not used */ + noentry, /* not used */ + noentry, /* not used */ + NOFLAGS +}; + + +/* + * ulink_start - open the devices and initialize data for processing + */ +static int +ulink_start( + int unit, + struct peer *peer + ) +{ + register struct ulinkunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct ulinkunit *) + emalloc(sizeof(struct ulinkunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct ulinkunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = ulink_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + peer->burst = NSTAGE; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + return (1); +} + + +/* + * ulink_shutdown - shut down the clock + */ +static void +ulink_shutdown( + int unit, + struct peer *peer + ) +{ + register struct ulinkunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct ulinkunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * ulink_receive - receive data from the serial interface + */ +static void +ulink_receive( + struct recvbuf *rbufp + ) +{ + struct ulinkunit *up; + struct refclockproc *pp; + struct peer *peer; + + l_fp trtmp; /* arrival timestamp */ + int quality; /* quality indicator */ + int temp; /* int temp */ + char syncchar; /* synchronization indicator */ + char leapchar; /* leap indicator */ + char modechar; /* model 320 mode flag */ + char char_quality[2]; /* temp quality flag */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct ulinkunit *)pp->unitptr; + temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + + /* + * Note we get a buffer and timestamp for both a <cr> and <lf>, + * but only the <cr> timestamp is retained. + */ + if (temp == 0) { + if (up->tcswitch == 0) { + up->tcswitch = 1; + up->laststamp = trtmp; + } else + up->tcswitch = 0; + return; + } + pp->lencode = temp; + pp->lastrec = up->laststamp; + up->laststamp = trtmp; + up->tcswitch = 1; +#ifdef DEBUG + if (debug) + printf("ulink: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + syncchar = leapchar = modechar = ' '; + switch (pp->lencode ) { + case LEN33X: + /* + * Model 33X decoder: + * Timecode format from January 29, 2001 datasheet is: + * <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5 + * S WWVB decoder sync indicator. S for in-sync(?) + * or N for noisy signal. + * 9+ RF signal level in S-units, 0-9 followed by + * a space (0x20). The space turns to '+' if the + * level is over 9. + * D Data bit 0, 1, 2 (position mark), or + * 3 (unknown). + * space Space character (0x20) + * 00 Hours since last good WWVB frame sync. Will + * be 00-23 hrs, or '1d' to '7d'. Will be 'Lk' + * if currently in sync. + * space Space character (0x20) + * YYYY Current year, 1990-2089 + * + Leap year indicator. '+' if a leap year, + * a space (0x20) if not. + * DDD Day of year, 001 - 366. + * UTC Timezone (always 'UTC'). + * S Daylight savings indicator + * S - standard time (STD) in effect + * O - during STD to DST day 0000-2400 + * D - daylight savings time (DST) in effect + * I - during DST to STD day 0000-2400 + * space Space character (0x20) + * HH Hours 00-23 + * : This is the REAL in sync indicator (: = insync) + * MM Minutes 00-59 + * : : = in sync ? = NOT in sync + * SS Seconds 00-59 + * L Leap second flag. Changes from space (0x20) + * to '+' or '-' during month preceding leap + * second adjustment. + * +5 UT1 correction (sign + digit )) + */ + + if (sscanf(pp->a_lastcode, + "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c", + char_quality, &pp->year, &pp->day, + &pp->hour, &syncchar, &pp->minute, &pp->second, + &leapchar) == 8) { + + if (char_quality[0] == 'L') { + quality = 0; + } else if (char_quality[0] == '0') { + quality = (char_quality[1] & 0x0f); + } else { + quality = 99; + } + +/* +#ifdef DEBUG + if (debug) { + printf("ulink: char_quality %c %c\n", + char_quality[0], char_quality[1]); + printf("ulink: quality %d\n", quality); + printf("ulink: syncchar %x\n", syncchar); + printf("ulink: leapchar %x\n", leapchar); + } +#endif +*/ + + break; + } + + case LEN320: + /* + * Model 320 Decoder + * The timecode format is: + * + * <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr> + * + * where: + * + * S = 'S' -- sync'd in last hour, + * '0'-'9' - hours x 10 since last update, + * '?' -- not in sync + * Q = Number of correlating time-frames, from 0 to 5 + * R = 'R' -- reception in progress, + * 'N' -- Noisy reception, + * ' ' -- standby mode + * YYYY = year from 1990 to 2089 + * DDD = current day from 1 to 366 + * + = '+' if current year is a leap year, else ' ' + * HH = UTC hour 0 to 23 + * MM = Minutes of current hour from 0 to 59 + * SS = Seconds of current minute from 0 to 59 + * mm = 10's milliseconds of the current second from 00 to 99 + * L = Leap second pending at end of month + * 'I' = insert, 'D'= delete + * T = DST <-> STD transition indicators + * + */ + if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c", + &syncchar, &quality, &modechar, &pp->year, &pp->day, + &pp->hour, &pp->minute, &pp->second, + &pp->nsec, &leapchar) == 10) { + pp->nsec *= 10000000; /* M320 returns 10's of msecs */ + if (leapchar == 'I' ) leapchar = '+'; + if (leapchar == 'D' ) leapchar = '-'; + if (syncchar != '?' ) syncchar = ':'; + + break; + } + + default: + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + + /* + * Decode quality indicator + * For the 325 & 33x series, the lower the number the "better" + * the time is. I used the dispersion as the measure of time + * quality. The quality indicator in the 320 is the number of + * correlating time frames (the more the better) + */ + + /* + * The spec sheet for the 325 & 33x series states the clock will + * maintain +/-0.002 seconds accuracy when locked to WWVB. This + * is indicated by 'Lk' in the quality portion of the incoming + * string. When not in lock, a drift of +/-0.015 seconds should + * be allowed for. + * With the quality indicator decoding scheme above, the 'Lk' + * condition will produce a quality value of 0. If the quality + * indicator starts with '0' then the second character is the + * number of hours since we were last locked. If the first + * character is anything other than 'L' or '0' then we have been + * out of lock for more than 9 hours so we assume the worst and + * force a quality value that selects the 'default' maximum + * dispersion. The dispersion values below are what came with the + * driver. They're not unreasonable so they've not been changed. + */ + + if (pp->lencode == LEN33X) { + switch (quality) { + case 0 : + pp->disp=.002; + break; + case 1 : + pp->disp=.02; + break; + case 2 : + pp->disp=.04; + break; + case 3 : + pp->disp=.08; + break; + default: + pp->disp=MAXDISPERSE; + break; + } + } else { + switch (quality) { + case 5 : + pp->disp=.002; + break; + case 4 : + pp->disp=.02; + break; + case 3 : + pp->disp=.04; + break; + case 2 : + pp->disp=.08; + break; + case 1 : + pp->disp=.16; + break; + default: + pp->disp=MAXDISPERSE; + break; + } + + } + + /* + * Decode synchronization, and leap characters. If + * unsynchronized, set the leap bits accordingly and exit. + * Otherwise, set the leap bits according to the leap character. + */ + + if (syncchar != ':') + pp->leap = LEAP_NOTINSYNC; + else if (leapchar == '+') + pp->leap = LEAP_ADDSECOND; + else if (leapchar == '-') + pp->leap = LEAP_DELSECOND; + else + pp->leap = LEAP_NOWARNING; + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + } + +} + + +/* + * ulink_poll - called by the transmit procedure + */ +static void +ulink_poll( + int unit, + struct peer *peer + ) +{ + struct refclockproc *pp; + char pollchar; + + pp = peer->procptr; + pollchar = 'T'; + if (pp->sloppyclockflag & CLK_FLAG1) { + if (write(pp->io.fd, &pollchar, 1) != 1) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; + } + else + pp->polls++; + + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + peer->burst = NSTAGE; + +} + +#else +int refclock_ulink_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_usno.c b/ntpd/refclock_usno.c new file mode 100644 index 0000000..057eef9 --- /dev/null +++ b/ntpd/refclock_usno.c @@ -0,0 +1,674 @@ +/* + * refclock_usno - clock driver for the Naval Observatory dialup + * Michael Shields <shields@tembel.org> 1995/02/25 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_USNO) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_control.h" + +#include <stdio.h> +#include <ctype.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +/* + * This driver supports the Naval Observatory dialup at +1 202 653 0351. + * It is a hacked-up version of the ACTS driver. + * + * This driver does not support the `phone' configuration because that + * is needlessly global; it would clash with the ACTS driver. + * + * The Naval Observatory does not support the echo-delay measurement scheme. + * + * However, this driver *does* support UUCP port locking, allowing the + * line to be shared with other processes when not actually dialing + * for time. + */ + +/* + * Interface definitions + */ + +#define DEVICE "/dev/cua%d" /* device name and unit */ +#define LOCKFILE "/var/lock/LCK..cua%d" +/* #define LOCKFILE "/usr/spool/uucp/LCK..cua%d" */ + +#define PHONE "atdt 202 653 0351" +/* #define PHONE "atdt 1 202 653 0351" */ + +#define SPEED232 B1200 /* uart speed (1200 cowardly baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "USNO" /* reference ID */ +#define DESCRIPTION "Naval Observatory dialup" + +#define MODE_AUTO 0 /* automatic mode */ +#define MODE_BACKUP 1 /* backup mode */ +#define MODE_MANUAL 2 /* manual mode */ + +#define MSGCNT 10 /* we need this many time messages */ +#define SMAX 80 /* max token string length */ +#define LENCODE 20 /* length of valid timecode string */ +#define USNO_MINPOLL 10 /* log2 min poll interval (1024 s) */ +#define USNO_MAXPOLL 14 /* log2 max poll interval (16384 s) */ +#define MAXOUTAGE 3600 /* max before USNO kicks in (s) */ + +/* + * Modem control strings. These may have to be changed for some modems. + * + * AT command prefix + * B1 initiate call negotiation using Bell 212A + * &C1 enable carrier detect + * &D2 hang up and return to command mode on DTR transition + * E0 modem command echo disabled + * l1 set modem speaker volume to low level + * M1 speaker enabled untill carrier detect + * Q0 return result codes + * V1 return result codes as English words + */ +#define MODEM_SETUP "ATB1&C1&D2E0L1M1Q0V1" /* modem setup */ +#define MODEM_HANGUP "ATH" /* modem disconnect */ + +/* + * Timeouts + */ +#define IDLE 60 /* idle timeout (s) */ +#define WAIT 2 /* wait timeout (s) */ +#define ANSWER 30 /* answer timeout (s) */ +#define CONNECT 10 /* connect timeout (s) */ +#define TIMECODE (MSGCNT+16) /* timecode timeout (s) */ + +/* + * Unit control structure + */ +struct usnounit { + int pollcnt; /* poll message counter */ + + int state; /* the first one was Delaware */ + int run; /* call program run switch */ + int msgcnt; /* count of time messages received */ + long redial; /* interval to next automatic call */ + int unit; /* unit number (= port) */ +}; + +/* + * Function prototypes + */ +static int usno_start P((int, struct peer *)); +static void usno_shutdown P((int, struct peer *)); +static void usno_poll P((int, struct peer *)); +static void usno_disc P((struct peer *)); +#if 0 +static void usno_timeout P((struct peer *)); +static void usno_receive P((struct recvbuf *)); +static int usno_write P((struct peer *, const char *)); +#endif /* 0 */ + +/* + * Transfer vector + */ +struct refclock refclock_usno = { + usno_start, /* start up driver */ + usno_shutdown, /* shut down driver */ + usno_poll, /* transmit poll message */ + noentry, /* not used (usno_control) */ + noentry, /* not used (usno_init) */ + noentry, /* not used (usno_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * usno_start - open the devices and initialize data for processing + */ +static int +usno_start( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + + /* + * Initialize miscellaneous variables + */ + pp = peer->procptr; + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + peer->minpoll = USNO_MINPOLL; + peer->maxpoll = USNO_MAXPOLL; + peer->sstclktype = CTL_SST_TS_TELEPHONE; + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct usnounit *) + emalloc(sizeof(struct usnounit)))) + return (0); + memset((char *)up, 0, sizeof(struct usnounit)); + up->unit = unit; + pp->unitptr = (caddr_t)up; + + /* + * Set up the driver timeout + */ + peer->nextdate = current_time + WAIT; + return (1); +} + + +/* + * usno_shutdown - shut down the clock + */ +static void +usno_shutdown( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + +#ifdef DEBUG + if (debug) + printf("usno: clock %s shutting down\n", ntoa(&peer->srcadr)); +#endif + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + usno_disc(peer); + free(up); +} + + +#if 0 +/* + * usno_receive - receive data from the serial interface + */ +static void +usno_receive( + struct recvbuf *rbufp + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + struct peer *peer; + char str[SMAX]; + u_long mjd; /* Modified Julian Day */ + static int day, hour, minute, second; + + /* + * Initialize pointers and read the timecode and timestamp. If + * the OK modem status code, leave it where folks can find it. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, + &pp->lastrec); + if (pp->lencode == 0) { + if (strcmp(pp->a_lastcode, "OK") == 0) + pp->lencode = 2; + return; + } +#ifdef DEBUG + if (debug) + printf("usno: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + switch (up->state) { + + case 0: + + /* + * State 0. We are not expecting anything. Probably + * modem disconnect noise. Go back to sleep. + */ + return; + + case 1: + + /* + * State 1. We are about to dial. Just drop it. + */ + return; + + case 2: + + /* + * State 2. We are waiting for the call to be answered. + * All we care about here is CONNECT as the first token + * in the string. If the modem signals BUSY, ERROR, NO + * ANSWER, NO CARRIER or NO DIALTONE, we immediately + * hang up the phone. If CONNECT doesn't happen after + * ANSWER seconds, hang up the phone. If everything is + * okay, start the connect timeout and slide into state + * 3. + */ + (void)strncpy(str, strtok(pp->a_lastcode, " "), SMAX); + if (strcmp(str, "BUSY") == 0 || strcmp(str, "ERROR") == + 0 || strcmp(str, "NO") == 0) { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO modem status %s", + ntoa(&peer->srcadr), pp->a_lastcode); + usno_disc(peer); + } else if (strcmp(str, "CONNECT") == 0) { + peer->nextdate = current_time + CONNECT; + up->msgcnt = 0; + up->state++; + } else { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_WARNING, + "clock %s USNO unknown modem status %s", + ntoa(&peer->srcadr), pp->a_lastcode); + } + return; + + case 3: + + /* + * State 3. The call has been answered and we are + * waiting for the first message. If this doesn't + * happen within the timecode timeout, hang up the + * phone. We probably got a wrong number or they are + * down. + */ + peer->nextdate = current_time + TIMECODE; + up->state++; + return; + + case 4: + + /* + * State 4. We are reading a timecode. It's an actual + * timecode, or it's the `*' OTM. + * + * jjjjj nnn hhmmss UTC + */ + if (pp->lencode == LENCODE) { + if (sscanf(pp->a_lastcode, "%5ld %3d %2d%2d%2d UTC", + &mjd, &day, &hour, &minute, &second) != 5) { +#ifdef DEBUG + if (debug) + printf("usno: bad timecode format\n"); +#endif + refclock_report(peer, CEVNT_BADREPLY); + } else + up->msgcnt++; + return; + } else if (pp->lencode != 1 || !up->msgcnt) + return; + /* else, OTM; drop out of switch */ + } + + pp->leap = LEAP_NOWARNING; + pp->day = day; + pp->hour = hour; + pp->minute = minute; + pp->second = second; + + /* + * Colossal hack here. We process each sample in a trimmed-mean + * filter and determine the reference clock offset and + * dispersion. The fudge time1 value is added to each sample as + * received. + */ + if (!refclock_process(pp)) { +#ifdef DEBUG + if (debug) + printf("usno: time rejected\n"); +#endif + refclock_report(peer, CEVNT_BADTIME); + return; + } else if (up->msgcnt < MSGCNT) + return; + + /* + * We have a filtered sample offset ready for peer processing. + * We use lastrec as both the reference time and receive time in + * order to avoid being cute, like setting the reference time + * later than the receive time, which may cause a paranoid + * protocol module to chuck out the data. Finaly, we unhook the + * timeout, arm for the next call, fold the tent and go home. + */ + pp->lastref = pp->lastrec; + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); + pp->sloppyclockflag &= ~CLK_FLAG1; + up->pollcnt = 0; + up->state = 0; + usno_disc(peer); +} +#endif /* 0 */ + + +/* + * usno_poll - called by the transmit routine + */ +static void +usno_poll( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + + /* + * If the driver is running, we set the enable flag (fudge + * flag1), which causes the driver timeout routine to initiate a + * call. If not, the enable flag can be set using + * ntpdc. If this is the sustem peer, then follow the system + * poll interval. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + if (up->run) { + pp->sloppyclockflag |= CLK_FLAG1; + if (peer == sys_peer) + peer->hpoll = sys_poll; + else + peer->hpoll = peer->minpoll; + } +} + + +#if 0 +/* + * usno_timeout - called by the timer interrupt + */ +static void +usno_timeout( + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int fd; + char device[20]; + char lockfile[128], pidbuf[8]; + int dtr = TIOCM_DTR; + + /* + * If a timeout occurs in other than state 0, the call has + * failed. If in state 0, we just see if there is other work to + * do. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + if (up->state) { + if (up->state != 1) { + usno_disc(peer); + return; + } + /* + * Call, and start the answer timeout. We think it + * strange if the OK status has not been received from + * the modem, but plow ahead anyway. + * + * This code is *here* because we had to stick in a brief + * delay to let the modem settle down after raising DTR, + * and for the OK to be received. State machines are + * contorted. + */ + if (strcmp(pp->a_lastcode, "OK") != 0) + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s USNO no modem status", + ntoa(&peer->srcadr)); + (void)usno_write(peer, PHONE); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s USNO calling %s\n", + ntoa(&peer->srcadr), PHONE); + up->state = 2; + up->pollcnt++; + pp->polls++; + peer->nextdate = current_time + ANSWER; + return; + } + switch (peer->ttl) { + + /* + * In manual mode the calling program is activated + * by the ntpdc program using the enable flag (fudge + * flag1), either manually or by a cron job. + */ + case MODE_MANUAL: + up->run = 0; + break; + + /* + * In automatic mode the calling program runs + * continuously at intervals determined by the sys_poll + * variable. + */ + case MODE_AUTO: + if (!up->run) + pp->sloppyclockflag |= CLK_FLAG1; + up->run = 1; + break; + + /* + * In backup mode the calling program is disabled, + * unless no system peer has been selected for MAXOUTAGE + * (3600 s). Once enabled, it runs until some other NTP + * peer shows up. + */ + case MODE_BACKUP: + if (!up->run && sys_peer == 0) { + if (current_time - last_time > MAXOUTAGE) { + up->run = 1; + peer->hpoll = peer->minpoll; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO backup started ", + ntoa(&peer->srcadr)); + } + } else if (up->run && sys_peer->sstclktype != CTL_SST_TS_TELEPHONE) { + peer->hpoll = peer->minpoll; + up->run = 0; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO backup stopped", + ntoa(&peer->srcadr)); + } + break; + + default: + msyslog(LOG_ERR, + "clock %s USNO invalid mode", ntoa(&peer->srcadr)); + + } + + /* + * The fudge flag1 is used as an enable/disable; if set either + * by the code or via ntpdc, the calling program is + * started; if reset, the phones stop ringing. + */ + if (!(pp->sloppyclockflag & CLK_FLAG1)) { + up->pollcnt = 0; + peer->nextdate = current_time + IDLE; + return; + } + + /* + * Lock the port. + */ + (void)sprintf(lockfile, LOCKFILE, up->unit); + fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd < 0) { + msyslog(LOG_ERR, "clock %s USNO port busy", + ntoa(&peer->srcadr)); + return; + } + sprintf(pidbuf, "%d\n", (int) getpid()); + write(fd, pidbuf, strlen(pidbuf)); + close(fd); + + /* + * Open serial port. Use ACTS line discipline, if available. It + * pumps a timestamp into the data stream at every on-time + * character '*' found. Note: the port must have modem control + * or deep pockets for the phone bill. HP-UX 9.03 users should + * have very deep pockets. + */ + (void)sprintf(device, DEVICE, up->unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_ACTS))) { + unlink(lockfile); + return; + } + if (ioctl(fd, TIOCMBIC, (char *)&dtr) < 0) + msyslog(LOG_WARNING, "usno_timeout: clock %s: couldn't clear DTR: %m", + ntoa(&peer->srcadr)); + + pp->io.clock_recv = usno_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + unlink(lockfile); + free(up); + return; + } + + /* + * Initialize modem and kill DTR. We skedaddle if this comes + * bum. + */ + if (!usno_write(peer, MODEM_SETUP)) { + msyslog(LOG_ERR, "clock %s USNO couldn't write", + ntoa(&peer->srcadr)); + io_closeclock(&pp->io); + unlink(lockfile); + free(up); + return; + } + + /* + * Initiate a call to the Observatory. If we wind up here in + * other than state 0, a successful call could not be completed + * within minpoll seconds. + */ + if (up->pollcnt) { + refclock_report(peer, CEVNT_TIMEOUT); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO calling program terminated", + ntoa(&peer->srcadr)); + pp->sloppyclockflag &= ~CLK_FLAG1; + up->pollcnt = 0; +#ifdef DEBUG + if (debug) + printf("usno: calling program terminated\n"); +#endif + usno_disc(peer); + return; + } + + /* + * Raise DTR, and let the modem settle. Then we'll dial. + */ + if (ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr) < -1) + msyslog(LOG_INFO, "usno_timeout: clock %s: couldn't set DTR: %m", + ntoa(&peer->srcadr)); + up->state = 1; + peer->nextdate = current_time + WAIT; +} +#endif /* 0 */ + + +/* + * usno_disc - disconnect the call and wait for the ruckus to cool + */ +static void +usno_disc( + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int dtr = TIOCM_DTR; + char lockfile[128]; + + /* + * We should never get here other than in state 0, unless a call + * has timed out. We drop DTR, which will reliably get the modem + * off the air, even while the modem is hammering away full tilt. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + + if (ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr) < 0) + msyslog(LOG_INFO, "usno_disc: clock %s: couldn't clear DTR: %m", + ntoa(&peer->srcadr)); + + if (up->state > 0) { + up->state = 0; + msyslog(LOG_NOTICE, "clock %s USNO call failed %d", + ntoa(&peer->srcadr), up->state); +#ifdef DEBUG + if (debug) + printf("usno: call failed %d\n", up->state); +#endif + } + + io_closeclock(&pp->io); + sprintf(lockfile, LOCKFILE, up->unit); + unlink(lockfile); + + peer->nextdate = current_time + WAIT; +} + + +#if 0 +/* + * usno_write - write a message to the serial port + */ +static int +usno_write( + struct peer *peer, + const char *str + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int len; + int code; + char cr = '\r'; + + /* + * Not much to do here, other than send the message, handle + * debug and report faults. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + len = strlen(str); +#ifdef DEBUG + if (debug) + printf("usno: state %d send %d %s\n", up->state, len, + str); +#endif + code = write(pp->io.fd, str, (unsigned)len) == len; + code |= write(pp->io.fd, &cr, 1) == 1; + if (!code) + refclock_report(peer, CEVNT_FAULT); + return (code); +} +#endif /* 0 */ + +#else +int refclock_usno_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_wwv.c b/ntpd/refclock_wwv.c new file mode 100644 index 0000000..11aae7f --- /dev/null +++ b/ntpd/refclock_wwv.c @@ -0,0 +1,2859 @@ +/* + * refclock_wwv - clock driver for NIST WWV/H time/frequency station + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_WWV) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" +#include "audio.h" + +#include <stdio.h> +#include <ctype.h> +#include <math.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +#define ICOM 1 + +#ifdef ICOM +#include "icom.h" +#endif /* ICOM */ + +/* + * Audio WWV/H demodulator/decoder + * + * This driver synchronizes the computer time using data encoded in + * radio transmissions from NIST time/frequency stations WWV in Boulder, + * CO, and WWVH in Kauai, HI. Transmissions are made continuously on + * 2.5, 5, 10, 15 and 20 MHz in AM mode. An ordinary shortwave receiver + * can be tuned manually to one of these frequencies or, in the case of + * ICOM receivers, the receiver can be tuned automatically using this + * program as propagation conditions change throughout the day and + * night. + * + * The driver receives, demodulates and decodes the radio signals when + * connected to the audio codec of a workstation running Solaris, SunOS + * FreeBSD or Linux, and with a little help, other workstations with + * similar codecs or sound cards. In this implementation, only one audio + * driver and codec can be supported on a single machine. + * + * The demodulation and decoding algorithms used in this driver are + * based on those developed for the TAPR DSP93 development board and the + * TI 320C25 digital signal processor described in: Mills, D.L. A + * precision radio clock for WWV transmissions. Electrical Engineering + * Report 97-8-1, University of Delaware, August 1997, 25 pp., available + * from www.eecis.udel.edu/~mills/reports.htm. The algorithms described + * in this report have been modified somewhat to improve performance + * under weak signal conditions and to provide an automatic station + * identification feature. + * + * The ICOM code is normally compiled in the driver. It isn't used, + * unless the mode keyword on the server configuration command specifies + * a nonzero ICOM ID select code. The C-IV trace is turned on if the + * debug level is greater than one. + */ +/* + * Interface definitions + */ +#define DEVICE_AUDIO "/dev/audio" /* audio device name */ +#define AUDIO_BUFSIZ 320 /* audio buffer size (50 ms) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define DESCRIPTION "WWV/H Audio Demodulator/Decoder" /* WRU */ +#define SECOND 8000 /* second epoch (sample rate) (Hz) */ +#define MINUTE (SECOND * 60) /* minute epoch */ +#define OFFSET 128 /* companded sample offset */ +#define SIZE 256 /* decompanding table size */ +#define MAXSIG 6000. /* max signal level reference */ +#define MAXCLP 100 /* max clips above reference per s */ +#define MAXSNR 30. /* max SNR reference */ +#define DGAIN 20. /* data channel gain reference */ +#define SGAIN 10. /* sync channel gain reference */ +#define MAXFREQ 1. /* max frequency tolerance (125 PPM) */ +#define PI 3.1415926535 /* the real thing */ +#define DATSIZ (170 * MS) /* data matched filter size */ +#define SYNSIZ (800 * MS) /* minute sync matched filter size */ +#define MAXERR 30 /* max data bit errors in minute */ +#define NCHAN 5 /* number of radio channels */ +#define AUDIO_PHI 5e-6 /* dispersion growth factor */ +#ifdef IRIG_SUCKS +#define WIGGLE 11 /* wiggle filter length */ +#endif /* IRIG_SUCKS */ + +/* + * General purpose status bits (status) + * + * SELV and/or SELH are set when WWV or WWVH has been heard and cleared + * on signal loss. SSYNC is set when the second sync pulse has been + * acquired and cleared by signal loss. MSYNC is set when the minute + * sync pulse has been acquired. DSYNC is set when a digit reaches the + * threshold and INSYNC is set when all nine digits have reached the + * threshold. The MSYNC, DSYNC and INSYNC bits are cleared only by + * timeout, upon which the driver starts over from scratch. + * + * DGATE is set if a data bit is invalid and BGATE is set if a BCD digit + * bit is invalid. SFLAG is set when during seconds 59, 0 and 1 while + * probing alternate frequencies. LEPDAY is set when SECWAR of the + * timecode is set on 30 June or 31 December. LEPSEC is set during the + * last minute of the day when LEPDAY is set. At the end of this minute + * the driver inserts second 60 in the seconds state machine and the + * minute sync slips a second. The SLOSS and SJITR bits are for monitor + * only. + */ +#define MSYNC 0x0001 /* minute epoch sync */ +#define SSYNC 0x0002 /* second epoch sync */ +#define DSYNC 0x0004 /* minute units sync */ +#define INSYNC 0x0008 /* clock synchronized */ +#define FGATE 0x0010 /* frequency gate */ +#define DGATE 0x0020 /* data bit error */ +#define BGATE 0x0040 /* BCD digit bit error */ +#define SFLAG 0x1000 /* probe flag */ +#define LEPDAY 0x2000 /* leap second day */ +#define LEPSEC 0x4000 /* leap second minute */ + +/* + * Station scoreboard bits + * + * These are used to establish the signal quality for each of the five + * frequencies and two stations. + */ +#define SYNCNG 0x0001 /* sync or SNR below threshold */ +#define DATANG 0x0002 /* data or SNR below threshold */ +#define ERRRNG 0x0004 /* data error */ +#define SELV 0x0100 /* WWV station select */ +#define SELH 0x0200 /* WWVH station select */ + +/* + * Alarm status bits (alarm) + * + * These bits indicate various alarm conditions, which are decoded to + * form the quality character included in the timecode. If not tracking + * second sync, the SYNERR alarm is raised. The data error counter is + * incremented for each invalid data bit. If too many data bit errors + * are encountered in one minute, the MODERR alarm is raised. The DECERR + * alarm is raised if a maximum likelihood digit fails to compare with + * the current clock digit. If the probability of any miscellaneous bit + * or any digit falls below the threshold, the SYMERR alarm is raised. + */ +#define DECERR 1 /* BCD digit compare error */ +#define SYMERR 2 /* low bit or digit probability */ +#define MODERR 4 /* too many data bit errors */ +#define SYNERR 8 /* not synchronized to station */ + +/* + * Watchcat timeouts (watch) + * + * If these timeouts expire, the status bits are mashed to zero and the + * driver starts from scratch. Suitably more refined procedures may be + * developed in future. All these are in minutes. + */ +#define ACQSN 5 /* station acquisition timeout */ +#define DIGIT 30 /* minute unit digit timeout */ +#define HOLD 30 /* reachable timeout */ +#define PANIC (2 * 1440) /* panic timeout */ + +/* + * Thresholds. These establish the minimum signal level, minimum SNR and + * maximum jitter thresholds which establish the error and false alarm + * rates of the driver. The values defined here may be on the + * adventurous side in the interest of the highest sensitivity. + */ +#define MTHR 13. /* acquisition signal gate (percent) */ +#define TTHR 50. /* tracking signal gate (percent) */ +#define ATHR 2000. /* acquisition amplitude threshold */ +#define ASNR 6. /* acquisition SNR threshold (dB) */ +#define AWND 20. /* acquisition jitter threshold (ms) */ +#define AMIN 3 /* min bit count */ +#define AMAX 6 /* max bit count */ +#define QTHR 2000 /* QSY sync threshold */ +#define QSNR 20. /* QSY sync SNR threshold (dB) */ +#define XTHR 1000. /* QSY data threshold */ +#define XSNR 10. /* QSY data SNR threshold (dB) */ +#define STHR 500 /* second sync amplitude threshold */ +#define SSNR 10. /* second sync SNR threshold */ +#define SCMP 10 /* second sync compare threshold */ +#define DTHR 1000 /* bit amplitude threshold */ +#define DSNR 10. /* bit SNR threshold (dB) */ +#define BTHR 1000 /* digit amplitude threshold */ +#define BSNR 3. /* digit likelihood threshold (dB) */ +#define BCMP 5 /* digit compare threshold */ + +/* + * Tone frequency definitions. The increments are for 4.5-deg sine + * table. + */ +#define MS (SECOND / 1000) /* samples per millisecond */ +#define IN100 ((100 * 80) / SECOND) /* 100 Hz increment */ +#define IN1000 ((1000 * 80) / SECOND) /* 1000 Hz increment */ +#define IN1200 ((1200 * 80) / SECOND) /* 1200 Hz increment */ + +/* + * Acquisition and tracking time constants. Usually powers of 2. + */ +#define MINAVG 8 /* min time constant */ +#define MAXAVG 1024 /* max time constant */ +#define TCONST 16 /* data bit/digit time constant */ + +/* + * Miscellaneous status bits (misc) + * + * These bits correspond to designated bits in the WWV/H timecode. The + * bit probabilities are exponentially averaged over several minutes and + * processed by a integrator and threshold. + */ +#define DUT1 0x01 /* 56 DUT .1 */ +#define DUT2 0x02 /* 57 DUT .2 */ +#define DUT4 0x04 /* 58 DUT .4 */ +#define DUTS 0x08 /* 50 DUT sign */ +#define DST1 0x10 /* 55 DST1 leap warning */ +#define DST2 0x20 /* 2 DST2 DST1 delayed one day */ +#define SECWAR 0x40 /* 3 leap second warning */ + +/* + * The on-time synchronization point for the driver is the second epoch + * sync pulse produced by the FIR matched filters. As the 5-ms delay of + * these filters is compensated, the program delay is 1.1 ms due to the + * 600-Hz IIR bandpass filter. The measured receiver delay is 4.7 ms and + * the codec delay less than 0.2 ms. The additional propagation delay + * specific to each receiver location can be programmed in the fudge + * time1 and time2 values for WWV and WWVH, respectively. + */ +#define PDELAY (.0011 + .0047 + .0002) /* net system delay (s) */ + +/* + * Table of sine values at 4.5-degree increments. This is used by the + * synchronous matched filter demodulators. The integral of sine-squared + * over one complete cycle is PI, so the table is normallized by 1 / PI. + */ +double sintab[] = { + 0.000000e+00, 2.497431e-02, 4.979464e-02, 7.430797e-02, /* 0-3 */ + 9.836316e-02, 1.218119e-01, 1.445097e-01, 1.663165e-01, /* 4-7 */ + 1.870979e-01, 2.067257e-01, 2.250791e-01, 2.420447e-01, /* 8-11 */ + 2.575181e-01, 2.714038e-01, 2.836162e-01, 2.940800e-01, /* 12-15 */ + 3.027307e-01, 3.095150e-01, 3.143910e-01, 3.173286e-01, /* 16-19 */ + 3.183099e-01, 3.173286e-01, 3.143910e-01, 3.095150e-01, /* 20-23 */ + 3.027307e-01, 2.940800e-01, 2.836162e-01, 2.714038e-01, /* 24-27 */ + 2.575181e-01, 2.420447e-01, 2.250791e-01, 2.067257e-01, /* 28-31 */ + 1.870979e-01, 1.663165e-01, 1.445097e-01, 1.218119e-01, /* 32-35 */ + 9.836316e-02, 7.430797e-02, 4.979464e-02, 2.497431e-02, /* 36-39 */ +-0.000000e+00, -2.497431e-02, -4.979464e-02, -7.430797e-02, /* 40-43 */ +-9.836316e-02, -1.218119e-01, -1.445097e-01, -1.663165e-01, /* 44-47 */ +-1.870979e-01, -2.067257e-01, -2.250791e-01, -2.420447e-01, /* 48-51 */ +-2.575181e-01, -2.714038e-01, -2.836162e-01, -2.940800e-01, /* 52-55 */ +-3.027307e-01, -3.095150e-01, -3.143910e-01, -3.173286e-01, /* 56-59 */ +-3.183099e-01, -3.173286e-01, -3.143910e-01, -3.095150e-01, /* 60-63 */ +-3.027307e-01, -2.940800e-01, -2.836162e-01, -2.714038e-01, /* 64-67 */ +-2.575181e-01, -2.420447e-01, -2.250791e-01, -2.067257e-01, /* 68-71 */ +-1.870979e-01, -1.663165e-01, -1.445097e-01, -1.218119e-01, /* 72-75 */ +-9.836316e-02, -7.430797e-02, -4.979464e-02, -2.497431e-02, /* 76-79 */ + 0.000000e+00}; /* 80 */ + +/* + * Decoder operations at the end of each second are driven by a state + * machine. The transition matrix consists of a dispatch table indexed + * by second number. Each entry in the table contains a case switch + * number and argument. + */ +struct progx { + int sw; /* case switch number */ + int arg; /* argument */ +}; + +/* + * Case switch numbers + */ +#define IDLE 0 /* no operation */ +#define COEF 1 /* BCD bit */ +#define COEF2 2 /* BCD bit ignored */ +#define DECIM9 3 /* BCD digit 0-9 */ +#define DECIM6 4 /* BCD digit 0-6 */ +#define DECIM3 5 /* BCD digit 0-3 */ +#define DECIM2 6 /* BCD digit 0-2 */ +#define MSCBIT 7 /* miscellaneous bit */ +#define MSC20 8 /* miscellaneous bit */ +#define MSC21 9 /* QSY probe channel */ +#define MIN1 10 /* minute */ +#define MIN2 11 /* leap second */ +#define SYNC2 12 /* QSY data channel */ +#define SYNC3 13 /* QSY data channel */ + +/* + * Offsets in decoding matrix + */ +#define MN 0 /* minute digits (2) */ +#define HR 2 /* hour digits (2) */ +#define DA 4 /* day digits (3) */ +#define YR 7 /* year digits (2) */ + +struct progx progx[] = { + {SYNC2, 0}, /* 0 latch sync max */ + {SYNC3, 0}, /* 1 QSY data channel */ + {MSCBIT, DST2}, /* 2 dst2 */ + {MSCBIT, SECWAR}, /* 3 lw */ + {COEF, 0}, /* 4 1 year units */ + {COEF, 1}, /* 5 2 */ + {COEF, 2}, /* 6 4 */ + {COEF, 3}, /* 7 8 */ + {DECIM9, YR}, /* 8 */ + {IDLE, 0}, /* 9 p1 */ + {COEF, 0}, /* 10 1 minute units */ + {COEF, 1}, /* 11 2 */ + {COEF, 2}, /* 12 4 */ + {COEF, 3}, /* 13 8 */ + {DECIM9, MN}, /* 14 */ + {COEF, 0}, /* 15 10 minute tens */ + {COEF, 1}, /* 16 20 */ + {COEF, 2}, /* 17 40 */ + {COEF2, 3}, /* 18 80 (not used) */ + {DECIM6, MN + 1}, /* 19 p2 */ + {COEF, 0}, /* 20 1 hour units */ + {COEF, 1}, /* 21 2 */ + {COEF, 2}, /* 22 4 */ + {COEF, 3}, /* 23 8 */ + {DECIM9, HR}, /* 24 */ + {COEF, 0}, /* 25 10 hour tens */ + {COEF, 1}, /* 26 20 */ + {COEF2, 2}, /* 27 40 (not used) */ + {COEF2, 3}, /* 28 80 (not used) */ + {DECIM2, HR + 1}, /* 29 p3 */ + {COEF, 0}, /* 30 1 day units */ + {COEF, 1}, /* 31 2 */ + {COEF, 2}, /* 32 4 */ + {COEF, 3}, /* 33 8 */ + {DECIM9, DA}, /* 34 */ + {COEF, 0}, /* 35 10 day tens */ + {COEF, 1}, /* 36 20 */ + {COEF, 2}, /* 37 40 */ + {COEF, 3}, /* 38 80 */ + {DECIM9, DA + 1}, /* 39 p4 */ + {COEF, 0}, /* 40 100 day hundreds */ + {COEF, 1}, /* 41 200 */ + {COEF2, 2}, /* 42 400 (not used) */ + {COEF2, 3}, /* 43 800 (not used) */ + {DECIM3, DA + 2}, /* 44 */ + {IDLE, 0}, /* 45 */ + {IDLE, 0}, /* 46 */ + {IDLE, 0}, /* 47 */ + {IDLE, 0}, /* 48 */ + {IDLE, 0}, /* 49 p5 */ + {MSCBIT, DUTS}, /* 50 dut+- */ + {COEF, 0}, /* 51 10 year tens */ + {COEF, 1}, /* 52 20 */ + {COEF, 2}, /* 53 40 */ + {COEF, 3}, /* 54 80 */ + {MSC20, DST1}, /* 55 dst1 */ + {MSCBIT, DUT1}, /* 56 0.1 dut */ + {MSCBIT, DUT2}, /* 57 0.2 */ + {MSC21, DUT4}, /* 58 0.4 QSY probe channel */ + {MIN1, 0}, /* 59 p6 latch sync min */ + {MIN2, 0} /* 60 leap second */ +}; + +/* + * BCD coefficients for maximum likelihood digit decode + */ +#define P15 1. /* max positive number */ +#define N15 -1. /* max negative number */ + +/* + * Digits 0-9 + */ +#define P9 (P15 / 4) /* mark (+1) */ +#define N9 (N15 / 4) /* space (-1) */ + +double bcd9[][4] = { + {N9, N9, N9, N9}, /* 0 */ + {P9, N9, N9, N9}, /* 1 */ + {N9, P9, N9, N9}, /* 2 */ + {P9, P9, N9, N9}, /* 3 */ + {N9, N9, P9, N9}, /* 4 */ + {P9, N9, P9, N9}, /* 5 */ + {N9, P9, P9, N9}, /* 6 */ + {P9, P9, P9, N9}, /* 7 */ + {N9, N9, N9, P9}, /* 8 */ + {P9, N9, N9, P9}, /* 9 */ + {0, 0, 0, 0} /* backstop */ +}; + +/* + * Digits 0-6 (minute tens) + */ +#define P6 (P15 / 3) /* mark (+1) */ +#define N6 (N15 / 3) /* space (-1) */ + +double bcd6[][4] = { + {N6, N6, N6, 0}, /* 0 */ + {P6, N6, N6, 0}, /* 1 */ + {N6, P6, N6, 0}, /* 2 */ + {P6, P6, N6, 0}, /* 3 */ + {N6, N6, P6, 0}, /* 4 */ + {P6, N6, P6, 0}, /* 5 */ + {N6, P6, P6, 0}, /* 6 */ + {0, 0, 0, 0} /* backstop */ +}; + +/* + * Digits 0-3 (day hundreds) + */ +#define P3 (P15 / 2) /* mark (+1) */ +#define N3 (N15 / 2) /* space (-1) */ + +double bcd3[][4] = { + {N3, N3, 0, 0}, /* 0 */ + {P3, N3, 0, 0}, /* 1 */ + {N3, P3, 0, 0}, /* 2 */ + {P3, P3, 0, 0}, /* 3 */ + {0, 0, 0, 0} /* backstop */ +}; + +/* + * Digits 0-2 (hour tens) + */ +#define P2 (P15 / 2) /* mark (+1) */ +#define N2 (N15 / 2) /* space (-1) */ + +double bcd2[][4] = { + {N2, N2, 0, 0}, /* 0 */ + {P2, N2, 0, 0}, /* 1 */ + {N2, P2, 0, 0}, /* 2 */ + {0, 0, 0, 0} /* backstop */ +}; + +/* + * DST decode (DST2 DST1) for prettyprint + */ +char dstcod[] = { + 'S', /* 00 standard time */ + 'I', /* 01 set clock ahead at 0200 local */ + 'O', /* 10 set clock back at 0200 local */ + 'D' /* 11 daylight time */ +}; + +/* + * The decoding matrix consists of nine row vectors, one for each digit + * of the timecode. The digits are stored from least to most significant + * order. The maximum likelihood timecode is formed from the digits + * corresponding to the maximum likelihood values reading in the + * opposite order: yy ddd hh:mm. + */ +struct decvec { + int radix; /* radix (3, 4, 6, 10) */ + int digit; /* current clock digit */ + int mldigit; /* maximum likelihood digit */ + int phase; /* maximum likelihood digit phase */ + int count; /* match count */ + double digprb; /* max digit probability */ + double digsnr; /* likelihood function (dB) */ + double like[10]; /* likelihood integrator 0-9 */ +}; + +/* + * The station structure is used to acquire the minute pulse from WWV + * and/or WWVH. These stations are distinguished by the frequency used + * for the second and minute sync pulses, 1000 Hz for WWV and 1200 Hz + * for WWVH. Other than frequency, the format is the same. + */ +struct sync { + double epoch; /* accumulated epoch differences */ + double maxamp; /* sync max envelope (square) */ + double noiamp; /* sync noise envelope (square) */ + long pos; /* max amplitude position */ + long lastpos; /* last max position */ + long mepoch; /* minute synch epoch */ + + double amp; /* sync amplitude (I, Q squares) */ + double synamp; /* sync max envelope at 800 ms */ + double synmax; /* sync envelope at 0 s */ + double synmin; /* sync envelope at 59, 1 s */ + double synsnr; /* sync signal SNR */ + int count; /* bit counter */ + char refid[5]; /* reference identifier */ + int select; /* select bits */ + int reach; /* reachability register */ +}; + +/* + * The channel structure is used to mitigate between channels. + */ +struct chan { + int gain; /* audio gain */ + double sigamp; /* data max envelope (square) */ + double noiamp; /* data noise envelope (square) */ + double datsnr; /* data signal SNR */ + struct sync wwv; /* wwv station */ + struct sync wwvh; /* wwvh station */ +}; + +/* + * WWV unit control structure + */ +struct wwvunit { + l_fp timestamp; /* audio sample timestamp */ + l_fp tick; /* audio sample increment */ + double phase, freq; /* logical clock phase and frequency */ + double monitor; /* audio monitor point */ + int fd_icom; /* ICOM file descriptor */ + int errflg; /* error flags */ + int watch; /* watchcat */ + + /* + * Audio codec variables + */ + double comp[SIZE]; /* decompanding table */ + int port; /* codec port */ + int gain; /* codec gain */ + int mongain; /* codec monitor gain */ + int clipcnt; /* sample clipped count */ +#ifdef IRIG_SUCKS + l_fp wigwag; /* wiggle accumulator */ + int wp; /* wiggle filter pointer */ + l_fp wiggle[WIGGLE]; /* wiggle filter */ + l_fp wigbot[WIGGLE]; /* wiggle bottom fisher*/ +#endif /* IRIG_SUCKS */ + + /* + * Variables used to establish basic system timing + */ + int avgint; /* master time constant */ + int tepoch; /* sync epoch median */ + int yepoch; /* sync epoch */ + int repoch; /* buffered sync epoch */ + double epomax; /* second sync amplitude */ + double eposnr; /* second sync SNR */ + double irig; /* data I channel amplitude */ + double qrig; /* data Q channel amplitude */ + int datapt; /* 100 Hz ramp */ + double datpha; /* 100 Hz VFO control */ + int rphase; /* second sample counter */ + long mphase; /* minute sample counter */ + + /* + * Variables used to mitigate which channel to use + */ + struct chan mitig[NCHAN]; /* channel data */ + struct sync *sptr; /* station pointer */ + int dchan; /* data channel */ + int schan; /* probe channel */ + int achan; /* active channel */ + + /* + * Variables used by the clock state machine + */ + struct decvec decvec[9]; /* decoding matrix */ + int rsec; /* seconds counter */ + int digcnt; /* count of digits synchronized */ + + /* + * Variables used to estimate signal levels and bit/digit + * probabilities + */ + double sigsig; /* data max signal */ + double sigamp; /* data max envelope (square) */ + double noiamp; /* data noise envelope (square) */ + double datsnr; /* data SNR (dB) */ + + /* + * Variables used to establish status and alarm conditions + */ + int status; /* status bits */ + int alarm; /* alarm flashers */ + int misc; /* miscellaneous timecode bits */ + int errcnt; /* data bit error counter */ + int errbit; /* data bit errors in minute */ +}; + +/* + * Function prototypes + */ +static int wwv_start P((int, struct peer *)); +static void wwv_shutdown P((int, struct peer *)); +static void wwv_receive P((struct recvbuf *)); +static void wwv_poll P((int, struct peer *)); + +/* + * More function prototypes + */ +static void wwv_epoch P((struct peer *)); +static void wwv_rf P((struct peer *, double)); +static void wwv_endpoc P((struct peer *, int)); +static void wwv_rsec P((struct peer *, double)); +static void wwv_qrz P((struct peer *, struct sync *, + double, int)); +static void wwv_corr4 P((struct peer *, struct decvec *, + double [], double [][4])); +static void wwv_gain P((struct peer *)); +static void wwv_tsec P((struct wwvunit *)); +static double wwv_data P((struct wwvunit *, double)); +static int timecode P((struct wwvunit *, char *)); +static double wwv_snr P((double, double)); +static int carry P((struct decvec *)); +static void wwv_newchan P((struct peer *)); +static void wwv_newgame P((struct peer *)); +static double wwv_metric P((struct sync *)); +#ifdef ICOM +static int wwv_qsy P((struct peer *, int)); +#endif /* ICOM */ + +static double qsy[NCHAN] = {2.5, 5, 10, 15, 20}; /* frequencies (MHz) */ + +/* + * Transfer vector + */ +struct refclock refclock_wwv = { + wwv_start, /* start up driver */ + wwv_shutdown, /* shut down driver */ + wwv_poll, /* transmit poll message */ + noentry, /* not used (old wwv_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old wwv_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * wwv_start - open the devices and initialize data for processing + */ +static int +wwv_start( + int unit, /* instance number (used by PCM) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; +#ifdef ICOM + int temp; +#endif /* ICOM */ + + /* + * Local variables + */ + int fd; /* file descriptor */ + int i; /* index */ + double step; /* codec adjustment */ + + /* + * Open audio device + */ + fd = audio_init(DEVICE_AUDIO, AUDIO_BUFSIZ, unit); + if (fd < 0) + return (0); +#ifdef DEBUG + if (debug) + audio_show(); +#endif + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct wwvunit *)emalloc(sizeof(struct wwvunit)))) { + close(fd); + return (0); + } + memset(up, 0, sizeof(struct wwvunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = wwv_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + + /* + * The companded samples are encoded sign-magnitude. The table + * contains all the 256 values in the interest of speed. + */ + up->comp[0] = up->comp[OFFSET] = 0.; + up->comp[1] = 1; up->comp[OFFSET + 1] = -1.; + up->comp[2] = 3; up->comp[OFFSET + 2] = -3.; + step = 2.; + for (i = 3; i < OFFSET; i++) { + up->comp[i] = up->comp[i - 1] + step; + up->comp[OFFSET + i] = -up->comp[i]; + if (i % 16 == 0) + step *= 2.; + } + DTOLFP(1. / SECOND, &up->tick); + + /* + * Initialize the decoding matrix with the radix for each digit + * position. + */ + up->decvec[MN].radix = 10; /* minutes */ + up->decvec[MN + 1].radix = 6; + up->decvec[HR].radix = 10; /* hours */ + up->decvec[HR + 1].radix = 3; + up->decvec[DA].radix = 10; /* days */ + up->decvec[DA + 1].radix = 10; + up->decvec[DA + 2].radix = 4; + up->decvec[YR].radix = 10; /* years */ + up->decvec[YR + 1].radix = 10; + wwv_newgame(peer); + up->schan = up->achan = 3; + + /* + * Initialize autotune if available. Start out at 15 MHz. Note + * that the ICOM select code must be less than 128, so the high + * order bit can be used to select the line speed. + */ +#ifdef ICOM + temp = 0; +#ifdef DEBUG + if (debug > 1) + temp = P_TRACE; +#endif + if (peer->ttl != 0) { + if (peer->ttl & 0x80) + up->fd_icom = icom_init("/dev/icom", B1200, + temp); + else + up->fd_icom = icom_init("/dev/icom", B9600, + temp); + } + if (up->fd_icom > 0) { + if ((temp = wwv_qsy(peer, up->schan)) != 0) { + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "icom: radio not found"); + up->errflg = CEVNT_FAULT; + close(up->fd_icom); + up->fd_icom = 0; + } else { + NLOG(NLOG_SYNCEVENT | NLOG_SYSEVENT) + msyslog(LOG_NOTICE, + "icom: autotune enabled"); + } + } +#endif /* ICOM */ + return (1); +} + + +/* + * wwv_shutdown - shut down the clock + */ +static void +wwv_shutdown( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + io_closeclock(&pp->io); + if (up->fd_icom > 0) + close(up->fd_icom); + free(up); +} + + +/* + * wwv_receive - receive data from the audio device + * + * This routine reads input samples and adjusts the logical clock to + * track the A/D sample clock by dropping or duplicating codec samples. + * It also controls the A/D signal level with an AGC loop to mimimize + * quantization noise and avoid overload. + */ +static void +wwv_receive( + struct recvbuf *rbufp /* receive buffer structure pointer */ + ) +{ + struct peer *peer; + struct refclockproc *pp; + struct wwvunit *up; + + /* + * Local variables + */ + double sample; /* codec sample */ + u_char *dpt; /* buffer pointer */ + int bufcnt; /* buffer counter */ + l_fp ltemp; + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Main loop - read until there ain't no more. Note codec + * samples are bit-inverted. + */ + DTOLFP((double)rbufp->recv_length / SECOND, <emp); + L_SUB(&rbufp->recv_time, <emp); + up->timestamp = rbufp->recv_time; + dpt = rbufp->recv_buffer; + for (bufcnt = 0; bufcnt < rbufp->recv_length; bufcnt++) { + sample = up->comp[~*dpt++ & 0xff]; + + /* + * Clip noise spikes greater than MAXSIG. If no clips, + * increase the gain a tad; if the clips are too high, + * decrease a tad. + */ + if (sample > MAXSIG) { + sample = MAXSIG; + up->clipcnt++; + } else if (sample < -MAXSIG) { + sample = -MAXSIG; + up->clipcnt++; + } + + /* + * Variable frequency oscillator. The codec oscillator + * runs at the nominal rate of 8000 samples per second, + * or 125 us per sample. A frequency change of one unit + * results in either duplicating or deleting one sample + * per second, which results in a frequency change of + * 125 PPM. + */ + up->phase += up->freq / SECOND; + if (up->phase >= .5) { + up->phase -= 1.; + } else if (up->phase < -.5) { + up->phase += 1.; + wwv_rf(peer, sample); + wwv_rf(peer, sample); + } else { + wwv_rf(peer, sample); + } + L_ADD(&up->timestamp, &up->tick); + } + + /* + * Set the input port and monitor gain for the next buffer. + */ + if (pp->sloppyclockflag & CLK_FLAG2) + up->port = 2; + else + up->port = 1; + if (pp->sloppyclockflag & CLK_FLAG3) + up->mongain = MONGAIN; + else + up->mongain = 0; +} + + +/* + * wwv_poll - called by the transmit procedure + * + * This routine keeps track of status. If no offset samples have been + * processed during a poll interval, a timeout event is declared. If + * errors have have occurred during the interval, they are reported as + * well. Once the clock is set, it always appears reachable, unless + * reset by watchdog timeout. + */ +static void +wwv_poll( + int unit, /* instance number (not used) */ + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + if (pp->coderecv == pp->codeproc) + up->errflg = CEVNT_TIMEOUT; + if (up->errflg) + refclock_report(peer, up->errflg); + up->errflg = 0; + pp->polls++; +} + + +/* + * wwv_rf - process signals and demodulate to baseband + * + * This routine grooms and filters decompanded raw audio samples. The + * output signals include the 100-Hz baseband data signal in quadrature + * form, plus the epoch index of the second sync signal and the second + * index of the minute sync signal. + * + * There are two 1-s ramps used by this program. Both count the 8000 + * logical clock samples spanning exactly one second. The epoch ramp + * counts the samples starting at an arbitrary time. The rphase ramp + * counts the samples starting at the 5-ms second sync pulse found + * during the epoch ramp. + * + * There are two 1-m ramps used by this program. The mphase ramp counts + * the 480,000 logical clock samples spanning exactly one minute and + * starting at an arbitrary time. The rsec ramp counts the 60 seconds of + * the minute starting at the 800-ms minute sync pulse found during the + * mphase ramp. The rsec ramp drives the seconds state machine to + * determine the bits and digits of the timecode. + * + * Demodulation operations are based on three synthesized quadrature + * sinusoids: 100 Hz for the data signal, 1000 Hz for the WWV sync + * signal and 1200 Hz for the WWVH sync signal. These drive synchronous + * matched filters for the data signal (170 ms at 100 Hz), WWV minute + * sync signal (800 ms at 1000 Hz) and WWVH minute sync signal (800 ms + * at 1200 Hz). Two additional matched filters are switched in + * as required for the WWV second sync signal (5 ms at 1000 Hz) and + * WWVH second sync signal (5 ms at 1200 Hz). + */ +static void +wwv_rf( + struct peer *peer, /* peerstructure pointer */ + double isig /* input signal */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + struct sync *sp; + + static double lpf[5]; /* 150-Hz lpf delay line */ + double data; /* lpf output */ + static double bpf[9]; /* 1000/1200-Hz bpf delay line */ + double syncx; /* bpf output */ + static double mf[41]; /* 1000/1200-Hz mf delay line */ + double mfsync; /* mf output */ + + static int iptr; /* data channel pointer */ + static double ibuf[DATSIZ]; /* data I channel delay line */ + static double qbuf[DATSIZ]; /* data Q channel delay line */ + + static int jptr; /* sync channel pointer */ + static double cibuf[SYNSIZ]; /* wwv I channel delay line */ + static double cqbuf[SYNSIZ]; /* wwv Q channel delay line */ + static double ciamp; /* wwv I channel amplitude */ + static double cqamp; /* wwv Q channel amplitude */ + static int csinptr; /* wwv channel phase */ + static double hibuf[SYNSIZ]; /* wwvh I channel delay line */ + static double hqbuf[SYNSIZ]; /* wwvh Q channel delay line */ + static double hiamp; /* wwvh I channel amplitude */ + static double hqamp; /* wwvh Q channel amplitude */ + static int hsinptr; /* wwvh channels phase */ + + static double epobuf[SECOND]; /* epoch sync comb filter */ + static double epomax; /* epoch sync amplitude buffer */ + static int epopos; /* epoch sync position buffer */ + + static int iniflg; /* initialization flag */ + int epoch; /* comb filter index */ + int pdelay; /* propagation delay (samples) */ + double dtemp; + int i; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + if (!iniflg) { + iniflg = 1; + memset((char *)lpf, 0, sizeof(lpf)); + memset((char *)bpf, 0, sizeof(bpf)); + memset((char *)mf, 0, sizeof(mf)); + memset((char *)ibuf, 0, sizeof(ibuf)); + memset((char *)qbuf, 0, sizeof(qbuf)); + memset((char *)cibuf, 0, sizeof(cibuf)); + memset((char *)cqbuf, 0, sizeof(cqbuf)); + memset((char *)hibuf, 0, sizeof(hibuf)); + memset((char *)hqbuf, 0, sizeof(hqbuf)); + memset((char *)epobuf, 0, sizeof(epobuf)); + } + + /* + * Baseband data demodulation. The 100-Hz subcarrier is + * extracted using a 150-Hz IIR lowpass filter. This attenuates + * the 1000/1200-Hz sync signals, as well as the 440-Hz and + * 600-Hz tones and most of the noise and voice modulation + * components. + * + * Matlab IIR 4th-order IIR elliptic, 150 Hz lowpass, 0.2 dB + * passband ripple, -50 dB stopband ripple. + */ + data = (lpf[4] = lpf[3]) * 8.360961e-01; + data += (lpf[3] = lpf[2]) * -3.481740e+00; + data += (lpf[2] = lpf[1]) * 5.452988e+00; + data += (lpf[1] = lpf[0]) * -3.807229e+00; + lpf[0] = isig - data; + data = lpf[0] * 3.281435e-03 + + lpf[1] * -1.149947e-02 + + lpf[2] * 1.654858e-02 + + lpf[3] * -1.149947e-02 + + lpf[4] * 3.281435e-03; + + /* + * The I and Q quadrature data signals are produced by + * multiplying the filtered signal by 100-Hz sine and cosine + * signals, respectively. The data signals are demodulated by + * 170-ms synchronous matched filters to produce the amplitude + * and phase signals used by the decoder. + */ + i = up->datapt; + up->datapt = (up->datapt + IN100) % 80; + dtemp = sintab[i] * data / DATSIZ * DGAIN; + up->irig -= ibuf[iptr]; + ibuf[iptr] = dtemp; + up->irig += dtemp; + i = (i + 20) % 80; + dtemp = sintab[i] * data / DATSIZ * DGAIN; + up->qrig -= qbuf[iptr]; + qbuf[iptr] = dtemp; + up->qrig += dtemp; + iptr = (iptr + 1) % DATSIZ; + + /* + * Baseband sync demodulation. The 1000/1200 sync signals are + * extracted using a 600-Hz IIR bandpass filter. This removes + * the 100-Hz data subcarrier, as well as the 440-Hz and 600-Hz + * tones and most of the noise and voice modulation components. + * + * Matlab 4th-order IIR elliptic, 800-1400 Hz bandpass, 0.2 dB + * passband ripple, -50 dB stopband ripple. + */ + syncx = (bpf[8] = bpf[7]) * 4.897278e-01; + syncx += (bpf[7] = bpf[6]) * -2.765914e+00; + syncx += (bpf[6] = bpf[5]) * 8.110921e+00; + syncx += (bpf[5] = bpf[4]) * -1.517732e+01; + syncx += (bpf[4] = bpf[3]) * 1.975197e+01; + syncx += (bpf[3] = bpf[2]) * -1.814365e+01; + syncx += (bpf[2] = bpf[1]) * 1.159783e+01; + syncx += (bpf[1] = bpf[0]) * -4.735040e+00; + bpf[0] = isig - syncx; + syncx = bpf[0] * 8.203628e-03 + + bpf[1] * -2.375732e-02 + + bpf[2] * 3.353214e-02 + + bpf[3] * -4.080258e-02 + + bpf[4] * 4.605479e-02 + + bpf[5] * -4.080258e-02 + + bpf[6] * 3.353214e-02 + + bpf[7] * -2.375732e-02 + + bpf[8] * 8.203628e-03; + + /* + * The I and Q quadrature minute sync signals are produced by + * multiplying the filtered signal by 1000-Hz (WWV) and 1200-Hz + * (WWVH) sine and cosine signals, respectively. The resulting + * signals are demodulated by 800-ms synchronous matched filters + * to synchronize the second and minute and to detect which one + * (or both) the WWV or WWVH signal is present. + * + * Note the master timing ramps, which run continuously. The + * minute counter (mphase) counts the samples in the minute, + * while the second counter (epoch) counts the samples in the + * second. + */ + up->mphase = (up->mphase + 1) % MINUTE; + epoch = up->mphase % SECOND; + i = csinptr; + csinptr = (csinptr + IN1000) % 80; + dtemp = sintab[i] * syncx / SYNSIZ * SGAIN; + ciamp = ciamp - cibuf[jptr] + dtemp; + cibuf[jptr] = dtemp; + i = (i + 20) % 80; + dtemp = sintab[i] * syncx / SYNSIZ * SGAIN; + cqamp = cqamp - cqbuf[jptr] + dtemp; + cqbuf[jptr] = dtemp; + sp = &up->mitig[up->schan].wwv; + dtemp = ciamp * ciamp + cqamp * cqamp; + sp->amp = dtemp; + if (!(up->status & MSYNC)) + wwv_qrz(peer, sp, dtemp, (int)(pp->fudgetime1 * + SECOND)); + i = hsinptr; + hsinptr = (hsinptr + IN1200) % 80; + dtemp = sintab[i] * syncx / SYNSIZ * SGAIN; + hiamp = hiamp - hibuf[jptr] + dtemp; + hibuf[jptr] = dtemp; + i = (i + 20) % 80; + dtemp = sintab[i] * syncx / SYNSIZ * SGAIN; + hqamp = hqamp - hqbuf[jptr] + dtemp; + hqbuf[jptr] = dtemp; + sp = &up->mitig[up->schan].wwvh; + dtemp = hiamp * hiamp + hqamp * hqamp; + sp->amp = dtemp; + if (!(up->status & MSYNC)) + wwv_qrz(peer, sp, dtemp, (int)(pp->fudgetime2 * + SECOND)); + jptr = (jptr + 1) % SYNSIZ; + + /* + * The following section is called once per minute. It does + * housekeeping and timeout functions and empties the dustbins. + */ + if (up->mphase == 0) { + up->watch++; + if (!(up->status & MSYNC)) { + + /* + * If minute sync has not been acquired before + * timeout, or if no signal is heard, the + * program cycles to the next frequency and + * tries again. + */ + wwv_newchan(peer); + if (!(up->status & (SELV | SELH)) || up->watch > + ACQSN) { + wwv_newgame(peer); +#ifdef ICOM + if (up->fd_icom > 0) { + up->schan = (up->schan + 1) % + NCHAN; + wwv_qsy(peer, up->schan); + } +#endif /* ICOM */ + } + } else { + + /* + * If the leap bit is set, set the minute epoch + * back one second so the station processes + * don't miss a beat. + */ + if (up->status & LEPSEC) { + up->mphase -= SECOND; + if (up->mphase < 0) + up->mphase += MINUTE; + } + } + } + + /* + * When the channel metric reaches threshold and the second + * counter matches the minute epoch within the second, the + * driver has synchronized to the station. The second number is + * the remaining seconds until the next minute epoch, while the + * sync epoch is zero. Watch out for the first second; if + * already synchronized to the second, the buffered sync epoch + * must be set. + */ + if (up->status & MSYNC) { + wwv_epoch(peer); + } else if ((sp = up->sptr) != NULL) { + struct chan *cp; + + if (sp->count >= AMIN && epoch == sp->mepoch % SECOND) { + up->rsec = 60 - sp->mepoch / SECOND; + up->rphase = 0; + up->status |= MSYNC; + up->watch = 0; + if (!(up->status & SSYNC)) + up->repoch = up->yepoch = epoch; + else + up->repoch = up->yepoch; + for (i = 0; i < NCHAN; i++) { + cp = &up->mitig[i]; + cp->wwv.count = cp->wwv.reach = 0; + cp->wwvh.count = cp->wwvh.reach = 0; + } + } + } + + /* + * The second sync pulse is extracted using 5-ms (40 sample) FIR + * matched filters at 1000 Hz for WWV or 1200 Hz for WWVH. This + * pulse is used for the most precise synchronization, since if + * provides a resolution of one sample (125 us). The filters run + * only if the station has been reliably determined. + */ + if (up->status & SELV) { + pdelay = (int)(pp->fudgetime1 * SECOND); + + /* + * WWV FIR matched filter, five cycles of 1000-Hz + * sinewave. + */ + mf[40] = mf[39]; + mfsync = (mf[39] = mf[38]) * 4.224514e-02; + mfsync += (mf[38] = mf[37]) * 5.974365e-02; + mfsync += (mf[37] = mf[36]) * 4.224514e-02; + mf[36] = mf[35]; + mfsync += (mf[35] = mf[34]) * -4.224514e-02; + mfsync += (mf[34] = mf[33]) * -5.974365e-02; + mfsync += (mf[33] = mf[32]) * -4.224514e-02; + mf[32] = mf[31]; + mfsync += (mf[31] = mf[30]) * 4.224514e-02; + mfsync += (mf[30] = mf[29]) * 5.974365e-02; + mfsync += (mf[29] = mf[28]) * 4.224514e-02; + mf[28] = mf[27]; + mfsync += (mf[27] = mf[26]) * -4.224514e-02; + mfsync += (mf[26] = mf[25]) * -5.974365e-02; + mfsync += (mf[25] = mf[24]) * -4.224514e-02; + mf[24] = mf[23]; + mfsync += (mf[23] = mf[22]) * 4.224514e-02; + mfsync += (mf[22] = mf[21]) * 5.974365e-02; + mfsync += (mf[21] = mf[20]) * 4.224514e-02; + mf[20] = mf[19]; + mfsync += (mf[19] = mf[18]) * -4.224514e-02; + mfsync += (mf[18] = mf[17]) * -5.974365e-02; + mfsync += (mf[17] = mf[16]) * -4.224514e-02; + mf[16] = mf[15]; + mfsync += (mf[15] = mf[14]) * 4.224514e-02; + mfsync += (mf[14] = mf[13]) * 5.974365e-02; + mfsync += (mf[13] = mf[12]) * 4.224514e-02; + mf[12] = mf[11]; + mfsync += (mf[11] = mf[10]) * -4.224514e-02; + mfsync += (mf[10] = mf[9]) * -5.974365e-02; + mfsync += (mf[9] = mf[8]) * -4.224514e-02; + mf[8] = mf[7]; + mfsync += (mf[7] = mf[6]) * 4.224514e-02; + mfsync += (mf[6] = mf[5]) * 5.974365e-02; + mfsync += (mf[5] = mf[4]) * 4.224514e-02; + mf[4] = mf[3]; + mfsync += (mf[3] = mf[2]) * -4.224514e-02; + mfsync += (mf[2] = mf[1]) * -5.974365e-02; + mfsync += (mf[1] = mf[0]) * -4.224514e-02; + mf[0] = syncx; + } else if (up->status & SELH) { + pdelay = (int)(pp->fudgetime2 * SECOND); + + /* + * WWVH FIR matched filter, six cycles of 1200-Hz + * sinewave. + */ + mf[40] = mf[39]; + mfsync = (mf[39] = mf[38]) * 4.833363e-02; + mfsync += (mf[38] = mf[37]) * 5.681959e-02; + mfsync += (mf[37] = mf[36]) * 1.846180e-02; + mfsync += (mf[36] = mf[35]) * -3.511644e-02; + mfsync += (mf[35] = mf[34]) * -5.974365e-02; + mfsync += (mf[34] = mf[33]) * -3.511644e-02; + mfsync += (mf[33] = mf[32]) * 1.846180e-02; + mfsync += (mf[32] = mf[31]) * 5.681959e-02; + mfsync += (mf[31] = mf[30]) * 4.833363e-02; + mf[30] = mf[29]; + mfsync += (mf[29] = mf[28]) * -4.833363e-02; + mfsync += (mf[28] = mf[27]) * -5.681959e-02; + mfsync += (mf[27] = mf[26]) * -1.846180e-02; + mfsync += (mf[26] = mf[25]) * 3.511644e-02; + mfsync += (mf[25] = mf[24]) * 5.974365e-02; + mfsync += (mf[24] = mf[23]) * 3.511644e-02; + mfsync += (mf[23] = mf[22]) * -1.846180e-02; + mfsync += (mf[22] = mf[21]) * -5.681959e-02; + mfsync += (mf[21] = mf[20]) * -4.833363e-02; + mf[20] = mf[19]; + mfsync += (mf[19] = mf[18]) * 4.833363e-02; + mfsync += (mf[18] = mf[17]) * 5.681959e-02; + mfsync += (mf[17] = mf[16]) * 1.846180e-02; + mfsync += (mf[16] = mf[15]) * -3.511644e-02; + mfsync += (mf[15] = mf[14]) * -5.974365e-02; + mfsync += (mf[14] = mf[13]) * -3.511644e-02; + mfsync += (mf[13] = mf[12]) * 1.846180e-02; + mfsync += (mf[12] = mf[11]) * 5.681959e-02; + mfsync += (mf[11] = mf[10]) * 4.833363e-02; + mf[10] = mf[9]; + mfsync += (mf[9] = mf[8]) * -4.833363e-02; + mfsync += (mf[8] = mf[7]) * -5.681959e-02; + mfsync += (mf[7] = mf[6]) * -1.846180e-02; + mfsync += (mf[6] = mf[5]) * 3.511644e-02; + mfsync += (mf[5] = mf[4]) * 5.974365e-02; + mfsync += (mf[4] = mf[3]) * 3.511644e-02; + mfsync += (mf[3] = mf[2]) * -1.846180e-02; + mfsync += (mf[2] = mf[1]) * -5.681959e-02; + mfsync += (mf[1] = mf[0]) * -4.833363e-02; + mf[0] = syncx; + } else { + mfsync = 0; + pdelay = 0; + } + + /* + * Enhance the seconds sync pulse using a 1-s (8000-sample) comb + * filter. Correct for the FIR matched filter delay, which is 5 + * ms for both the WWV and WWVH filters, and also for the + * propagation delay. Once each second look for second sync. If + * not in minute sync, fiddle the codec gain. Note the SNR is + * computed from the maximum sample and the two samples 6 ms + * before and 6 ms after it, so if we slip more than a cycle the + * SNR should plummet. + */ + dtemp = (epobuf[epoch] += (mfsync - epobuf[epoch]) / + up->avgint); + if (dtemp > epomax) { + epomax = dtemp; + epopos = epoch; + } + if (epoch == 0) { + int k, j; + + up->epomax = epomax; + k = epopos - 6 * MS; + if (k < 0) + k += SECOND; + j = epopos + 6 * MS; + if (j >= SECOND) + i -= SECOND; + up->eposnr = wwv_snr(epomax, max(abs(epobuf[k]), + abs(epobuf[j]))); + epopos -= pdelay + 5 * MS; + if (epopos < 0) + epopos += SECOND; + wwv_endpoc(peer, epopos); + if (!(up->status & SSYNC)) + up->alarm |= SYNERR; + epomax = 0; + if (!(up->status & MSYNC)) + wwv_gain(peer); + } +} + + +/* + * wwv_qrz - identify and acquire WWV/WWVH minute sync pulse + * + * This routine implements a virtual station process used to acquire + * minute sync and to mitigate among the ten frequency and station + * combinations. During minute sync acquisition the process probes each + * frequency in turn for the minute pulse from either station, which + * involves searching through the entire minute of samples. After + * finding a candidate, the process searches only the seconds before and + * after the candidate for the signal and all other seconds for the + * noise. + * + * Students of radar receiver technology will discover this algorithm + * amounts to a range gate discriminator. The discriminator requires + * that the peak minute pulse amplitude be at least 2000 and the SNR be + * at least 6 dB. In addition after finding a candidate, The peak second + * pulse amplitude must be at least 2000, the SNR at least 6 dB and the + * difference between the current and previous epoch must be less than + * 7.5 ms, which corresponds to a frequency error of 125 PPM.. A compare + * counter keeps track of the number of successive intervals which + * satisfy these criteria. + * + * Note that, while the minute pulse is found by by the discriminator, + * the actual value is determined from the second epoch. The assumption + * is that the discriminator peak occurs about 800 ms into the second, + * so the timing is retarted to the previous second epoch. + */ +static void +wwv_qrz( + struct peer *peer, /* peer structure pointer */ + struct sync *sp, /* sync channel structure */ + double syncx, /* bandpass filtered sync signal */ + int pdelay /* propagation delay (samples) */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + char tbuf[80]; /* monitor buffer */ + double snr; /* on-pulse/off-pulse ratio (dB) */ + long epoch, fpoch; + int isgood; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Find the sample with peak energy, which defines the minute + * epoch. If a sample has been found with good amplitude, + * accumulate the noise squares for all except the second before + * and after that position. + */ + isgood = up->epomax > STHR && up->eposnr > SSNR; + if (isgood) { + fpoch = up->mphase % SECOND - up->tepoch; + if (fpoch < 0) + fpoch += SECOND; + } else { + fpoch = pdelay + SYNSIZ; + } + epoch = up->mphase - fpoch; + if (epoch < 0) + epoch += MINUTE; + if (syncx > sp->maxamp) { + sp->maxamp = syncx; + sp->pos = epoch; + } + if (abs((epoch - sp->lastpos) % MINUTE) > SECOND) + sp->noiamp += syncx; + + /* + * At the end of the minute, determine the epoch of the + * sync pulse, as well as the SNR and difference between + * the current and previous epoch, which represents the + * intrinsic frequency error plus jitter. + */ + if (up->mphase == 0) { + sp->synmax = sqrt(sp->maxamp); + sp->synmin = sqrt(sp->noiamp / (MINUTE - 2 * SECOND)); + epoch = (sp->pos - sp->lastpos) % MINUTE; + + /* + * If not yet in minute sync, we have to do a little + * dance to find a valid minute sync pulse, emphasis + * valid. + */ + snr = wwv_snr(sp->synmax, sp->synmin); + isgood = isgood && sp->synmax > ATHR && snr > ASNR; + switch (sp->count) { + + /* + * In state 0 the station was not heard during the + * previous probe. Look for the biggest blip greater + * than the amplitude threshold in the minute and assume + * that the minute sync pulse. We're fishing here, since + * the range gate has not yet been determined. If found, + * bump to state 1. + */ + case 0: + if (sp->synmax >= ATHR) + sp->count++; + break; + + /* + * In state 1 a candidate blip has been found and the + * next minute has been searched for another blip. If + * none are found acceptable, drop back to state 0 and + * hunt some more. Otherwise, a legitimate minute pulse + * may have been found, so bump to state 2. + */ + case 1: + if (!isgood) { + sp->count = 0; + break; + } + sp->count++; + break; + + /* + * In states 2 and above, continue to groom samples as + * before and drop back to state 0 if the groom fails. + * If it succeeds, set the epoch and bump to the next + * state until reaching the threshold, if ever. + */ + default: + if (!isgood || abs(epoch) > AWND * MS) { + sp->count = 0; + break; + } + sp->mepoch = sp->pos; + sp->count++; + break; + } + if (pp->sloppyclockflag & CLK_FLAG4) { + sprintf(tbuf, + "wwv8 %d %3d %s %d %5.0f %5.1f %5ld %5d %ld", + up->port, up->gain, sp->refid, sp->count, + sp->synmax, snr, sp->pos, up->tepoch, + epoch); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif + } + sp->lastpos = sp->pos; + sp->maxamp = sp->noiamp = 0; + } +} + + +/* + * wwv_endpoc - identify and acquire second sync pulse + * + * This routine is called at the end of the second sync interval. It + * determines the second sync epoch position within the interval and + * disciplines the sample clock using a frequency-lock loop (FLL). + * + * Second sync is determined in the RF input routine as the maximum + * over all 8000 samples in the second comb filter. To assure accurate + * and reliable time and frequency discipline, this routine performs a + * great deal of heavy-handed heuristic data filtering and grooming. + * + * Note that, since the minute sync pulse is very wide (800 ms), precise + * minute sync epoch acquisition requires at least a rough estimate of + * the second sync pulse (5 ms). This becomes more important in choppy + * conditions at the lower frequencies at night, since sferics and + * cochannel crude can badly distort the minute pulse. + */ +static void +wwv_endpoc( + struct peer *peer, /* peer structure pointer */ + int epopos /* epoch max position */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + static int epoch_mf[3]; /* epoch median filter */ + static int xepoch; /* last second epoch */ + static int zepoch; /* last averaging interval epoch */ + static int syncnt; /* run length counter */ + static int maxrun; /* longest run length */ + static int mepoch; /* longest run epoch */ + static int avgcnt; /* averaging interval counter */ + static int avginc; /* averaging ratchet */ + static int iniflg; /* initialization flag */ + char tbuf[80]; /* monitor buffer */ + double dtemp; + int tmp2; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + if (!iniflg) { + iniflg = 1; + memset((char *)epoch_mf, 0, sizeof(epoch_mf)); + } + + /* + * A three-stage median filter is used to help denoise the + * second sync pulse. The median sample becomes the candidate + * epoch. + */ + epoch_mf[2] = epoch_mf[1]; + epoch_mf[1] = epoch_mf[0]; + epoch_mf[0] = epopos; + if (epoch_mf[0] > epoch_mf[1]) { + if (epoch_mf[1] > epoch_mf[2]) + up->tepoch = epoch_mf[1]; /* 0 1 2 */ + else if (epoch_mf[2] > epoch_mf[0]) + up->tepoch = epoch_mf[0]; /* 2 0 1 */ + else + up->tepoch = epoch_mf[2]; /* 0 2 1 */ + } else { + if (epoch_mf[1] < epoch_mf[2]) + up->tepoch = epoch_mf[1]; /* 2 1 0 */ + else if (epoch_mf[2] < epoch_mf[0]) + up->tepoch = epoch_mf[0]; /* 1 0 2 */ + else + up->tepoch = epoch_mf[2]; /* 1 2 0 */ + } + + /* + * If the signal amplitude or SNR fall below thresholds or if no + * stations are heard, dim the second sync lamp and start over. + */ + if (!(up->status & (SELV | SELH)) || up->epomax < STHR || + up->eposnr < SSNR) { + up->status &= ~(SSYNC | FGATE); + avgcnt = syncnt = maxrun = 0; + return; + } + avgcnt++; + + /* + * If the epoch candidate is the same as the last one, increment + * the compare counter. If not, save the length and epoch of the + * current run for use later and reset the counter. + */ + tmp2 = (up->tepoch - xepoch) % SECOND; + if (tmp2 == 0) { + syncnt++; + } else { + if (maxrun > 0 && mepoch == xepoch) { + maxrun += syncnt; + } else if (syncnt > maxrun) { + maxrun = syncnt; + mepoch = xepoch; + } + syncnt = 0; + } + if ((pp->sloppyclockflag & CLK_FLAG4) && !(up->status & (SSYNC | + MSYNC))) { + sprintf(tbuf, + "wwv1 %04x %5.0f %5.1f %5d %5d %4d %4d", + up->status, up->epomax, up->eposnr, up->tepoch, + tmp2, avgcnt, syncnt); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif /* DEBUG */ + } + + /* + * The sample clock frequency is disciplined using a first order + * feedback loop with time constant consistent with the Allan + * intercept of typical computer clocks. + * + * The frequency update is calculated from the epoch change in + * 125-us units divided by the averaging interval in seconds. + * The averaging interval affects other receiver functions, + * including the the 1000/1200-Hz comb filter and codec clock + * loop. It also affects the 100-Hz subcarrier loop and the bit + * and digit comparison counter thresholds. + */ + if (avgcnt < up->avgint) { + xepoch = up->tepoch; + return; + } + + /* + * During the averaging interval the longest run of identical + * epoches is determined. If the longest run is at least 10 + * seconds, the SSYNC bit is lit and the value becomes the + * reference epoch for the next interval. If not, the second + * synd lamp is dark and flashers set. + */ + if (maxrun > 0 && mepoch == xepoch) { + maxrun += syncnt; + } else if (syncnt > maxrun) { + maxrun = syncnt; + mepoch = xepoch; + } + xepoch = up->tepoch; + if (maxrun > SCMP) { + up->status |= SSYNC; + up->yepoch = mepoch; + } else { + up->status &= ~SSYNC; + } + + /* + * If the epoch change over the averaging interval is less than + * 1 ms, the frequency is adjusted, but clamped at +-125 PPM. If + * greater than 1 ms, the counter is decremented. If the epoch + * change is less than 0.5 ms, the counter is incremented. If + * the counter increments to +3, the averaging interval is + * doubled and the counter set to zero; if it increments to -3, + * the interval is halved and the counter set to zero. + * + * Here be spooks. From careful observations, the epoch + * sometimes makes a long run of identical samples, then takes a + * lurch due apparently to lost interrupts or spooks. If this + * happens, the epoch change times the maximum run length will + * be greater than the averaging interval, so the lurch should + * be believed but the frequency left alone. Really intricate + * here. + */ + if (maxrun == 0) + mepoch = up->tepoch; + dtemp = (mepoch - zepoch) % SECOND; + if (up->status & FGATE) { + if (abs(dtemp) < MAXFREQ * MINAVG) { + if (maxrun * abs(mepoch - zepoch) < + avgcnt) { + up->freq += dtemp / avgcnt; + if (up->freq > MAXFREQ) + up->freq = MAXFREQ; + else if (up->freq < -MAXFREQ) + up->freq = -MAXFREQ; + } + if (abs(dtemp) < MAXFREQ * MINAVG / 2) { + if (avginc < 3) { + avginc++; + } else { + if (up->avgint < MAXAVG) { + up->avgint <<= 1; + avginc = 0; + } + } + } + } else { + if (avginc > -3) { + avginc--; + } else { + if (up->avgint > MINAVG) { + up->avgint >>= 1; + avginc = 0; + } + } + } + } + if (pp->sloppyclockflag & CLK_FLAG4) { + sprintf(tbuf, + "wwv2 %04x %4.0f %4d %4d %2d %4d %4.0f %6.1f", + up->status, up->epomax, mepoch, maxrun, avginc, + avgcnt, dtemp, up->freq * 1e6 / SECOND); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif /* DEBUG */ + } + up->status |= FGATE; + zepoch = mepoch; + avgcnt = syncnt = maxrun = 0; +} + + +/* + * wwv_epoch - epoch scanner + * + * This routine scans the receiver second epoch to determine the signal + * amplitudes and pulse timings. Receiver synchronization is determined + * by the minute sync pulse detected in the wwv_rf() routine and the + * second sync pulse detected in the wwv_epoch() routine. A pulse width + * discriminator extracts data signals from the 100-Hz subcarrier. The + * transmitted signals are delayed by the propagation delay, receiver + * delay and filter delay of this program. Delay corrections are + * introduced separately for WWV and WWVH. + * + * Most communications radios use a highpass filter in the audio stages, + * which can do nasty things to the subcarrier phase relative to the + * sync pulses. Therefore, the data subcarrier reference phase is + * disciplined using the hardlimited quadrature-phase signal sampled at + * the same time as the in-phase signal. The phase tracking loop uses + * phase adjustments of plus-minus one sample (125 us). + */ +static void +wwv_epoch( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + struct chan *cp; + static double dpulse; /* data pulse length */ + double dtemp; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Sample the minute sync pulse envelopes at epoch 800 for both + * the WWV and WWVH stations. This will be used later for + * channel and station mitigation. Note that the seconds epoch + * is set here well before the end of the second to make sure we + * never seet the epoch backwards. + */ + if (up->rphase == 800 * MS) { + up->repoch = up->yepoch; + cp = &up->mitig[up->achan]; + cp->wwv.synamp = cp->wwv.amp; + cp->wwvh.synamp = cp->wwvh.amp; + } + + /* + * Sample the data subcarrier at epoch 15 ms, giving a guard + * time of +-15 ms from the beginning of the second until the + * pulse rises at 30 ms. The I-channel amplitude is used to + * calculate the slice level. The envelope amplitude is used + * during the probe seconds to determine the SNR. There is a + * compromise here; we want to delay the sample as long as + * possible to give the radio time to change frequency and the + * AGC to stabilize, but as early as possible if the second + * epoch is not exact. + */ + if (up->rphase == 15 * MS) { + up->noiamp = up->irig * up->irig + up->qrig * up->qrig; + + /* + * Sample the data subcarrier at epoch 215 ms, giving a guard + * time of +-15 ms from the earliest the pulse peak can be + * reached to the earliest it can begin to fall. For the data + * channel latch the I-channel amplitude for all except the + * probe seconds and adjust the 100-Hz reference oscillator + * phase using the Q-channel amplitude at this epoch. For the + * probe channel latch the envelope amplitude. + */ + } else if (up->rphase == 215 * MS) { + up->sigsig = up->irig; + if (up->sigsig < 0) + up->sigsig = 0; + up->datpha = up->qrig / up->avgint; + if (up->datpha >= 0) { + up->datapt++; + if (up->datapt >= 80) + up->datapt -= 80; + } else { + up->datapt--; + if (up->datapt < 0) + up->datapt += 80; + } + up->sigamp = up->irig * up->irig + up->qrig * up->qrig; + + /* + * The slice level is set half way between the peak signal and + * noise levels. Sample the negative zero crossing after epoch + * 200 ms and record the epoch at that time. This defines the + * length of the data pulse, which will later be converted into + * scaled bit probabilities. + */ + } else if (up->rphase > 200 * MS) { + dtemp = (up->sigsig + sqrt(up->noiamp)) / 2; + if (up->irig < dtemp && dpulse == 0) + dpulse = up->rphase; + } + + /* + * At the end of the second crank the clock state machine and + * adjust the codec gain. Note the epoch is buffered from the + * center of the second in order to avoid jitter while the + * seconds synch is diddling the epoch. Then, determine the true + * offset and update the median filter in the driver interface. + * + * Sample the data subcarrier envelope at the end of the second + * to determine the SNR for the pulse. This gives a guard time + * of +-30 ms from the decay of the longest pulse to the rise of + * the next pulse. + */ + up->rphase++; + if (up->mphase % SECOND == up->repoch) { + up->datsnr = wwv_snr(up->sigsig, sqrt(up->noiamp)); + wwv_rsec(peer, dpulse); + wwv_gain(peer); + up->rphase = dpulse = 0; + } +} + + +/* + * wwv_rsec - process receiver second + * + * This routine is called at the end of each receiver second to + * implement the per-second state machine. The machine assembles BCD + * digit bits, decodes miscellaneous bits and dances the leap seconds. + * + * Normally, the minute has 60 seconds numbered 0-59. If the leap + * warning bit is set, the last minute (1439) of 30 June (day 181 or 182 + * for leap years) or 31 December (day 365 or 366 for leap years) is + * augmented by one second numbered 60. This is accomplished by + * extending the minute interval by one second and teaching the state + * machine to ignore it. + */ +static void +wwv_rsec( + struct peer *peer, /* peer structure pointer */ + double dpulse + ) +{ + static int iniflg; /* initialization flag */ + static double bcddld[4]; /* BCD data bits */ + static double bitvec[61]; /* bit integrator for misc bits */ + struct refclockproc *pp; + struct wwvunit *up; + struct chan *cp; + struct sync *sp, *rp; + l_fp offset; /* offset in NTP seconds */ + double bit; /* bit likelihood */ + char tbuf[80]; /* monitor buffer */ + int sw, arg, nsec; +#ifdef IRIG_SUCKS + int i; + l_fp ltemp; +#endif /* IRIG_SUCKS */ + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + if (!iniflg) { + iniflg = 1; + memset((char *)bitvec, 0, sizeof(bitvec)); + } + + /* + * The bit represents the probability of a hit on zero (negative + * values), a hit on one (positive values) or a miss (zero + * value). The likelihood vector is the exponential average of + * these probabilities. Only the bits of this vector + * corresponding to the miscellaneous bits of the timecode are + * used, but it's easier to do them all. After that, crank the + * seconds state machine. + */ + nsec = up->rsec + 1; + bit = wwv_data(up, dpulse); + bitvec[up->rsec] += (bit - bitvec[up->rsec]) / TCONST; + sw = progx[up->rsec].sw; + arg = progx[up->rsec].arg; + switch (sw) { + + /* + * Ignore this second. + */ + case IDLE: /* 9, 45-49 */ + break; + + /* + * Probe channel stuff + * + * The WWV/H format contains data pulses in second 59 (position + * identifier), second 1 (not used) and the minute sync pulse in + * second 0. At the end of second 58, QSY to the probe channel, + * which rotates over all WWV/H frequencies. At the end of + * second 1 QSY back to the data channel. + * + * At the end of second 0 save the minute sync pulse peak value + * previously latched at 800 ms. + */ + case SYNC2: /* 0 */ + cp = &up->mitig[up->achan]; + cp->wwv.synmax = sqrt(cp->wwv.synamp); + cp->wwvh.synmax = sqrt(cp->wwvh.synamp); + break; + + /* + * At the end of second 1 determine the minute sync pulse + * amplitude and SNR and set SYNCNG if these values are below + * thresholds. Determine the data pulse amplitude and SNR and + * set DATANG if these values are below thresholds. Set ERRRNG + * if data pulses in second 59 and second 1 are decoded in + * error. Shift a 1 into the reachability register if SYNCNG and + * DATANG are both lit; otherwise shift a 0. Ignore ERRRNG for + * the present. The number of 1 bits in the last six intervals + * represents the channel metric used by the mitigation routine. + * Finally, QSY back to the data channel. + */ + case SYNC3: /* 1 */ + cp = &up->mitig[up->achan]; + cp->sigamp = sqrt(up->sigamp); + cp->noiamp = sqrt(up->noiamp); + cp->datsnr = wwv_snr(cp->sigamp, cp->noiamp); + + /* + * WWV station + */ + sp = &cp->wwv; + sp->synmin = sqrt((sp->synmin + sp->synamp) / 2.); + sp->synsnr = wwv_snr(sp->synmax, sp->synmin); + sp->select &= ~(SYNCNG | DATANG | ERRRNG); + if (sp->synmax < QTHR || sp->synsnr < QSNR) + sp->select |= SYNCNG; + if (cp->sigamp < XTHR || cp->datsnr < XSNR) + sp->select |= DATANG; + if (up->errcnt > 2) + sp->select |= ERRRNG; + sp->reach <<= 1; + if (sp->reach & (1 << AMAX)) + sp->count--; + if (!(sp->select & (SYNCNG | DATANG))) { + sp->reach |= 1; + sp->count++; + } + + /* + * WWVH station + */ + rp = &cp->wwvh; + rp->synmin = sqrt((rp->synmin + rp->synamp) / 2.); + rp->synsnr = wwv_snr(rp->synmax, rp->synmin); + rp->select &= ~(SYNCNG | DATANG | ERRRNG); + if (rp->synmax < QTHR || rp->synsnr < QSNR) + rp->select |= SYNCNG; + if (cp->sigamp < XTHR || cp->datsnr < XSNR) + rp->select |= DATANG; + if (up->errcnt > 2) + rp->select |= ERRRNG; + rp->reach <<= 1; + if (rp->reach & (1 << AMAX)) + rp->count--; + if (!(rp->select & (SYNCNG | DATANG | ERRRNG))) { + rp->reach |= 1; + rp->count++; + } + + /* + * Set up for next minute. + */ + if (pp->sloppyclockflag & CLK_FLAG4) { + sprintf(tbuf, + "wwv5 %2d %04x %3d %4d %d %.0f/%.1f %s %04x %.0f %.0f/%.1f %s %04x %.0f %.0f/%.1f", + up->port, up->status, up->gain, up->yepoch, + up->errcnt, cp->sigamp, cp->datsnr, + sp->refid, sp->reach & 0xffff, + wwv_metric(sp), sp->synmax, sp->synsnr, + rp->refid, rp->reach & 0xffff, + wwv_metric(rp), rp->synmax, rp->synsnr); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif /* DEBUG */ + } +#ifdef ICOM + if (up->fd_icom > 0) + wwv_qsy(peer, up->dchan); +#endif /* ICOM */ + up->status &= ~SFLAG; + up->errcnt = 0; + up->alarm = 0; + wwv_newchan(peer); + break; + + /* + * Save the bit probability in the BCD data vector at the index + * given by the argument. Note that all bits of the vector have + * to be above the data gate threshold for the digit to be + * considered valid. Bits not used in the digit are forced to + * zero and not checked for errors. + */ + case COEF: /* 4-7, 10-13, 15-17, 20-23, + 25-26, 30-33, 35-38, 40-41, + 51-54 */ + if (up->status & DGATE) + up->status |= BGATE; + bcddld[arg] = bit; + break; + + case COEF2: /* 18, 27-28, 42-43 */ + bcddld[arg] = 0; + break; + + /* + * Correlate coefficient vector with each valid digit vector and + * save in decoding matrix. We step through the decoding matrix + * digits correlating each with the coefficients and saving the + * greatest and the next lower for later SNR calculation. + */ + case DECIM2: /* 29 */ + wwv_corr4(peer, &up->decvec[arg], bcddld, bcd2); + break; + + case DECIM3: /* 44 */ + wwv_corr4(peer, &up->decvec[arg], bcddld, bcd3); + break; + + case DECIM6: /* 19 */ + wwv_corr4(peer, &up->decvec[arg], bcddld, bcd6); + break; + + case DECIM9: /* 8, 14, 24, 34, 39 */ + wwv_corr4(peer, &up->decvec[arg], bcddld, bcd9); + break; + + /* + * Miscellaneous bits. If above the positive threshold, declare + * 1; if below the negative threshold, declare 0; otherwise + * raise the SYMERR alarm. At the end of second 58, QSY to the + * probe channel. The design is intended to preserve the bits + * over periods of signal loss. + */ + case MSC20: /* 55 */ + wwv_corr4(peer, &up->decvec[YR + 1], bcddld, bcd9); + /* fall through */ + + case MSCBIT: /* 2-3, 50, 56-57 */ + if (bitvec[up->rsec] > BTHR) + up->misc |= arg; + else if (bitvec[up->rsec] < -BTHR) + up->misc &= ~arg; + else + up->alarm |= SYMERR; + break; + + /* + * Save the data channel gain, then QSY to the probe channel. + */ + case MSC21: /* 58 */ + if (bitvec[up->rsec] > BTHR) + up->misc |= arg; + else if (bitvec[up->rsec] < -BTHR) + up->misc &= ~arg; + else + up->alarm |= SYMERR; + up->mitig[up->dchan].gain = up->gain; +#ifdef ICOM + if (up->fd_icom > 0) { + up->schan = (up->schan + 1) % NCHAN; + wwv_qsy(peer, up->schan); + } +#endif /* ICOM */ + up->status |= SFLAG | SELV | SELH; + up->errbit = up->errcnt; + up->errcnt = 0; + break; + + /* + * The endgames + * + * During second 59 the receiver and codec AGC are settling + * down, so the data pulse is unusable. At the end of this + * second, latch the minute sync pulse noise floor. Then do the + * minute processing and update the system clock. If a leap + * second sail on to the next second (60); otherwise, set up for + * the next minute. + */ + case MIN1: /* 59 */ + cp = &up->mitig[up->achan]; + cp->wwv.synmin = cp->wwv.synamp; + cp->wwvh.synmin = cp->wwvh.synamp; + + /* + * Dance the leap if necessary and the kernel has the + * right stuff. Then, wind up the clock and initialize + * for the following minute. If the leap dance, note the + * kernel is armed one second before the actual leap is + * scheduled. + */ + if (up->status & SSYNC && up->digcnt >= 9) + up->status |= INSYNC; + if (up->status & LEPDAY) { + pp->leap = LEAP_ADDSECOND; + } else { + pp->leap = LEAP_NOWARNING; + wwv_tsec(up); + nsec = up->digcnt = 0; + } + pp->lencode = timecode(up, pp->a_lastcode); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("wwv: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif /* DEBUG */ + if (up->status & INSYNC && up->watch < HOLD) + refclock_receive(peer); + break; + + /* + * If LEPDAY is set on the last minute of 30 June or 31 + * December, the LEPSEC bit is set. At the end of the minute in + * which LEPSEC is set the transmitter and receiver insert an + * extra second (60) in the timescale and the minute sync skips + * a second. We only get to test this wrinkle at intervals of + * about 18 months; the actual mileage may vary. + */ + case MIN2: /* 60 */ + wwv_tsec(up); + nsec = up->digcnt = 0; + break; + } + + /* + * If digit sync has not been acquired before timeout or if no + * station has been heard, game over and restart from scratch. + */ + if (!(up->status & DSYNC) && (!(up->status & (SELV | SELH)) || + up->watch > DIGIT)) { + wwv_newgame(peer); + return; + } + + /* + * If no timestamps have been struck before timeout, game over + * and restart from scratch. + */ + if (up->watch > PANIC) { + wwv_newgame(peer); + return; + } + pp->disp += AUDIO_PHI; + up->rsec = nsec; + +#ifdef IRIG_SUCKS + /* + * You really don't wanna know what comes down here. Leave it to + * say Solaris 2.8 broke the nice clean audio stream, apparently + * affected by a 5-ms sawtooth jitter. Sundown on Solaris. This + * leaves a little twilight. + * + * The scheme involves differentiation, forward learning and + * integration. The sawtooth has a period of 11 seconds. The + * timestamp differences are integrated and subtracted from the + * signal. + */ + ltemp = pp->lastrec; + L_SUB(<emp, &pp->lastref); + if (ltemp.l_f < 0) + ltemp.l_i = -1; + else + ltemp.l_i = 0; + pp->lastref = pp->lastrec; + if (!L_ISNEG(<emp)) + L_CLR(&up->wigwag); + else + L_ADD(&up->wigwag, <emp); + L_SUB(&pp->lastrec, &up->wigwag); + up->wiggle[up->wp] = ltemp; + + /* + * Bottom fisher. To understand this, you have to know about + * velocity microphones and AM transmitters. No further + * explanation is offered, as this is truly a black art. + */ + up->wigbot[up->wp] = pp->lastrec; + for (i = 0; i < WIGGLE; i++) { + if (i != up->wp) + up->wigbot[i].l_ui++; + L_SUB(&pp->lastrec, &up->wigbot[i]); + if (L_ISNEG(&pp->lastrec)) + L_ADD(&pp->lastrec, &up->wigbot[i]); + else + pp->lastrec = up->wigbot[i]; + } + up->wp++; + up->wp %= WIGGLE; +#endif /* IRIG_SUCKS */ + + /* + * If victory has been declared and seconds sync is lit, strike + * a timestamp. It should not be a surprise, especially if the + * radio is not tunable, that sometimes no stations are above + * the noise and the reference ID set to NONE. + */ + if (up->status & INSYNC && up->status & SSYNC) { + pp->second = up->rsec; + pp->minute = up->decvec[MN].digit + up->decvec[MN + + 1].digit * 10; + pp->hour = up->decvec[HR].digit + up->decvec[HR + + 1].digit * 10; + pp->day = up->decvec[DA].digit + up->decvec[DA + + 1].digit * 10 + up->decvec[DA + 2].digit * 100; + pp->year = up->decvec[YR].digit + up->decvec[YR + + 1].digit * 10; + pp->year += 2000; + L_CLR(&offset); + if (!clocktime(pp->day, pp->hour, pp->minute, + pp->second, GMT, up->timestamp.l_ui, + &pp->yearstart, &offset.l_ui)) { + up->errflg = CEVNT_BADTIME; + } else { + up->watch = 0; + pp->disp = 0; + refclock_process_offset(pp, offset, + up->timestamp, PDELAY); + } + } + if ((pp->sloppyclockflag & CLK_FLAG4) && !(up->status & + DSYNC)) { + sprintf(tbuf, + "wwv3 %2d %04x %5.0f %5.1f %5.0f %5.1f %5.0f", + up->rsec, up->status, up->epomax, up->eposnr, + up->sigsig, up->datsnr, bit); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif /* DEBUG */ + } +} + + +/* + * wwv_data - calculate bit probability + * + * This routine is called at the end of the receiver second to calculate + * the probabilities that the previous second contained a zero (P0), one + * (P1) or position indicator (P2) pulse. If not in sync or if the data + * bit is bad, a bit error is declared and the probabilities are forced + * to zero. Otherwise, the data gate is enabled and the probabilities + * are calculated. Note that the data matched filter contributes half + * the pulse width, or 85 ms. + * + * It's important to observe that there are three conditions to + * determine: average to +1 (hit), average to -1 (miss) or average to + * zero (erasure). The erasure condition results from insufficient + * signal (noise) and has no bias toward either a hit or miss. + */ +static double +wwv_data( + struct wwvunit *up, /* driver unit pointer */ + double pulse /* pulse length (sample units) */ + ) +{ + double p0, p1, p2; /* probabilities */ + double dpulse; /* pulse length in ms */ + + p0 = p1 = p2 = 0; + dpulse = pulse - DATSIZ / 2; + + /* + * If no station is being tracked, if either the data amplitude + * or SNR are below threshold or if the pulse length is less + * than 170 ms, declare an erasure. + */ + if (!(up->status & (SELV | SELH)) || up->sigsig < DTHR || + up->datsnr < DSNR || dpulse < DATSIZ) { + up->status |= DGATE; + up->errcnt++; + if (up->errcnt > MAXERR) + up->alarm |= MODERR; + return (0); + } + + /* + * The probability of P0 is one below 200 ms falling to zero at + * 500 ms. The probability of P1 is zero below 200 ms rising to + * one at 500 ms and falling to zero at 800 ms. The probability + * of P2 is zero below 500 ms, rising to one above 800 ms. + */ + up->status &= ~DGATE; + if (dpulse < (200 * MS)) { + p0 = 1; + } else if (dpulse < 500 * MS) { + dpulse -= 200 * MS; + p1 = dpulse / (300 * MS); + p0 = 1 - p1; + } else if (dpulse < 800 * MS) { + dpulse -= 500 * MS; + p2 = dpulse / (300 * MS); + p1 = 1 - p2; + } else { + p2 = 1; + } + + /* + * The ouput is a metric that ranges from -1 (P0), to +1 (P1) + * scaled for convenience. An output of zero represents an + * erasure, either because of a data error or pulse length + * greater than 500 ms. At the moment, we don't use P2. + */ + return ((p1 - p0) * MAXSIG); +} + + +/* + * wwv_corr4 - determine maximum likelihood digit + * + * This routine correlates the received digit vector with the BCD + * coefficient vectors corresponding to all valid digits at the given + * position in the decoding matrix. The maximum value corresponds to the + * maximum likelihood digit, while the ratio of this value to the next + * lower value determines the likelihood function. Note that, if the + * digit is invalid, the likelihood vector is averaged toward a miss. + */ +static void +wwv_corr4( + struct peer *peer, /* peer unit pointer */ + struct decvec *vp, /* decoding table pointer */ + double data[], /* received data vector */ + double tab[][4] /* correlation vector array */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + + double topmax, nxtmax; /* metrics */ + double acc; /* accumulator */ + char tbuf[80]; /* monitor buffer */ + int mldigit; /* max likelihood digit */ + int diff; /* decoding difference */ + int i, j; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Correlate digit vector with each BCD coefficient vector. If + * any BCD digit bit is bad, consider all bits a miss. + */ + mldigit = 0; + topmax = nxtmax = -MAXSIG; + for (i = 0; tab[i][0] != 0; i++) { + acc = 0; + for (j = 0; j < 4; j++) { + if (!(up->status & BGATE)) + acc += data[j] * tab[i][j]; + } + acc = (vp->like[i] += (acc - vp->like[i]) / TCONST); + if (acc > topmax) { + nxtmax = topmax; + topmax = acc; + mldigit = i; + } else if (acc > nxtmax) { + nxtmax = acc; + } + } + vp->mldigit = mldigit; + vp->digprb = topmax; + vp->digsnr = wwv_snr(topmax, nxtmax); + + /* + * The maximum likelihood digit is compared with the current + * clock digit. The difference represents the decoding phase + * error. If the clock is not yet synchronized, the phase error + * is corrected even of the digit probability and likelihood are + * below thresholds. This avoids lengthy averaging times should + * a carry mistake occur. However, the digit is not declared + * synchronized until these values are above thresholds and the + * last five decoded values are identical. If the clock is + * synchronized, the phase error is not corrected unless the + * last five digits are all above thresholds and identical. This + * avoids mistakes when the signal is coming out of the noise + * and the SNR is very marginal. + */ + diff = mldigit - vp->digit; + if (diff < 0) + diff += vp->radix; + if (diff != vp->phase) { + vp->count = 0; + vp->phase = diff; + } + if (vp->digsnr < BSNR) { + vp->count = 0; + up->alarm |= SYMERR; + } else if (vp->digprb < BTHR) { + vp->count = 0; + up->alarm |= SYMERR; + if (!(up->status & INSYNC)) { + vp->phase = 0; + vp->digit = mldigit; + } + } else if (vp->count < BCMP) { + vp->count++; + up->status |= DSYNC; + if (!(up->status & INSYNC)) { + vp->phase = 0; + vp->digit = mldigit; + } + } else { + vp->phase = 0; + vp->digit = mldigit; + up->digcnt++; + } + if (vp->digit != mldigit) + up->alarm |= DECERR; + if ((pp->sloppyclockflag & CLK_FLAG4) && !(up->status & + INSYNC)) { + sprintf(tbuf, + "wwv4 %2d %04x %5.0f %2d %d %d %d %d %5.0f %5.1f", + up->rsec, up->status, up->epomax, vp->radix, + vp->digit, vp->mldigit, vp->phase, vp->count, + vp->digprb, vp->digsnr); + record_clock_stats(&peer->srcadr, tbuf); +#ifdef DEBUG + if (debug) + printf("%s\n", tbuf); +#endif /* DEBUG */ + } + up->status &= ~BGATE; +} + + +/* + * wwv_tsec - transmitter minute processing + * + * This routine is called at the end of the transmitter minute. It + * implements a state machine that advances the logical clock subject to + * the funny rules that govern the conventional clock and calendar. + */ +static void +wwv_tsec( + struct wwvunit *up /* driver structure pointer */ + ) +{ + int minute, day, isleap; + int temp; + + /* + * Advance minute unit of the day. + */ + temp = carry(&up->decvec[MN]); /* minute units */ + + /* + * Propagate carries through the day. + */ + if (temp == 0) /* carry minutes */ + temp = carry(&up->decvec[MN + 1]); + if (temp == 0) /* carry hours */ + temp = carry(&up->decvec[HR]); + if (temp == 0) + temp = carry(&up->decvec[HR + 1]); + + /* + * Decode the current minute and day. Set leap day if the + * timecode leap bit is set on 30 June or 31 December. Set leap + * minute if the last minute on leap day. This code fails in + * 2400 AD. + */ + minute = up->decvec[MN].digit + up->decvec[MN + 1].digit * + 10 + up->decvec[HR].digit * 60 + up->decvec[HR + + 1].digit * 600; + day = up->decvec[DA].digit + up->decvec[DA + 1].digit * 10 + + up->decvec[DA + 2].digit * 100; + isleap = (up->decvec[YR].digit & 0x3) == 0; + if (up->misc & SECWAR && (day == (isleap ? 182 : 183) || day == + (isleap ? 365 : 366)) && up->status & INSYNC && up->status & + SSYNC) + up->status |= LEPDAY; + else + up->status &= ~LEPDAY; + if (up->status & LEPDAY && minute == 1439) + up->status |= LEPSEC; + else + up->status &= ~LEPSEC; + + /* + * Roll the day if this the first minute and propagate carries + * through the year. + */ + if (minute != 1440) + return; + minute = 0; + while (carry(&up->decvec[HR]) != 0); /* advance to minute 0 */ + while (carry(&up->decvec[HR + 1]) != 0); + day++; + temp = carry(&up->decvec[DA]); /* carry days */ + if (temp == 0) + temp = carry(&up->decvec[DA + 1]); + if (temp == 0) + temp = carry(&up->decvec[DA + 2]); + + /* + * Roll the year if this the first day and propagate carries + * through the century. + */ + if (day != (isleap ? 365 : 366)) + return; + day = 1; + while (carry(&up->decvec[DA]) != 1); /* advance to day 1 */ + while (carry(&up->decvec[DA + 1]) != 0); + while (carry(&up->decvec[DA + 2]) != 0); + temp = carry(&up->decvec[YR]); /* carry years */ + if (temp) + carry(&up->decvec[YR + 1]); +} + + +/* + * carry - process digit + * + * This routine rotates a likelihood vector one position and increments + * the clock digit modulo the radix. It returns the new clock digit or + * zero if a carry occurred. Once synchronized, the clock digit will + * match the maximum likelihood digit corresponding to that position. + */ +static int +carry( + struct decvec *dp /* decoding table pointer */ + ) +{ + int temp; + int j; + + dp->digit++; /* advance clock digit */ + if (dp->digit == dp->radix) { /* modulo radix */ + dp->digit = 0; + } + temp = dp->like[dp->radix - 1]; /* rotate likelihood vector */ + for (j = dp->radix - 1; j > 0; j--) + dp->like[j] = dp->like[j - 1]; + dp->like[0] = temp; + return (dp->digit); +} + + +/* + * wwv_snr - compute SNR or likelihood function + */ +static double +wwv_snr( + double signal, /* signal */ + double noise /* noise */ + ) +{ + double rval; + + /* + * This is a little tricky. Due to the way things are measured, + * either or both the signal or noise amplitude can be negative + * or zero. The intent is that, if the signal is negative or + * zero, the SNR must always be zero. This can happen with the + * subcarrier SNR before the phase has been aligned. On the + * other hand, in the likelihood function the "noise" is the + * next maximum down from the peak and this could be negative. + * However, in this case the SNR is truly stupendous, so we + * simply cap at MAXSNR dB. + */ + if (signal <= 0) { + rval = 0; + } else if (noise <= 0) { + rval = MAXSNR; + } else { + rval = 20 * log10(signal / noise); + if (rval > MAXSNR) + rval = MAXSNR; + } + return (rval); +} + + +/* + * wwv_newchan - change to new data channel + * + * The radio actually appears to have ten channels, one channel for each + * of five frequencies and each of two stations (WWV and WWVH), although + * if not tunable only the 15 MHz channels appear live. While the radio + * is tuned to the working data channel frequency and station for most + * of the minute, during seconds 59, 0 and 1 the radio is tuned to a + * probe frequency in order to search for minute sync pulse and data + * subcarrier from other transmitters. + * + * The search for WWV and WWVH operates simultaneously, with WWV minute + * sync pulse at 1000 Hz and WWVH at 1200 Hz. The probe frequency + * rotates each minute over 2.5, 5, 10, 15 and 20 MHz in order and yes, + * we all know WWVH is dark on 20 MHz, but few remember when WWV was lit + * on 25 MHz. + * + * This routine selects the best channel using a metric computed from + * the reachability register and minute pulse amplitude. Normally, the + * award goes to the the channel with the highest metric; but, in case + * of ties, the award goes to the channel with the highest minute sync + * pulse amplitude and then to the highest frequency. + * + * The routine performs an important squelch function to keep dirty data + * from polluting the integrators. During acquisition and until the + * clock is synchronized, the signal metric must be at least MTR (13); + * after that the metrict must be at least TTHR (50). If either of these + * is not true, the station select bits are cleared so the second sync + * is disabled and the data bit integrators averaged to a miss. + */ +static void +wwv_newchan( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + struct sync *sp, *rp; + double rank, dtemp; + int i, j; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Search all five station pairs looking for the channel with + * maximum metric. If no station is found above thresholds, the + * reference ID is set to NONE and we wait for hotter ions. + */ + j = 0; + sp = NULL; + rank = 0; + for (i = 0; i < NCHAN; i++) { + rp = &up->mitig[i].wwvh; + dtemp = wwv_metric(rp); + if (dtemp >= rank) { + rank = dtemp; + sp = rp; + j = i; + } + rp = &up->mitig[i].wwv; + dtemp = wwv_metric(rp); + if (dtemp >= rank) { + rank = dtemp; + sp = rp; + j = i; + } + } + up->dchan = j; + up->sptr = sp; + up->status &= ~(SELV | SELH); + memcpy(&pp->refid, "NONE", 4); + if ((!(up->status & INSYNC) && rank >= MTHR) || ((up->status & + INSYNC) && rank >= TTHR)) { + up->status |= sp->select & (SELV | SELH); + memcpy(&pp->refid, sp->refid, 4); + } + if (peer->stratum <= 1) + memcpy(&peer->refid, &pp->refid, 4); +} + + +/* + * www_newgame - reset and start over + */ +static void +wwv_newgame( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + struct chan *cp; + int i; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Initialize strategic values. Note we set the leap bits + * NOTINSYNC and the refid "NONE". + */ + peer->leap = LEAP_NOTINSYNC; + up->watch = up->status = up->alarm = 0; + up->avgint = MINAVG; + up->freq = 0; + up->sptr = NULL; + up->gain = MAXGAIN / 2; + + /* + * Initialize the station processes for audio gain, select bit, + * station/frequency identifier and reference identifier. + */ + memset(up->mitig, 0, sizeof(up->mitig)); + for (i = 0; i < NCHAN; i++) { + cp = &up->mitig[i]; + cp->gain = up->gain; + cp->wwv.select = SELV; + sprintf(cp->wwv.refid, "WV%.0f", floor(qsy[i])); + cp->wwvh.select = SELH; + sprintf(cp->wwvh.refid, "WH%.0f", floor(qsy[i])); + } + wwv_newchan(peer); +} + +/* + * wwv_metric - compute station metric + * + * The most significant bits represent the number of ones in the + * reachability register. The least significant bits represent the + * minute sync pulse amplitude. The combined value is scaled 0-100. + */ +double +wwv_metric( + struct sync *sp /* station pointer */ + ) +{ + double dtemp; + + dtemp = sp->count * MAXSIG; + if (sp->synmax < MAXSIG) + dtemp += sp->synmax; + else + dtemp += MAXSIG - 1; + dtemp /= (AMAX + 1) * MAXSIG; + return (dtemp * 100.); +} + + +#ifdef ICOM +/* + * wwv_qsy - Tune ICOM receiver + * + * This routine saves the AGC for the current channel, switches to a new + * channel and restores the AGC for that channel. If a tunable receiver + * is not available, just fake it. + */ +static int +wwv_qsy( + struct peer *peer, /* peer structure pointer */ + int chan /* channel */ + ) +{ + int rval = 0; + struct refclockproc *pp; + struct wwvunit *up; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + if (up->fd_icom > 0) { + up->mitig[up->achan].gain = up->gain; + rval = icom_freq(up->fd_icom, peer->ttl & 0x7f, + qsy[chan]); + up->achan = chan; + up->gain = up->mitig[up->achan].gain; + } + return (rval); +} +#endif /* ICOM */ + + +/* + * timecode - assemble timecode string and length + * + * Prettytime format - similar to Spectracom + * + * sq yy ddd hh:mm:ss ld dut lset agc iden sig errs freq avgt + * + * s sync indicator ('?' or ' ') + * q error bits (hex 0-F) + * yyyy year of century + * ddd day of year + * hh hour of day + * mm minute of hour + * ss second of minute) + * l leap second warning (' ' or 'L') + * d DST state ('S', 'D', 'I', or 'O') + * dut DUT sign and magnitude (0.1 s) + * lset minutes since last clock update + * agc audio gain (0-255) + * iden reference identifier (station and frequency) + * sig signal quality (0-100) + * errs bit errors in last minute + * freq frequency offset (PPM) + * avgt averaging time (s) + */ +static int +timecode( + struct wwvunit *up, /* driver structure pointer */ + char *ptr /* target string */ + ) +{ + struct sync *sp; + int year, day, hour, minute, second, dut; + char synchar, leapchar, dst; + char cptr[50]; + + + /* + * Common fixed-format fields + */ + synchar = (up->status & INSYNC) ? ' ' : '?'; + year = up->decvec[YR].digit + up->decvec[YR + 1].digit * 10 + + 2000; + day = up->decvec[DA].digit + up->decvec[DA + 1].digit * 10 + + up->decvec[DA + 2].digit * 100; + hour = up->decvec[HR].digit + up->decvec[HR + 1].digit * 10; + minute = up->decvec[MN].digit + up->decvec[MN + 1].digit * 10; + second = 0; + leapchar = (up->misc & SECWAR) ? 'L' : ' '; + dst = dstcod[(up->misc >> 4) & 0x3]; + dut = up->misc & 0x7; + if (!(up->misc & DUTS)) + dut = -dut; + sprintf(ptr, "%c%1X", synchar, up->alarm); + sprintf(cptr, " %4d %03d %02d:%02d:%02d %c%c %+d", + year, day, hour, minute, second, leapchar, dst, dut); + strcat(ptr, cptr); + + /* + * Specific variable-format fields + */ + sp = up->sptr; + sprintf(cptr, " %d %d %s %.0f %d %.1f %d", up->watch, + up->mitig[up->dchan].gain, sp->refid, wwv_metric(sp), + up->errbit, up->freq / SECOND * 1e6, up->avgint); + strcat(ptr, cptr); + return (strlen(ptr)); +} + + +/* + * wwv_gain - adjust codec gain + * + * This routine is called at the end of each second. It counts the + * number of signal clips above the MAXSIG threshold during the previous + * second. If there are no clips, the gain is bumped up; if too many + * clips, it is bumped down. The decoder is relatively insensitive to + * amplitude, so this crudity works just fine. The input port is set and + * the error flag is cleared, mostly to be ornery. + */ +static void +wwv_gain( + struct peer *peer /* peer structure pointer */ + ) +{ + struct refclockproc *pp; + struct wwvunit *up; + + pp = peer->procptr; + up = (struct wwvunit *)pp->unitptr; + + /* + * Apparently, the codec uses only the high order bits of the + * gain control field. Thus, it may take awhile for changes to + * wiggle the hardware bits. + */ + if (up->clipcnt == 0) { + up->gain += 4; + if (up->gain > MAXGAIN) + up->gain = MAXGAIN; + } else if (up->clipcnt > MAXCLP) { + up->gain -= 4; + if (up->gain < 0) + up->gain = 0; + } + audio_gain(up->gain, up->mongain, up->port); + up->clipcnt = 0; +#if DEBUG + if (debug > 1) + audio_show(); +#endif +} + + +#else +int refclock_wwv_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_wwvb.c b/ntpd/refclock_wwvb.c new file mode 100644 index 0000000..c5ef9f9 --- /dev/null +++ b/ntpd/refclock_wwvb.c @@ -0,0 +1,441 @@ +/* + * refclock_wwvb - clock driver for Spectracom WWVB receivers + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_calendar.h" +#include "ntp_stdlib.h" + +#include <stdio.h> +#include <ctype.h> + +/* + * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB + * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB + * and GPS clocks have proven reliable sources of time; however, the + * WWVB clocks have proven vulnerable to high ambient conductive RF + * interference. The claimed accuracy of the WWVB clocks is 100 us + * relative to the broadcast signal, while the claimed accuracy of the + * GPS clock is 50 ns; however, in most cases the actual accuracy is + * limited by the resolution of the timecode and the latencies of the + * serial interface and operating system. + * + * The WWVB and GPS clocks should be configured for 24-hour display, + * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and + * baud rate 9600. If the clock is to used as the source for the IRIG + * Audio Decoder (refclock_irig.c in this distribution), it should be + * configured for AM IRIG output and IRIG format 1 (IRIG B with + * signature control). The GPS clock can be configured either to respond + * to a 'T' poll character or left running continuously. + * + * There are two timecode formats used by these clocks. Format 0, which + * is available with both the Netclock/2 and 8170, and format 2, which + * is available only with the Netclock/2, specially modified 8170 and + * GPS. + * + * Format 0 (22 ASCII printing characters): + * + * <cr><lf>i ddd hh:mm:ss TZ=zz<cr><lf> + * + * on-time = first <cr> + * hh:mm:ss = hours, minutes, seconds + * i = synchronization flag (' ' = in synch, '?' = out of synch) + * + * The alarm condition is indicated by other than ' ' at a, which occurs + * during initial synchronization and when received signal is lost for + * about ten hours. + * + * Format 2 (24 ASCII printing characters): + * + * <cr><lf>iqyy ddd hh:mm:ss.fff ld + * + * on-time = <cr> + * i = synchronization flag (' ' = in synch, '?' = out of synch) + * q = quality indicator (' ' = locked, 'A'...'D' = unlocked) + * yy = year (as broadcast) + * ddd = day of year + * hh:mm:ss.fff = hours, minutes, seconds, milliseconds + * + * The alarm condition is indicated by other than ' ' at a, which occurs + * during initial synchronization and when received signal is lost for + * about ten hours. The unlock condition is indicated by other than ' ' + * at q. + * + * The q is normally ' ' when the time error is less than 1 ms and a + * character in the set 'A'...'D' when the time error is less than 10, + * 100, 500 and greater than 500 ms respectively. The l is normally ' ', + * but is set to 'L' early in the month of an upcoming UTC leap second + * and reset to ' ' on the first day of the following month. The d is + * set to 'S' for standard time 'I' on the day preceding a switch to + * daylight time, 'D' for daylight time and 'O' on the day preceding a + * switch to standard time. The start bit of the first <cr> is + * synchronized to the indicated time as returned. + * + * This driver does not need to be told which format is in use - it + * figures out which one from the length of the message.The driver makes + * no attempt to correct for the intrinsic jitter of the radio itself, + * which is a known problem with the older radios. + * + * Fudge Factors + * + * This driver can retrieve a table of quality data maintained + * internally by the Netclock/2 clock. If flag4 of the fudge + * configuration command is set to 1, the driver will retrieve this + * table and write it to the clockstats file on when the first timecode + * message of a new day is received. + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/wwvb%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-13) /* precision assumed (about 100 us) */ +#define REFID "WWVB" /* reference ID */ +#define DESCRIPTION "Spectracom WWVB/GPS Receivers" /* WRU */ + +#define LENWWVB0 22 /* format 0 timecode length */ +#define LENWWVB1 22 /* format 1 timecode length */ +#define LENWWVB2 24 /* format 2 timecode length */ +#define LENWWVB3 29 /* format 3 timecode length */ +#define MONLIN 15 /* number of monitoring lines */ + +/* + * WWVB unit control structure + */ +struct wwvbunit { + u_char tcswitch; /* timecode switch */ + l_fp laststamp; /* last receive timestamp */ + u_char lasthour; /* last hour (for monitor) */ + u_char linect; /* count ignored lines (for monitor */ +}; + +/* + * Function prototypes + */ +static int wwvb_start P((int, struct peer *)); +static void wwvb_shutdown P((int, struct peer *)); +static void wwvb_receive P((struct recvbuf *)); +static void wwvb_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_wwvb = { + wwvb_start, /* start up driver */ + wwvb_shutdown, /* shut down driver */ + wwvb_poll, /* transmit poll message */ + noentry, /* not used (old wwvb_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old wwvb_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * wwvb_start - open the devices and initialize data for processing + */ +static int +wwvb_start( + int unit, + struct peer *peer + ) +{ + register struct wwvbunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. Use CLK line discipline, if available. + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct wwvbunit *) + emalloc(sizeof(struct wwvbunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct wwvbunit)); + pp = peer->procptr; + pp->unitptr = (caddr_t)up; + pp->io.clock_recv = wwvb_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + peer->burst = MAXSTAGE; + return (1); +} + + +/* + * wwvb_shutdown - shut down the clock + */ +static void +wwvb_shutdown( + int unit, + struct peer *peer + ) +{ + register struct wwvbunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct wwvbunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * wwvb_receive - receive data from the serial interface + */ +static void +wwvb_receive( + struct recvbuf *rbufp + ) +{ + struct wwvbunit *up; + struct refclockproc *pp; + struct peer *peer; + + l_fp trtmp; /* arrival timestamp */ + int tz; /* time zone */ + int day, month; /* ddd conversion */ + int temp; /* int temp */ + char syncchar; /* synchronization indicator */ + char qualchar; /* quality indicator */ + char leapchar; /* leap indicator */ + char dstchar; /* daylight/standard indicator */ + char tmpchar; /* trashbin */ + + /* + * Initialize pointers and read the timecode and timestamp + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct wwvbunit *)pp->unitptr; + temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + + /* + * Note we get a buffer and timestamp for both a <cr> and <lf>, + * but only the <cr> timestamp is retained. Note: in format 0 on + * a Netclock/2 or upgraded 8170 the start bit is delayed 100 + * +-50 us relative to the pps; however, on an unmodified 8170 + * the start bit can be delayed up to 10 ms. In format 2 the + * reading precision is only to the millisecond. Thus, unless + * you have a pps gadget and don't have to have the year, format + * 0 provides the lowest jitter. + */ + if (temp == 0) { + if (up->tcswitch == 0) { + up->tcswitch = 1; + up->laststamp = trtmp; + } else + up->tcswitch = 0; + return; + } + pp->lencode = temp; + pp->lastrec = up->laststamp; + up->laststamp = trtmp; + up->tcswitch = 1; + + /* + * We get down to business, check the timecode format and decode + * its contents. This code uses the timecode length to determine + * format 0, 2 or 3. If the timecode has invalid length or is + * not in proper format, we declare bad format and exit. + */ + syncchar = qualchar = leapchar = dstchar = ' '; + tz = 0; + switch (pp->lencode) { + + case LENWWVB0: + + /* + * Timecode format 0: "I ddd hh:mm:ss DTZ=nn" + */ + if (sscanf(pp->a_lastcode, + "%c %3d %2d:%2d:%2d%c%cTZ=%2d", + &syncchar, &pp->day, &pp->hour, &pp->minute, + &pp->second, &tmpchar, &dstchar, &tz) == 8) + pp->nsec = 0; + break; + + case LENWWVB2: + + /* + * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */ + if (sscanf(pp->a_lastcode, + "%c%c %2d %3d %2d:%2d:%2d.%3ld %c", + &syncchar, &qualchar, &pp->year, &pp->day, + &pp->hour, &pp->minute, &pp->second, &pp->nsec, + &leapchar) == 9) + pp->nsec *= 1000000; + break; + + case LENWWVB3: + + /* + * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#" + */ + if (sscanf(pp->a_lastcode, + "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c", + &syncchar, &pp->year, &month, &day, &pp->hour, + &pp->minute, &pp->second, &dstchar, &leapchar) == 8) + { + pp->day = ymd2yd(pp->year, month, day); + pp->nsec = 0; + break; + } + + default: + + /* + * Unknown format: If dumping internal table, record + * stats; otherwise, declare bad format. + */ + if (up->linect > 0) { + up->linect--; + record_clock_stats(&peer->srcadr, + pp->a_lastcode); + } else { + refclock_report(peer, CEVNT_BADREPLY); + } + return; + } + + /* + * Decode synchronization, quality and leap characters. If + * unsynchronized, set the leap bits accordingly and exit. + * Otherwise, set the leap bits according to the leap character. + * Once synchronized, the dispersion depends only on the + * quality character. + */ + switch (qualchar) { + + case ' ': + pp->disp = .001; + pp->lastref = pp->lastrec; + break; + + case 'A': + pp->disp = .01; + break; + + case 'B': + pp->disp = .1; + break; + + case 'C': + pp->disp = .5; + break; + + case 'D': + pp->disp = MAXDISPERSE; + break; + + default: + pp->disp = MAXDISPERSE; + refclock_report(peer, CEVNT_BADREPLY); + break; + } + if (syncchar != ' ') + pp->leap = LEAP_NOTINSYNC; + else if (leapchar == 'L') + pp->leap = LEAP_ADDSECOND; + else + pp->leap = LEAP_NOWARNING; + + /* + * Process the new sample in the median filter and determine the + * timecode timestamp. + */ + if (!refclock_process(pp)) + refclock_report(peer, CEVNT_BADTIME); +} + + +/* + * wwvb_poll - called by the transmit procedure + */ +static void +wwvb_poll( + int unit, + struct peer *peer + ) +{ + register struct wwvbunit *up; + struct refclockproc *pp; + char pollchar; /* character sent to clock */ + + /* + * Time to poll the clock. The Spectracom clock responds to a + * 'T' by returning a timecode in the format(s) specified above. + * Note there is no checking on state, since this may not be the + * only customer reading the clock. Only one customer need poll + * the clock; all others just listen in. If the clock becomes + * unreachable, declare a timeout and keep going. + */ + pp = peer->procptr; + up = (struct wwvbunit *)pp->unitptr; + if (up->linect > 0) + pollchar = 'R'; + else + pollchar = 'T'; + if (write(pp->io.fd, &pollchar, 1) != 1) + refclock_report(peer, CEVNT_FAULT); + if (peer->burst > 0) + return; + if (pp->coderecv == pp->codeproc) { + refclock_report(peer, CEVNT_TIMEOUT); + return; + } + refclock_receive(peer); + record_clock_stats(&peer->srcadr, pp->a_lastcode); +#ifdef DEBUG + if (debug) + printf("wwvb: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + peer->burst = MAXSTAGE; + pp->polls++; + + /* + * If the monitor flag is set (flag4), we dump the internal + * quality table at the first timecode beginning the day. + */ + if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour < + (int)up->lasthour) + up->linect = MONLIN; + up->lasthour = pp->hour; +} + +#else +int refclock_wwvb_bs; +#endif /* REFCLOCK */ diff --git a/ntpd/refclock_zyfer.c b/ntpd/refclock_zyfer.c new file mode 100644 index 0000000..44f2c4d --- /dev/null +++ b/ntpd/refclock_zyfer.c @@ -0,0 +1,346 @@ +/* + * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock + * + * Harlan Stenn, Jan 2002 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_ZYFER) + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_unixtime.h" + +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_SYS_TERMIOS_H +# include <sys/termios.h> +#endif +#ifdef HAVE_SYS_PPSCLOCK_H +# include <sys/ppsclock.h> +#endif + +/* + * This driver provides support for the TOD serial port of a Zyfer GPStarplus. + * This clock also provides PPS as well as IRIG outputs. + * Precision is limited by the serial driver, etc. + * + * If I was really brave I'd hack/generalize the serial driver to deal + * with arbitrary on-time characters. This clock *begins* the stream with + * `!`, the on-time character, and the string is *not* EOL-terminated. + * + * Configure the beast for 9600, 8N1. While I see leap-second stuff + * in the documentation, the published specs on the TOD format only show + * the seconds going to '59'. I see no leap warning in the TOD format. + * + * The clock sends the following message once per second: + * + * !TIME,2002,017,07,59,32,2,4,1 + * YYYY DDD HH MM SS m T O + * + * ! On-time character + * YYYY Year + * DDD 001-366 Day of Year + * HH 00-23 Hour + * MM 00-59 Minute + * SS 00-59 Second (probably 00-60) + * m 1-5 Time Mode: + * 1 = GPS time + * 2 = UTC time + * 3 = LGPS time (Local GPS) + * 4 = LUTC time (Local UTC) + * 5 = Manual time + * T 4-9 Time Figure Of Merit: + * 4 x <= 1us + * 5 1us < x <= 10 us + * 6 10us < x <= 100us + * 7 100us < x <= 1ms + * 8 1ms < x <= 10ms + * 9 10ms < x + * O 0-4 Operation Mode: + * 0 Warm-up + * 1 Time Locked + * 2 Coasting + * 3 Recovering + * 4 Manual + * + */ + +/* + * Interface definitions + */ +#define DEVICE "/dev/zyfer%d" /* device name and unit */ +#define SPEED232 B9600 /* uart speed (9600 baud) */ +#define PRECISION (-20) /* precision assumed (about 1 us) */ +#define REFID "GPS\0" /* reference ID */ +#define DESCRIPTION "Zyfer GPStarplus" /* WRU */ + +#define LENZYFER 29 /* timecode length */ + +/* + * Unit control structure + */ +struct zyferunit { + u_char Rcvbuf[LENZYFER + 1]; + u_char polled; /* poll message flag */ + int pollcnt; + l_fp tstamp; /* timestamp of last poll */ + int Rcvptr; +}; + +/* + * Function prototypes + */ +static int zyfer_start P((int, struct peer *)); +static void zyfer_shutdown P((int, struct peer *)); +static void zyfer_receive P((struct recvbuf *)); +static void zyfer_poll P((int, struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_zyfer = { + zyfer_start, /* start up driver */ + zyfer_shutdown, /* shut down driver */ + zyfer_poll, /* transmit poll message */ + noentry, /* not used (old zyfer_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old zyfer_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * zyfer_start - open the devices and initialize data for processing + */ +static int +zyfer_start( + int unit, + struct peer *peer + ) +{ + register struct zyferunit *up; + struct refclockproc *pp; + int fd; + char device[20]; + + /* + * Open serial port. + * Something like LDISC_ACTS that looked for ! would be nice... + */ + (void)sprintf(device, DEVICE, unit); + if ( !(fd = refclock_open(device, SPEED232, LDISC_RAW)) ) + return (0); + + msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct zyferunit *) + emalloc(sizeof(struct zyferunit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct zyferunit)); + pp = peer->procptr; + pp->io.clock_recv = zyfer_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->pollcnt = 2; + up->polled = 0; /* May not be needed... */ + + return (1); +} + + +/* + * zyfer_shutdown - shut down the clock + */ +static void +zyfer_shutdown( + int unit, + struct peer *peer + ) +{ + register struct zyferunit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct zyferunit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * zyfer_receive - receive data from the serial interface + */ +static void +zyfer_receive( + struct recvbuf *rbufp + ) +{ + register struct zyferunit *up; + struct refclockproc *pp; + struct peer *peer; + int tmode; /* Time mode */ + int tfom; /* Time Figure Of Merit */ + int omode; /* Operation mode */ + u_char *p; +#ifdef PPS + struct ppsclockev ppsev; + int request; +#ifdef HAVE_CIOGETEV + request = CIOGETEV; +#endif +#ifdef HAVE_TIOCGPPSEV + request = TIOCGPPSEV; +#endif +#endif /* PPS */ + + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct zyferunit *)pp->unitptr; + p = (u_char *) &rbufp->recv_space; + /* + * If lencode is 0: + * - if *rbufp->recv_space is ! + * - - call refclock_gtlin to get things going + * - else flush + * else stuff it on the end of lastcode + * If we don't have LENZYFER bytes + * - wait for more data + * Crack the beast, and if it's OK, process it. + * + * We use refclock_gtlin() because we might use LDISC_CLK. + * + * Under FreeBSD, we get the ! followed by two 14-byte packets. + */ + + if (pp->lencode >= LENZYFER) + pp->lencode = 0; + + if (!pp->lencode) { + if (*p == '!') + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, + BMAX, &pp->lastrec); + else + return; + } else { + memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length); + pp->lencode += rbufp->recv_length; + pp->a_lastcode[pp->lencode] = '\0'; + } + + if (pp->lencode < LENZYFER) + return; + + record_clock_stats(&peer->srcadr, pp->a_lastcode); + + /* + * We get down to business, check the timecode format and decode + * its contents. If the timecode has invalid length or is not in + * proper format, we declare bad format and exit. + */ + + if (pp->lencode != LENZYFER) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1" + */ + if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d", + &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second, + &tmode, &tfom, &omode) != 8) { + refclock_report(peer, CEVNT_BADREPLY); + return; + } + + if (tmode != 2) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* Should we make sure tfom is 4? */ + + if (omode != 1) { + pp->leap = LEAP_NOTINSYNC; + return; + } +#ifdef PPS + if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) { + ppsev.tv.tv_sec += (u_int32) JAN_1970; + TVTOTS(&ppsev.tv,&up->tstamp); + } + /* record the last ppsclock event time stamp */ + pp->lastrec = up->tstamp; +#endif /* PPS */ + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + + /* + * Good place for record_clock_stats() + */ + up->pollcnt = 2; + + if (up->polled) { + up->polled = 0; + refclock_receive(peer); + } +} + + +/* + * zyfer_poll - called by the transmit procedure + */ +static void +zyfer_poll( + int unit, + struct peer *peer + ) +{ + register struct zyferunit *up; + struct refclockproc *pp; + + /* + * We don't really do anything here, except arm the receiving + * side to capture a sample and check for timeouts. + */ + pp = peer->procptr; + up = (struct zyferunit *)pp->unitptr; + if (!up->pollcnt) + refclock_report(peer, CEVNT_TIMEOUT); + else + up->pollcnt--; + pp->polls++; + up->polled = 1; +} + +#else +int refclock_zyfer_bs; +#endif /* REFCLOCK */ |