/* * Copyright (c) 2023 Valtteri Koskivuori * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, 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. */ #include #include "tcp.h" #include "dnr.h" #include "MacNTP.h" /* TODO: rename tcp.c/h to udp.c/h and rename _TCPInit to UDPInit */ void on_udp_receive(struct UDPiopb *iopb); // We already have this from dnr.h above, but Think C ignores it. OSErr ResolveName(char *name, unsigned long *ipAddress); /* The NTP protocol expresses time as seconds from 01-01-1900. The Macintosh expresses them as seconds from 01-01-1904. This first step converts the two. Seconds between 01-01-1900 and 01-01-1904 (none were leap years): 4 * 365 * 24 * 60 * 60 = 126144000 */ #define NTP_MAC_OFFSET 126144000 enum MacNTPError MacNTPSetSystemTime(struct ntp_packet *ntp, minutes_t utc_offset) { //FIXME: We seem to be behind about 10 seconds after update. OSErr err; long mac_seconds; // The Mac clock isn't very precise, and we don't currently even // measure the precise time we receive the response, so we forgo // the clock filter computations, and trust that the network latency // isn't too bad. //FIXME: Record receive time and compensate for it if delay exceeds 1s long ntp_seconds = ntp->transmit_timestamp.upper; ntp_seconds += utc_offset * 60; mac_seconds = ntp_seconds - NTP_MAC_OFFSET; err = SetDateTime(mac_seconds); if (err) { if (err == -86) { // clkWrErr return ClockWriteFailed; } else if (err == -85) { // clkRdErr return ClockReadFailed; } } return Success; } enum RequestState { AwaitingResponse = 0, GotResponse, ResponseFailed }; struct ntp_request { ip_addr host_ip; char *url; UDPiopb udp_iopb; StreamPtr udp_stream; unsigned char *udp_buf; unsigned short udp_buf_size; wdsEntry udp_wds[2]; enum RequestState state; struct ntp_packet payload; }; void on_udp_receive(struct UDPiopb *iopb) { struct ntp_request *req; req = (struct ntp_request *)iopb->csParam.receive.userDataPtr; if (iopb->csParam.receive.rcvBuffLen != sizeof(struct ntp_packet)) { req->state = ResponseFailed; } else { req->payload = *(struct ntp_packet *)iopb->csParam.receive.rcvBuff; } req->state = GotResponse; } enum MacNTPError MacNTPFetchTime(char *ntp_url, struct ntp_packet *packet, OSErr *supplemental, minutes_t utc_offset) { struct ntp_request req; OSErr err; UDPIOCompletionProc completion; long i; unsigned long local_transmit_time; long retries = 40000; // Arbitrary enum MacNTPError current_error = Success; // I tried other values, but they all seem to make UDPCreate fail with // the undocumented error code -23006 unsigned char udp_buf[3000]; completion = NewUDPIOCompletionProc(on_udp_receive); if (packet == NULL) return PacketParamNull; if (ntp_url == NULL) return InvalidURL; if (sizeof(struct ntp_packet) != 48lu) return BadNtpStructSize; err = _TCPInit(); if (err != 0) { if (supplemental) *supplemental = err; // error goto tears down UDP, which we don't want here, so return return MacTCPInitFailed; } mntp_memset(&req, 0, sizeof(req)); req.url = ntp_url; mntp_memset(&udp_buf, 0, sizeof(udp_buf)); req.udp_buf_size = sizeof(udp_buf); req.udp_buf = (unsigned char *)&udp_buf; err = _UDPCreate(&req.udp_iopb, &req.udp_stream, (Ptr)req.udp_buf, req.udp_buf_size, nil, nil, completion, false); if (err) { if (supplemental) *supplemental = err; current_error = UDPCreateFailed; goto error; } err = ResolveName(req.url, &req.host_ip); if (err) { if (supplemental) *supplemental = err; current_error = DNSResolveFailed; goto error; } // Set up NTP payload mntp_memset(&req.payload, 0, sizeof(req.payload)); // Set the mode to tell the server we're a client req.payload.li_vn_mode = (4 << 3) | 3; GetDateTime(&local_transmit_time); local_transmit_time += NTP_MAC_OFFSET; local_transmit_time -= utc_offset * 60; // Copy and verify this matches the response transmit ts req.payload.transmit_timestamp.upper = local_transmit_time; // And prepare for sending mntp_memset(&req.udp_wds, 0, sizeof(req.udp_wds)); req.udp_wds[0].ptr = (void *)&req.payload; req.udp_wds[0].length = sizeof(req.payload); err = _UDPSend(&req.udp_iopb, req.udp_stream, req.udp_wds, req.host_ip, 123, nil, nil, false); if (err) { if (supplemental) *supplemental = err; current_error = UDPSendFailed; goto error; } req.state = AwaitingResponse; //FIXME: Only works asynchronously err = _UDPRcv(&req.udp_iopb, req.udp_stream, nil, nil, req.host_ip, 123, (Ptr)&req, completion, true); if (err) { if (supplemental) *supplemental = err; current_error = UDPRcvFailed; goto error; } // Wait for event handler to fire //FIXME: This needs a timeout, or we might be able to set it for udp while (req.state == AwaitingResponse) { if (retries <= 0) { current_error = UDPRcvTimedOut; goto error; } retries--; } if (req.state == ResponseFailed) { current_error = InvalidNTPResponse; goto error; } // The server copies transmit_timestamp from the packet we sent to // origin_timestamp in the response. We verify it here. if (req.payload.origin_timestamp.upper != local_transmit_time) { current_error = OriginTimestampMismatch; goto error; } *packet = req.payload; error: _UDPRelease(&req.udp_iopb, req.udp_stream, nil, nil, false); return current_error; }