154359Sroberto/*
254359Sroberto * clocktime - compute the NTP date from a day of year, hour, minute
354359Sroberto *	       and second.
454359Sroberto */
5285612Sdelphij#include <config.h>
654359Sroberto#include "ntp_fp.h"
754359Sroberto#include "ntp_unixtime.h"
854359Sroberto#include "ntp_stdlib.h"
9285612Sdelphij#include "ntp_calendar.h"
1054359Sroberto
1154359Sroberto/*
12285612Sdelphij * We check that the time be within CLOSETIME seconds of the receive
13285612Sdelphij * time stamp.	This is about 4 hours, which hopefully should be wide
14285612Sdelphij * enough to collect most data, while close enough to keep things from
15285612Sdelphij * getting confused.
1654359Sroberto */
17285612Sdelphij#define	CLOSETIME	(4u*60u*60u)
1854359Sroberto
1954359Sroberto/*
20285612Sdelphij * Since we try to match years, the result of a full search will not
21285612Sdelphij * change when we are already less than a half year from the receive
22285612Sdelphij * time stamp.	Since the length of a year is variable we use a
23285612Sdelphij * slightly narrower limit; this might require a full evaluation near
24285612Sdelphij * the edge, but will make sure we always get the correct result.
2554359Sroberto */
26285612Sdelphij#define NEARTIME	(182u * SECSPERDAY)
2754359Sroberto
2854359Sroberto/*
29285612Sdelphij * local calendar helpers
3054359Sroberto */
31285612Sdelphijstatic int32   ntp_to_year(u_int32);
32285612Sdelphijstatic u_int32 year_to_ntp(int32);
3354359Sroberto
34285612Sdelphij/*
35285612Sdelphij * Take a time spec given as day-of-year, hour, minute and second as
36285612Sdelphij * well as a GMT offset in hours and convert it to a NTP time stamp in
37285612Sdelphij * '*ts_ui'. The value will be in the range (rec_ui-0.5yrs) to
38285612Sdelphij * (rec_ui+0.5yrs). A hint for the current start-of-year will be
39285612Sdelphij * read from '*yearstart'.
40285612Sdelphij *
41285612Sdelphij * On return '*ts_ui' will always the best matching solution, and
42285612Sdelphij * '*yearstart' will receive the associated start-of-year.
43285612Sdelphij *
44285612Sdelphij * The function will tell if the result in 'ts_ui' is in CLOSETIME
45285612Sdelphij * (+/-4hrs) around the receive time by returning a non-zero value.
46285612Sdelphij *
47285612Sdelphij * Note: The function puts no constraints on the value ranges for the
48285612Sdelphij * time specification, but evaluates the effective seconds in
49285612Sdelphij * 32-bit arithmetic.
50285612Sdelphij */
5154359Srobertoint
5254359Srobertoclocktime(
53285612Sdelphij	int	yday	 ,	/* day-of-year */
54285612Sdelphij	int	hour	 ,	/* hour of day */
55285612Sdelphij	int	minute	 ,	/* minute of hour */
56285612Sdelphij	int	second	 ,	/* second of minute */
57285612Sdelphij	int	tzoff	 ,	/* hours west of GMT */
58285612Sdelphij	u_int32 rec_ui	 ,	/* pivot value */
59285612Sdelphij	u_long *yearstart,	/* cached start-of-year, should be fixed to u_int32 */
60285612Sdelphij	u_int32 *ts_ui	 )	/* effective time stamp */
6154359Sroberto{
62285612Sdelphij	u_int32 ystt[3];	/* year start */
63285612Sdelphij	u_int32 test[3];	/* result time stamp */
64285612Sdelphij	u_int32 diff[3];	/* abs difference to receive */
65285612Sdelphij	int32 y, tmp, idx, min;
66285612Sdelphij
6754359Sroberto	/*
68285612Sdelphij	 * Compute the offset into the year in seconds.	 Note that
6954359Sroberto	 * this could come out to be a negative number.
7054359Sroberto	 */
71285612Sdelphij	tmp = ((int32)second +
72285612Sdelphij	       SECSPERMIN * ((int32)minute +
73285612Sdelphij			     MINSPERHR * ((int32)hour + (int32)tzoff +
74285612Sdelphij					  HRSPERDAY * ((int32)yday - 1))));
7554359Sroberto	/*
76285612Sdelphij	 * Based on the cached year start, do a first attempt. Be
77285612Sdelphij	 * happy and return if this gets us better than NEARTIME to
78285612Sdelphij	 * the receive time stamp. Do this only if the cached year
79285612Sdelphij	 * start is not zero, which will not happen after 1900 for the
80285612Sdelphij	 * next few thousand years.
8154359Sroberto	 */
82285612Sdelphij	if (*yearstart) {
83285612Sdelphij		/* -- get time stamp of potential solution */
84285612Sdelphij		test[0] = (u_int32)(*yearstart) + tmp;
85285612Sdelphij		/* -- calc absolute difference to receive time */
86285612Sdelphij		diff[0] = test[0] - rec_ui;
87285612Sdelphij		if (diff[0] >= 0x80000000u)
88285612Sdelphij			diff[0] = ~diff[0] + 1;
89285612Sdelphij		/* -- can't get closer if diff < NEARTIME */
90285612Sdelphij		if (diff[0] < NEARTIME) {
91285612Sdelphij			*ts_ui = test[0];
92285612Sdelphij			return diff[0] < CLOSETIME;
93285612Sdelphij		}
9454359Sroberto	}
9554359Sroberto
9654359Sroberto	/*
97285612Sdelphij	 * Now the dance begins. Based on the receive time stamp and
98285612Sdelphij	 * the seconds offset in 'tmp', we make an educated guess
99285612Sdelphij	 * about the year to start with. This takes us on the spot
100285612Sdelphij	 * with a fuzz of +/-1 year.
101285612Sdelphij	 *
102285612Sdelphij	 * We calculate the effective timestamps for the three years
103285612Sdelphij	 * around the guess and select the entry with the minimum
104285612Sdelphij	 * absolute difference to the receive time stamp.
10554359Sroberto	 */
106285612Sdelphij	y = ntp_to_year(rec_ui - tmp);
107285612Sdelphij	for (idx = 0; idx < 3; idx++) {
108285612Sdelphij		/* -- get year start of potential solution */
109285612Sdelphij		ystt[idx] = year_to_ntp(y + idx - 1);
110285612Sdelphij		/* -- get time stamp of potential solution */
111285612Sdelphij		test[idx] = ystt[idx] + tmp;
112285612Sdelphij		/* -- calc absolute difference to receive time */
113285612Sdelphij		diff[idx] = test[idx] - rec_ui;
114285612Sdelphij		if (diff[idx] >= 0x80000000u)
115285612Sdelphij			diff[idx] = ~diff[idx] + 1;
11654359Sroberto	}
117285612Sdelphij	/* -*- assume current year fits best, then search best fit */
118285612Sdelphij	for (min = 1, idx = 0; idx < 3; idx++)
119285612Sdelphij		if (diff[idx] < diff[min])
120285612Sdelphij			min = idx;
121285612Sdelphij	/* -*- store results and update year start */
122285612Sdelphij	*ts_ui	   = test[min];
123285612Sdelphij	*yearstart = ystt[min];
12454359Sroberto
125285612Sdelphij	/* -*- tell if we could get into CLOSETIME*/
126285612Sdelphij	return diff[min] < CLOSETIME;
127285612Sdelphij}
12854359Sroberto
129285612Sdelphijstatic int32
130285612Sdelphijntp_to_year(
131285612Sdelphij	u_int32 ntp)
132285612Sdelphij{
133285612Sdelphij	vint64	     t;
134285612Sdelphij	ntpcal_split s;
13554359Sroberto
136285612Sdelphij	t = ntpcal_ntp_to_ntp(ntp, NULL);
137285612Sdelphij	s = ntpcal_daysplit(&t);
138285612Sdelphij	s = ntpcal_split_eradays(s.hi + DAY_NTP_STARTS - 1, NULL);
139285612Sdelphij	return s.hi + 1;
140285612Sdelphij}
14154359Sroberto
142285612Sdelphijstatic u_int32
143285612Sdelphijyear_to_ntp(
144285612Sdelphij	int32 year)
145285612Sdelphij{
146285612Sdelphij	u_int32 days;
147285612Sdelphij	days = ntpcal_days_in_years(year-1) - DAY_NTP_STARTS + 1;
148285612Sdelphij	return days * SECSPERDAY;
14954359Sroberto}
150