AmendHub

Download

vkoskiv

/

MacNTP

/

MacNTP.c

 

(View History)

vkoskiv   Small NTP corrections Latest amendment: 13 on 2023-09-07

1 /*
2 * Copyright (c) 2023 Valtteri Koskivuori <vkoskiv@gmail.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <OSUtils.h>
18 #include "tcp.h"
19 #include "dnr.h"
20 #include "MacNTP.h"
21
22 /*
23 TODO:
24 rename tcp.c/h to udp.c/h and rename _TCPInit to UDPInit
25 */
26
27 void on_udp_receive(struct UDPiopb *iopb);
28
29 // We already have this from dnr.h above, but Think C ignores it.
30 OSErr ResolveName(char *name, unsigned long *ipAddress);
31
32 /*
33 The NTP protocol expresses time as seconds from 01-01-1900.
34 The Macintosh expresses them as seconds from 01-01-1904.
35 This first step converts the two.
36 Seconds between 01-01-1900 and 01-01-1904 (none were leap years):
37 4 * 365 * 24 * 60 * 60 = 126144000
38 */
39 #define NTP_MAC_OFFSET 126144000
40
41 enum MacNTPError MacNTPSetSystemTime(struct ntp_packet *ntp, minutes_t utc_offset) {
42 //FIXME: We seem to be behind about 10 seconds after update.
43 OSErr err;
44 long mac_seconds;
45
46 // The Mac clock isn't very precise, and we don't currently even
47 // measure the precise time we receive the response, so we forgo
48 // the clock filter computations, and trust that the network latency
49 // isn't too bad.
50 //FIXME: Record receive time and compensate for it if delay exceeds 1s
51 long ntp_seconds = ntp->transmit_timestamp.upper;
52 ntp_seconds += utc_offset * 60;
53 mac_seconds = ntp_seconds - NTP_MAC_OFFSET;
54 err = SetDateTime(mac_seconds);
55 if (err) {
56 if (err == -86) { // clkWrErr
57 return ClockWriteFailed;
58 } else if (err == -85) { // clkRdErr
59 return ClockReadFailed;
60 }
61 }
62 return Success;
63 }
64
65 enum RequestState {
66 AwaitingResponse = 0,
67 GotResponse,
68 ResponseFailed
69 };
70
71 struct ntp_request {
72 ip_addr host_ip;
73 char *url;
74 UDPiopb udp_iopb;
75 StreamPtr udp_stream;
76 unsigned char *udp_buf;
77 unsigned short udp_buf_size;
78 wdsEntry udp_wds[2];
79 enum RequestState state;
80
81 struct ntp_packet payload;
82 };
83
84 void on_udp_receive(struct UDPiopb *iopb) {
85 struct ntp_request *req;
86 req = (struct ntp_request *)iopb->csParam.receive.userDataPtr;
87 if (iopb->csParam.receive.rcvBuffLen != sizeof(struct ntp_packet)) {
88 req->state = ResponseFailed;
89 } else {
90 req->payload = *(struct ntp_packet *)iopb->csParam.receive.rcvBuff;
91 }
92
93 req->state = GotResponse;
94 }
95
96 enum MacNTPError MacNTPFetchTime(char *ntp_url, struct ntp_packet *packet, OSErr *supplemental, minutes_t utc_offset) {
97 struct ntp_request req;
98 OSErr err;
99 UDPIOCompletionProc completion;
100 long i;
101 unsigned long local_transmit_time;
102 long retries = 40000; // Arbitrary
103 enum MacNTPError current_error = Success;
104 // I tried other values, but they all seem to make UDPCreate fail with
105 // the undocumented error code -23006
106 unsigned char udp_buf[3000];
107
108 completion = NewUDPIOCompletionProc(on_udp_receive);
109
110 if (packet == NULL) return PacketParamNull;
111 if (ntp_url == NULL) return InvalidURL;
112 if (sizeof(struct ntp_packet) != 48lu) return BadNtpStructSize;
113 err = _TCPInit();
114 if (err != 0) {
115 if (supplemental) *supplemental = err;
116 // error goto tears down UDP, which we don't want here, so return
117 return MacTCPInitFailed;
118 }
119
120 mntp_memset(&req, 0, sizeof(req));
121 req.url = ntp_url;
122
123 mntp_memset(&udp_buf, 0, sizeof(udp_buf));
124 req.udp_buf_size = sizeof(udp_buf);
125 req.udp_buf = (unsigned char *)&udp_buf;
126 err = _UDPCreate(&req.udp_iopb, &req.udp_stream, (Ptr)req.udp_buf,
127 req.udp_buf_size, nil, nil, completion, false);
128 if (err) {
129 if (supplemental) *supplemental = err;
130 current_error = UDPCreateFailed;
131 goto error;
132 }
133 err = ResolveName(req.url, &req.host_ip);
134 if (err) {
135 if (supplemental) *supplemental = err;
136 current_error = DNSResolveFailed;
137 goto error;
138 }
139
140 // Set up NTP payload
141 mntp_memset(&req.payload, 0, sizeof(req.payload));
142 // Set the mode to tell the server we're a client
143 req.payload.li_vn_mode = (4 << 3) | 3;
144
145 GetDateTime(&local_transmit_time);
146 local_transmit_time += NTP_MAC_OFFSET;
147 local_transmit_time -= utc_offset * 60;
148 // Copy and verify this matches the response transmit ts
149 req.payload.transmit_timestamp.upper = local_transmit_time;
150
151 // And prepare for sending
152 mntp_memset(&req.udp_wds, 0, sizeof(req.udp_wds));
153 req.udp_wds[0].ptr = (void *)&req.payload;
154 req.udp_wds[0].length = sizeof(req.payload);
155
156 err = _UDPSend(&req.udp_iopb, req.udp_stream, req.udp_wds,
157 req.host_ip, 123, nil, nil, false);
158
159 if (err) {
160 if (supplemental) *supplemental = err;
161 current_error = UDPSendFailed;
162 goto error;
163 }
164
165 req.state = AwaitingResponse;
166
167 //FIXME: Only works asynchronously
168 err = _UDPRcv(&req.udp_iopb,
169 req.udp_stream,
170 nil,
171 nil,
172 req.host_ip,
173 123,
174 (Ptr)&req,
175 completion,
176 true);
177
178 if (err) {
179 if (supplemental) *supplemental = err;
180 current_error = UDPRcvFailed;
181 goto error;
182 }
183
184 // Wait for event handler to fire
185 //FIXME: This needs a timeout, or we might be able to set it for udp
186 while (req.state == AwaitingResponse) {
187 if (retries <= 0) {
188 current_error = UDPRcvTimedOut;
189 goto error;
190 }
191 retries--;
192 }
193 if (req.state == ResponseFailed) {
194 current_error = InvalidNTPResponse;
195 goto error;
196 }
197
198 // The server copies transmit_timestamp from the packet we sent to
199 // origin_timestamp in the response. We verify it here.
200 if (req.payload.origin_timestamp.upper != local_transmit_time) {
201 current_error = OriginTimestampMismatch;
202 goto error;
203 }
204
205 *packet = req.payload;
206
207 error:
208 _UDPRelease(&req.udp_iopb, req.udp_stream, nil, nil, false);
209 return current_error;
210 }