Skip to content

File syn_sntp.c

File List > net > syn_sntp.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_SNTP) || SYN_USE_SNTP

#include "../port/syn_port_system.h"
#include "../util/syn_assert.h"
#include "../util/syn_pack.h"
#include "syn_sntp.h"

#include <stdio.h>
#include <string.h>

/* ── Internal Helpers ───────────────────────────────────────────────────── */

static SYN_Status sntp_send_request(const SYN_SNTP *sntp, SYN_Socket sock);
static SYN_Status sntp_parse_packet(SYN_SNTP *sntp, const uint8_t *pkt, size_t len);

/* ── API ────────────────────────────────────────────────────────────────── */

void syn_sntp_init(SYN_SNTP *sntp, const SYN_SockAddr *server, uint32_t sync_interval_s)
{
    SYN_ASSERT(sntp != NULL);
    SYN_ASSERT(server != NULL);

    memset(sntp, 0, sizeof(*sntp));
    sntp->server = *server;
    sntp->sync_interval_s = sync_interval_s;
    sntp->synced = false;

    /* Initialize backoff: 1s base, 60s max, factor 2, max_retries */
    syn_backoff_init(&sntp->backoff, 1000, 60000, 2, SYN_SNTP_MAX_RETRIES);
    /* Initialize EMA drift filter: alpha=64 (~0.25 smoothing factor for network jitter rejection)
     */
    syn_filter_ema_init(&sntp->drift_filter, 64);
    sntp->udp_sock = SYN_SOCKET_INVALID;
}

static SYN_Status sntp_parse_packet(SYN_SNTP *sntp, const uint8_t *pkt, size_t len)
{
    if (len < SYN_SNTP_PACKET_SIZE)
        return SYN_BUSY;

    /* Validate mode (4=server, 5=broadcast) and stratum != 0 */
    uint8_t mode = pkt[0] & 0x07;
    if (mode != 4 && mode != 5)
        return SYN_ERROR;
    if (pkt[1] == 0)
        return SYN_ERROR; /* kiss-of-death */

    /* Extract transmit timestamp (bytes 40–47, NTP epoch big-endian) */
    uint32_t ntp_s = syn_peek_u32(pkt, 40);
    uint32_t ntp_frac = syn_peek_u32(pkt, 44);

    if (ntp_s < SYN_SNTP_EPOCH_OFFSET)
        return SYN_ERROR;

    uint32_t new_epoch = ntp_s - SYN_SNTP_EPOCH_OFFSET;
    uint32_t new_tick_ms = syn_port_get_tick_ms();

    /* Calculate clock drift in PPM if we have a previous sync baseline */
    if (sntp->synced && sntp->prev_sync_epoch != 0) {
        uint32_t ntp_elapsed_s = new_epoch - sntp->prev_sync_epoch;
        uint32_t tick_elapsed_ms = new_tick_ms - sntp->prev_sync_tick_ms;

        if (ntp_elapsed_s >= 5) {
            int64_t expected_ms = (int64_t)ntp_elapsed_s * 1000LL;
            int64_t diff_ms = (int64_t)tick_elapsed_ms - expected_ms;
            int16_t raw_ppm = (int16_t)((diff_ms * 1000000LL) / expected_ms);

            /* Apply SyntropicOS Exponential Moving Average Filter (SYN_FilterEMA) */
            sntp->drift_ppm = (int32_t)syn_filter_ema_update(&sntp->drift_filter, raw_ppm);
        }
    }

    sntp->prev_sync_epoch = new_epoch;
    sntp->prev_sync_tick_ms = new_tick_ms;
    sntp->epoch_s = new_epoch;
    sntp->epoch_frac = ntp_frac;
    sntp->sync_tick_ms = new_tick_ms;
    sntp->synced = true;

    return SYN_OK;
}

SYN_Status syn_sntp_query(SYN_SNTP *sntp)
{
    SYN_ASSERT(sntp != NULL);

    uint8_t pkt[SYN_SNTP_PACKET_SIZE];
    SYN_SockAddr from;

    SYN_Socket sock = syn_port_udp_open(0);
    if (sock == SYN_SOCKET_INVALID)
        return SYN_ERROR;

    if (sntp_send_request(sntp, sock) != SYN_OK) {
        syn_port_sock_close(sock);
        return SYN_ERROR;
    }

    int n = syn_port_udp_recvfrom(sock, pkt, sizeof(pkt), &from, SYN_SNTP_TIMEOUT_MS);
    syn_port_sock_close(sock);

    if (n < (int)SYN_SNTP_PACKET_SIZE)
        return (n <= 0) ? SYN_TIMEOUT : SYN_ERROR;

    return sntp_parse_packet(sntp, pkt, (size_t)n);
}

uint32_t syn_sntp_get_epoch_s(const SYN_SNTP *sntp)
{
    if (!sntp->synced)
        return 0;

    uint32_t elapsed_ms = syn_port_get_tick_ms() - sntp->sync_tick_ms;
    return sntp->epoch_s + (elapsed_ms / 1000u);
}

uint32_t syn_sntp_get_epoch_ns(const SYN_SNTP *sntp)
{
    if (!sntp->synced)
        return 0;

    uint32_t elapsed_ms = syn_port_get_tick_ms() - sntp->sync_tick_ms;
    uint32_t sub_s_ms = elapsed_ms % 1000u;

    return sub_s_ms * 1000000u; /* ms → ns */
}

int32_t syn_sntp_get_drift_ppm(const SYN_SNTP *sntp)
{
    if (!sntp)
        return 0;
    return sntp->drift_ppm;
}

/* ── Non-blocking helpers (for protothread task) ───────────────────────── */

static SYN_Status sntp_send_request(const SYN_SNTP *sntp, SYN_Socket sock)
{
    uint8_t pkt[SYN_SNTP_PACKET_SIZE];
    memset(pkt, 0, sizeof(pkt));
    pkt[0] = 0x23; /* LI=0, VN=4, Mode=3 (client) */

    int n = syn_port_udp_sendto(sock, pkt, sizeof(pkt), &sntp->server);
    return (n == SYN_SNTP_PACKET_SIZE) ? SYN_OK : SYN_ERROR;
}

static SYN_Status sntp_try_recv(SYN_SNTP *sntp, SYN_Socket sock)
{
    uint8_t pkt[SYN_SNTP_PACKET_SIZE];
    SYN_SockAddr from;

    int n = syn_port_udp_recvfrom(sock, pkt, sizeof(pkt), &from, 0);
    if (n < (int)SYN_SNTP_PACKET_SIZE) {
        return (n <= 0) ? SYN_BUSY : SYN_ERROR;
    }

    return sntp_parse_packet(sntp, pkt, (size_t)n);
}

/* ── Protothread task ───────────────────────────────────────────────────── */

SYN_PT_Status syn_sntp_task(SYN_PT *pt, SYN_Task *task)
{
    SYN_SNTP *sntp = (SYN_SNTP *)task->user_data;
    SYN_ASSERT(sntp != NULL);

    PT_BEGIN(pt);

    for (;;) {
        syn_backoff_reset(&sntp->backoff);

        while (sntp->backoff.attempts < SYN_SNTP_MAX_RETRIES) {
            /* Phase 1: Open socket */
            sntp->udp_sock = syn_port_udp_open(0);
            if (sntp->udp_sock == SYN_SOCKET_INVALID) {
                PT_TASK_DELAY_MS(pt, task, syn_backoff_next_ms(&sntp->backoff));
                continue;
            }

            /* Phase 2: Send request */
            if (sntp_send_request(sntp, sntp->udp_sock) != SYN_OK) {
                syn_port_sock_close(sntp->udp_sock);
                sntp->udp_sock = SYN_SOCKET_INVALID;
                PT_TASK_DELAY_MS(pt, task, syn_backoff_next_ms(&sntp->backoff));
                continue;
            }

            /* Phase 3: Non-blocking poll with deadline */
            sntp->recv_deadline = syn_port_get_tick_ms() + SYN_SNTP_TIMEOUT_MS;
            PT_WAIT_UNTIL(pt, sntp_try_recv(sntp, sntp->udp_sock) != SYN_BUSY ||
                                  (int32_t)(syn_port_get_tick_ms() - sntp->recv_deadline) >= 0);

            syn_port_sock_close(sntp->udp_sock);
            sntp->udp_sock = SYN_SOCKET_INVALID;

            if (sntp->synced) {
                break; /* Success! */
            }

            /* Failure or timeout — backoff and try again */
            PT_TASK_DELAY_MS(pt, task, syn_backoff_next_ms(&sntp->backoff));
        }

        /* Wait for next sync interval */
        PT_TASK_DELAY_MS(pt, task, sntp->sync_interval_s * 1000);
    }

    PT_END(pt);
}

#endif /* SYN_USE_SNTP */