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 | } |