blob: 1a1aec3e3f8eebaab0d7727341adf761ea36032e [file] [log] [blame]
/*
* Copyright (c) 2017 Linaro Limited
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_sntp, CONFIG_SNTP_LOG_LEVEL);
#include <zephyr/net/sntp.h>
#include "sntp_pkt.h"
#define SNTP_LI_MAX 3
#define SNTP_VERSION_NUMBER 3
#define SNTP_MODE_CLIENT 3
#define SNTP_MODE_SERVER 4
#define SNTP_STRATUM_KOD 0 /* kiss-o'-death */
#define OFFSET_1970_JAN_1 2208988800
static void sntp_pkt_dump(struct sntp_pkt *pkt)
{
if (!pkt) {
return;
}
NET_DBG("li %x", SNTP_GET_LI(pkt->lvm));
NET_DBG("vn %x", SNTP_GET_VN(pkt->lvm));
NET_DBG("mode %x", SNTP_GET_MODE(pkt->lvm));
NET_DBG("stratum: %x", pkt->stratum);
NET_DBG("poll: %x", pkt->poll);
NET_DBG("precision: %x", pkt->precision);
NET_DBG("root_delay: %x", pkt->root_delay);
NET_DBG("root_dispersion: %x", pkt->root_dispersion);
NET_DBG("ref_id: %x", pkt->ref_id);
NET_DBG("ref_tm_s: %x", pkt->ref_tm_s);
NET_DBG("ref_tm_f: %x", pkt->ref_tm_f);
NET_DBG("orig_tm_s: %x", pkt->orig_tm_s);
NET_DBG("orig_tm_f: %x", pkt->orig_tm_f);
NET_DBG("rx_tm_s: %x", pkt->rx_tm_s);
NET_DBG("rx_tm_f: %x", pkt->rx_tm_f);
NET_DBG("tx_tm_s: %x", pkt->tx_tm_s);
NET_DBG("tx_tm_f: %x", pkt->tx_tm_f);
}
static int32_t parse_response(uint8_t *data, uint16_t len, uint32_t orig_ts,
struct sntp_time *time)
{
struct sntp_pkt *pkt = (struct sntp_pkt *)data;
uint32_t ts;
sntp_pkt_dump(pkt);
if (ntohl(pkt->orig_tm_s) != orig_ts) {
NET_DBG("Mismatch originate timestamp: %d, expect: %d",
ntohl(pkt->orig_tm_s), orig_ts);
return -EINVAL;
}
if (SNTP_GET_MODE(pkt->lvm) != SNTP_MODE_SERVER) {
/* For unicast and manycast, server should return 4.
* For broadcast (which is not supported now), server should
* return 5.
*/
NET_DBG("Unexpected mode: %d", SNTP_GET_MODE(pkt->lvm));
return -EINVAL;
}
if (pkt->stratum == SNTP_STRATUM_KOD) {
NET_DBG("kiss-o'-death stratum");
return -EBUSY;
}
if (ntohl(pkt->tx_tm_s) == 0 && ntohl(pkt->tx_tm_f) == 0) {
NET_DBG("zero transmit timestamp");
return -EINVAL;
}
time->fraction = ntohl(pkt->tx_tm_f);
ts = ntohl(pkt->tx_tm_s);
/* Check if most significant bit is set */
if (ts & 0x80000000) {
/* UTC time is reckoned from 0h 0m 0s UTC
* on 1 January 1900.
*/
if (ts >= OFFSET_1970_JAN_1) {
time->seconds = ts - OFFSET_1970_JAN_1;
} else {
return -EINVAL;
}
} else {
/* UTC time is reckoned from 6h 28m 16s UTC
* on 7 February 2036.
*/
time->seconds = ts + 0x100000000ULL - OFFSET_1970_JAN_1;
}
return 0;
}
static int sntp_recv_response(struct sntp_ctx *sntp, uint32_t timeout,
struct sntp_time *time)
{
struct sntp_pkt buf = { 0 };
int status;
int rcvd;
status = poll(sntp->sock.fds, sntp->sock.nfds, timeout);
if (status < 0) {
NET_ERR("Error in poll:%d", errno);
return -errno;
}
if (status == 0) {
return -ETIMEDOUT;
}
rcvd = recv(sntp->sock.fd, (uint8_t *)&buf, sizeof(buf), 0);
if (rcvd < 0) {
return -errno;
}
if (rcvd != sizeof(struct sntp_pkt)) {
return -EMSGSIZE;
}
status = parse_response((uint8_t *)&buf, sizeof(buf),
sntp->expected_orig_ts,
time);
return status;
}
static uint32_t get_uptime_in_sec(void)
{
uint64_t time;
time = k_uptime_get_32();
return time / MSEC_PER_SEC;
}
int sntp_init(struct sntp_ctx *ctx, struct sockaddr *addr, socklen_t addr_len)
{
int ret;
if (!ctx || !addr) {
return -EFAULT;
}
memset(ctx, 0, sizeof(struct sntp_ctx));
ctx->sock.fd = socket(addr->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (ctx->sock.fd < 0) {
NET_ERR("Failed to create UDP socket %d", errno);
return -errno;
}
ret = connect(ctx->sock.fd, addr, addr_len);
if (ret < 0) {
(void)close(ctx->sock.fd);
NET_ERR("Cannot connect to UDP remote : %d", errno);
return -errno;
}
ctx->sock.fds[ctx->sock.nfds].fd = ctx->sock.fd;
ctx->sock.fds[ctx->sock.nfds].events = POLLIN;
ctx->sock.nfds++;
return 0;
}
int sntp_query(struct sntp_ctx *ctx, uint32_t timeout, struct sntp_time *time)
{
struct sntp_pkt tx_pkt = { 0 };
int ret = 0;
if (!ctx || !time) {
return -EFAULT;
}
/* prepare request pkt */
SNTP_SET_LI(tx_pkt.lvm, 0);
SNTP_SET_VN(tx_pkt.lvm, SNTP_VERSION_NUMBER);
SNTP_SET_MODE(tx_pkt.lvm, SNTP_MODE_CLIENT);
ctx->expected_orig_ts = get_uptime_in_sec() + OFFSET_1970_JAN_1;
tx_pkt.tx_tm_s = htonl(ctx->expected_orig_ts);
ret = send(ctx->sock.fd, (uint8_t *)&tx_pkt, sizeof(tx_pkt), 0);
if (ret < 0) {
NET_ERR("Failed to send over UDP socket %d", ret);
return ret;
}
return sntp_recv_response(ctx, timeout, time);
}
void sntp_close(struct sntp_ctx *ctx)
{
if (ctx) {
(void)close(ctx->sock.fd);
}
}