root/src/preferences/time/ntp.cpp
/*
 * Copyright 2010-2011, Ryan Leavengood. All Rights Reserved.
 * Copyright 2004-2009, pinc Software. All Rights Reserved.
 * Distributed under the terms of the MIT license.
 */

#include "ntp.h"

#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#include <OS.h>

#include <Catalog.h>
#include <NetworkAddress.h>
#include <NetworkAddressResolver.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Time"


/* This structure and its data fields are described in RFC 1305
 * "Network Time Protocol (Version 3)" in appendix A.
 */

struct fixed32 {
        int16   integer;
        uint16  fraction;

        void
        SetTo(int16 integer, uint16 fraction = 0)
        {
                this->integer = htons(integer);
                this->fraction = htons(fraction);
        }

        int16 Integer() { return htons(integer); }
        uint16 Fraction() { return htons(fraction); }
};

struct ufixed64 {
        uint32  integer;
        uint32  fraction;

        void
        SetTo(uint32 integer, uint32 fraction = 0)
        {
                this->integer = htonl(integer);
                this->fraction = htonl(fraction);
        }

        uint32 Integer() { return htonl(integer); }
        uint32 Fraction() { return htonl(fraction); }
};

struct ntp_data {
        uint8           mode : 3;
        uint8           version : 3;
        uint8           leap_indicator : 2;

        uint8           stratum;
        int8            poll;
        int8            precision;      /* in seconds of the nearest power of two */

        fixed32         root_delay;
        fixed32         root_dispersion;
        uint32          root_identifier;

        ufixed64        reference_timestamp;
        ufixed64        originate_timestamp;
        ufixed64        receive_timestamp;
        ufixed64        transmit_timestamp;

        /* optional authenticator follows (96 bits) */
};

#define NTP_PORT                123
#define NTP_VERSION_3   3

enum ntp_leap_warnings {
        LEAP_NO_WARNING = 0,
        LEAP_LAST_MINUTE_61_SECONDS,
        LEAP_LAST_MINUTE_59_SECONDS,
        LEAP_CLOCK_NOT_IN_SYNC,
};

enum ntp_modes {
        MODE_RESERVED = 0,
        MODE_SYMMETRIC_ACTIVE,
        MODE_SYMMETRIC_PASSIVE,
        MODE_CLIENT,
        MODE_SERVER,
        MODE_BROADCAST,
        MODE_NTP_CONTROL_MESSAGE,
};


const uint32 kSecondsBetween1900And1970 = 2208988800UL;


uint32
seconds_since_1900(void)
{
        return kSecondsBetween1900And1970 + real_time_clock();
}


status_t
ntp_update_time(const char* hostname, const char** errorString,
        int32* errorCode)
{
        BNetworkAddressResolver resolver(hostname, NTP_PORT);
        BNetworkAddress address;
        uint32 cookie = 0;
        bool success = false;

        if (resolver.InitCheck() != B_OK) {
                *errorString = B_TRANSLATE("Could not resolve server address");
                return B_ENTRY_NOT_FOUND;
        }

        ntp_data message;
        memset(&message, 0, sizeof(ntp_data));

        message.leap_indicator = LEAP_CLOCK_NOT_IN_SYNC;
        message.version = NTP_VERSION_3;
        message.mode = MODE_CLIENT;

        message.stratum = 1;    // primary reference
        message.precision = -5; // 2^-5 ~ 32-64 Hz precision

        message.root_delay.SetTo(1);    // 1 sec
        message.root_dispersion.SetTo(1);

        message.transmit_timestamp.SetTo(seconds_since_1900());

        int connection = socket(AF_INET, SOCK_DGRAM, 0);
        if (connection < 0) {
                *errorString = B_TRANSLATE("Could not create socket");
                *errorCode = errno;
                return B_ERROR;
        }

        while (resolver.GetNextAddress(&cookie, address) == B_OK) {
                if (sendto(connection, reinterpret_cast<char*>(&message),
                                sizeof(ntp_data), 0, &address.SockAddr(),
                                address.Length()) != -1) {
                        success = true;
                        break;
                }
        }

        if (!success) {
                *errorString = B_TRANSLATE("Sending request failed");
                close(connection);
                return B_ERROR;
        }

        fd_set waitForReceived;
        FD_ZERO(&waitForReceived);
        FD_SET(connection, &waitForReceived);

        struct timeval timeout;
        timeout.tv_sec = 3;
        timeout.tv_usec = 0;
        // we'll wait 3 seconds for the answer

        int status;
        do {
                status = select(connection + 1, &waitForReceived, NULL, NULL,
                        &timeout);
        } while (status == -1 && errno == EINTR);
        if (status <= 0) {
                *errorString = B_TRANSLATE("Waiting for answer failed");
                *errorCode = errno;
                close(connection);
                return B_ERROR;
        }

        message.transmit_timestamp.SetTo(0);

        socklen_t addressSize = address.Length();
        if (recvfrom(connection, reinterpret_cast<char*>(&message), sizeof(ntp_data), 0,
                        &address.SockAddr(), &addressSize) < (ssize_t)sizeof(ntp_data)) {
                *errorString = B_TRANSLATE("Message receiving failed");
                *errorCode = errno;
                close(connection);
                return B_ERROR;
        }

        close(connection);

        if (message.transmit_timestamp.Integer() == 0) {
                *errorString = B_TRANSLATE("Received invalid time");
                return B_BAD_VALUE;
        }

        time_t now = message.transmit_timestamp.Integer() - kSecondsBetween1900And1970;
        set_real_time_clock(now);
        return B_OK;
}