- added missing SNTP code (LIB61850-360)
parent
99bdd4e61c
commit
0f544e9e25
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Michael Zillgith
|
||||
*
|
||||
* This file is part of libIEC61850.
|
||||
*
|
||||
* libIEC61850 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* libIEC61850 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#ifndef LIBIEC61850_SRC_COMMON_INC_SNTP_CLIENT_H_
|
||||
#define LIBIEC61850_SRC_COMMON_INC_SNTP_CLIENT_H_
|
||||
|
||||
#include "libiec61850_common_api.h"
|
||||
#include "hal_socket.h"
|
||||
|
||||
typedef struct sSNTPClient* SNTPClient;
|
||||
|
||||
typedef void (*SNTPClient_UserCallback)(void* parameter, bool isSynced);
|
||||
|
||||
LIB61850_API SNTPClient
|
||||
SNTPClient_create();
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_setLocalAddress(SNTPClient self, const char* localAddress);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_setLocalPort(SNTPClient self, int udpPort);
|
||||
|
||||
LIB61850_API HandleSet
|
||||
SNTPClient_getHandleSet(SNTPClient self);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_addServer(SNTPClient self, const char* serverAddr, int serverPort);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_setPollInterval(SNTPClient self, uint32_t intervalInSeconds);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_setUserCallback(SNTPClient self, SNTPClient_UserCallback callback, void* parameter);
|
||||
|
||||
LIB61850_API bool
|
||||
SNTPClient_isSynchronized(SNTPClient self);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_start(SNTPClient self);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_stop(SNTPClient self);
|
||||
|
||||
LIB61850_API void
|
||||
SNTPClient_destroy(SNTPClient self);
|
||||
|
||||
#endif /* LIBIEC61850_SRC_COMMON_INC_SNTP_CLIENT_H_ */
|
@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Michael Zillgith
|
||||
*
|
||||
* This file is part of libIEC61850.
|
||||
*
|
||||
* libIEC61850 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* libIEC61850 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with libIEC61850. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include "hal_socket.h"
|
||||
#include "hal_time.h"
|
||||
#include "hal_thread.h"
|
||||
#include "libiec61850_platform_includes.h"
|
||||
#include "sntp_client.h"
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define SNTP_DEFAULT_PORT 123
|
||||
|
||||
#ifndef SNTP_DEBUG
|
||||
#define SNTP_DEBUG 1
|
||||
#endif
|
||||
|
||||
typedef struct sSNTPClient* SNTPClient;
|
||||
|
||||
/* new time types */
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t coarse;
|
||||
uint32_t fine;
|
||||
} NtpTime;
|
||||
|
||||
|
||||
struct sSNTPClient
|
||||
{
|
||||
UdpSocket socket;
|
||||
HandleSet handleSet;
|
||||
nsSinceEpoch lastReceivedMessage; /* time of last received synchronization message */
|
||||
uint64_t nextMessageExpected;
|
||||
bool outStandingRequest;
|
||||
|
||||
uint64_t pollInterval; /* poll interval in ns */
|
||||
|
||||
nsSinceEpoch lastRequestTimestamp; /* timestamp used in the last request message */
|
||||
|
||||
SNTPClient_UserCallback userCallback;
|
||||
void* userCallbackParameter;
|
||||
|
||||
Thread thread;
|
||||
bool running;
|
||||
|
||||
bool clockSynced;
|
||||
|
||||
char* serverAddr;
|
||||
int serverPort;
|
||||
};
|
||||
|
||||
msSinceEpoch
|
||||
nsTimeToMsTime(nsSinceEpoch nsTime)
|
||||
{
|
||||
return nsTime / 1000000UL;
|
||||
}
|
||||
|
||||
#define UNIX_EPOCH_OFFSET 2208988800 /* offset between NTP seconds and UNIX time */
|
||||
|
||||
static nsSinceEpoch
|
||||
ntpTimeToNsTime(uint32_t coarse, uint32_t fine)
|
||||
{
|
||||
if ((coarse == 0) && (fine == 0))
|
||||
return 0;
|
||||
|
||||
uint64_t nsTime = (coarse - UNIX_EPOCH_OFFSET) * 1000000000UL;
|
||||
|
||||
uint64_t nsPart = fine * 1000000000UL;
|
||||
nsPart = nsPart >> 32;
|
||||
nsTime += nsPart;
|
||||
|
||||
return nsTime;
|
||||
}
|
||||
|
||||
static void
|
||||
nsTimeToNtpTime(nsSinceEpoch nsTime, uint32_t* coarse, uint32_t* fine)
|
||||
{
|
||||
uint32_t secondsPart = (nsTime / 1000000000UL) + UNIX_EPOCH_OFFSET;
|
||||
|
||||
*coarse = secondsPart;
|
||||
|
||||
uint64_t nsPart = nsTime % 1000000000UL;
|
||||
nsPart = nsPart << 32;
|
||||
nsPart = nsPart / 1000000000UL;
|
||||
|
||||
*fine = (uint32_t) nsPart;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
nsTimeToSeconds(nsSinceEpoch nsTime)
|
||||
{
|
||||
uint32_t secondsSinceEpoch = nsTime / 1000000000UL;
|
||||
|
||||
return secondsSinceEpoch;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
getUsPartFromNsTime(nsSinceEpoch nsTime)
|
||||
{
|
||||
uint64_t nsPart = nsTime % 1000000000UL;
|
||||
|
||||
uint32_t msPart = nsPart / 1000UL;
|
||||
|
||||
return msPart;
|
||||
}
|
||||
|
||||
static int
|
||||
encodeUint32(uint8_t* buffer, int bufPos, uint32_t value)
|
||||
{
|
||||
buffer[bufPos++] = (uint8_t) (value / 0x1000000);
|
||||
buffer[bufPos++] = (uint8_t) ((value / 0x10000) % 0x100);
|
||||
buffer[bufPos++] = (uint8_t) ((value / 0x100) % 0x100);
|
||||
buffer[bufPos++] = (uint8_t) (value % 0x100);
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
||||
static int
|
||||
decodeUint32(uint8_t* buffer, int bufPos, uint32_t* value)
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
val = buffer[bufPos++] * 0x1000000;
|
||||
val += buffer[bufPos++] * 0x10000;
|
||||
val += buffer[bufPos++] * 0x100;
|
||||
val += buffer[bufPos++];
|
||||
|
||||
*value = val;
|
||||
|
||||
return bufPos;
|
||||
}
|
||||
|
||||
static void
|
||||
parseResponseMessage(SNTPClient self, uint8_t* buffer, int bufSize)
|
||||
{
|
||||
self->lastReceivedMessage = Hal_getTimeInNs();
|
||||
|
||||
int bufPos = 0;
|
||||
|
||||
uint8_t header = buffer[bufPos++];
|
||||
int8_t stratum = (int8_t) buffer[bufPos++];
|
||||
int8_t poll = (int8_t) buffer[bufPos++];
|
||||
int8_t precision = (int8_t) buffer[bufPos++];
|
||||
|
||||
int li = (header & 0xc0)>> 6;
|
||||
|
||||
/* check for "clock-not-synchronized" */
|
||||
if (li == 3) {
|
||||
/* ignore time message */
|
||||
if (SNTP_DEBUG)
|
||||
printf("WARNING: received clock-not-synchronized from server\n");
|
||||
//TODO call user callback?
|
||||
return;
|
||||
}
|
||||
|
||||
int version = (header & 0x38) >> 3;
|
||||
|
||||
int mode = (header & 0x7);
|
||||
|
||||
/* TODO: expect mode 4 - server */
|
||||
|
||||
int timeoutInSeconds = 1 << poll;
|
||||
|
||||
if (SNTP_DEBUG)
|
||||
printf("SNTP: response - version: %i mode: %i (4 = server, 3 = client) timeout(in s): %i\n", version, mode, timeoutInSeconds);
|
||||
|
||||
/* root delay */
|
||||
bufPos += 4;
|
||||
|
||||
/* root dispersion */
|
||||
bufPos += 4;
|
||||
|
||||
/* reference */
|
||||
bufPos += 4;
|
||||
|
||||
/* reference timestamp */
|
||||
uint32_t coarse;
|
||||
uint32_t fine;
|
||||
|
||||
bufPos = decodeUint32(buffer, bufPos, &coarse);
|
||||
bufPos = decodeUint32(buffer, bufPos, &fine);
|
||||
|
||||
uint64_t refTime = ntpTimeToNsTime(coarse, fine);
|
||||
|
||||
/* originate timestamp */
|
||||
|
||||
bufPos = decodeUint32(buffer, bufPos, &coarse);
|
||||
bufPos = decodeUint32(buffer, bufPos, &fine);
|
||||
|
||||
uint64_t origTime = ntpTimeToNsTime(coarse, fine);
|
||||
|
||||
/* receive timestamp */
|
||||
|
||||
bufPos = decodeUint32(buffer, bufPos, &coarse);
|
||||
bufPos = decodeUint32(buffer, bufPos, &fine);
|
||||
|
||||
uint64_t recvTime = ntpTimeToNsTime(coarse, fine);
|
||||
|
||||
/* transmit timestamp */
|
||||
|
||||
bufPos = decodeUint32(buffer, bufPos, &coarse);
|
||||
bufPos = decodeUint32(buffer, bufPos, &fine);
|
||||
|
||||
uint64_t trnsTime = ntpTimeToNsTime(coarse, fine);
|
||||
|
||||
/* set system time */
|
||||
if (Hal_setTimeInNs(trnsTime) == false) {
|
||||
if (SNTP_DEBUG)
|
||||
printf("SNTP: failed to set system clock!\n");
|
||||
}
|
||||
|
||||
self->clockSynced = true;
|
||||
self->outStandingRequest = false;
|
||||
|
||||
if (self->userCallback) {
|
||||
self->userCallback(self->userCallbackParameter, true);
|
||||
}
|
||||
|
||||
if (SNTP_DEBUG) {
|
||||
printf("SNTP: reference time: %u.%.6u\n", nsTimeToSeconds(refTime), getUsPartFromNsTime(refTime));
|
||||
printf("SNTP: original time: %u.%.9u\n", nsTimeToSeconds(origTime), getUsPartFromNsTime(origTime));
|
||||
printf("SNTP: receive time: %u.%.6u\n", nsTimeToSeconds(recvTime), getUsPartFromNsTime(recvTime));
|
||||
printf("SNTP: transmit time: %u.%.6u\n", nsTimeToSeconds(trnsTime), getUsPartFromNsTime(trnsTime));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sendRequestMessage(SNTPClient self, const char* serverAddr, int serverPort)
|
||||
{
|
||||
uint8_t buffer[100];
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
uint8_t li = 0;
|
||||
int8_t version = 3;
|
||||
int8_t mode = 3; /* mode: client */
|
||||
int8_t stratum = 0; /* stratum: not specified */
|
||||
int8_t poll = 4;
|
||||
int8_t precision = -6;
|
||||
|
||||
int bufPos = 0;
|
||||
|
||||
buffer[bufPos++] = (li << 6) | (version << 3) | mode;
|
||||
buffer[bufPos++] = stratum;
|
||||
buffer[bufPos++] = poll;
|
||||
buffer[bufPos++] = precision;
|
||||
|
||||
/* root delay */
|
||||
bufPos = encodeUint32(buffer, bufPos, 1 << 16);
|
||||
|
||||
/* root dispersion */
|
||||
bufPos = encodeUint32(buffer, bufPos, 1 << 16);
|
||||
|
||||
/* reference */
|
||||
bufPos += 4;
|
||||
|
||||
/* skip reference timestamp */
|
||||
bufPos += 8;
|
||||
|
||||
/* skip originate timestamp */
|
||||
bufPos += 8;
|
||||
|
||||
/* skip receive timestamp */
|
||||
bufPos += 8;
|
||||
|
||||
uint32_t time_coarse;
|
||||
uint32_t time_fine;
|
||||
|
||||
nsSinceEpoch nsTime = Hal_getTimeInNs();
|
||||
|
||||
nsTimeToNtpTime(nsTime, &time_coarse, &time_fine);
|
||||
|
||||
/* transmit timestamp */
|
||||
bufPos = encodeUint32(buffer, bufPos, time_coarse);
|
||||
bufPos = encodeUint32(buffer, bufPos, time_fine);
|
||||
|
||||
UdpSocket_sendTo(self->socket, serverAddr ,serverPort, buffer, bufPos);
|
||||
|
||||
if (SNTP_DEBUG)
|
||||
printf("SNTP: sent request to %s:%i\n", serverAddr, serverPort);
|
||||
|
||||
self->lastRequestTimestamp = nsTime;
|
||||
self->outStandingRequest = true;
|
||||
|
||||
//if (self->lastReceivedMessage == 0)
|
||||
// self->lastReceivedMessage = nsTime;
|
||||
}
|
||||
|
||||
SNTPClient
|
||||
SNTPClient_create()
|
||||
{
|
||||
SNTPClient self = (SNTPClient) GLOBAL_CALLOC(1, sizeof(struct sSNTPClient));
|
||||
|
||||
if (self) {
|
||||
self->socket = UdpSocket_create();
|
||||
|
||||
if (self->socket == NULL) {
|
||||
GLOBAL_FREEMEM(self);
|
||||
self = NULL;
|
||||
}
|
||||
else {
|
||||
self->handleSet = Handleset_new();
|
||||
Handleset_addSocket(self->handleSet, (Socket) self->socket);
|
||||
self->pollInterval = 30000000000UL; /* 30 s */
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
HandleSet
|
||||
SNTPClient_getHandleSet(SNTPClient self)
|
||||
{
|
||||
return self->handleSet;
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_addServer(SNTPClient self, const char* serverAddr, int serverPort)
|
||||
{
|
||||
if (self) {
|
||||
self->serverAddr = StringUtils_copyString(serverAddr);
|
||||
self->serverPort = serverPort;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SNTPClient_isSynchronized(SNTPClient self)
|
||||
{
|
||||
return self->clockSynced;
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_setUserCallback(SNTPClient self, SNTPClient_UserCallback callback, void* parameter)
|
||||
{
|
||||
if (self) {
|
||||
self->userCallback = callback;
|
||||
self->userCallbackParameter = parameter;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_setPollInterval(SNTPClient self, uint32_t intervalInSeconds)
|
||||
{
|
||||
if (self)
|
||||
self->pollInterval = intervalInSeconds * 1000000000UL;
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_tick(SNTPClient self)
|
||||
{
|
||||
nsSinceEpoch now = Hal_getTimeInNs();
|
||||
|
||||
if (self->lastReceivedMessage > now)
|
||||
self->lastReceivedMessage = now;
|
||||
|
||||
if (self->lastRequestTimestamp > now)
|
||||
self->lastRequestTimestamp = now;
|
||||
|
||||
if (self->lastRequestTimestamp > 0) {
|
||||
/* check for timeout */
|
||||
if ((now - self->lastReceivedMessage) > 300000000000UL) {
|
||||
|
||||
if (self->clockSynced) {
|
||||
self->clockSynced = false;
|
||||
printf("SNTP: request timeout\n");
|
||||
//TODO when to call user handler?
|
||||
if (self->userCallback) {
|
||||
self->userCallback(self->userCallbackParameter, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self->serverAddr) {
|
||||
if ((now - self->lastRequestTimestamp) > self->pollInterval) {
|
||||
sendRequestMessage(self, self->serverAddr, self->serverPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_handleIncomingMessage(SNTPClient self)
|
||||
{
|
||||
char ipAddress[200];
|
||||
|
||||
uint8_t buffer[200];
|
||||
int rcvdBytes = UdpSocket_receiveFrom(self->socket, ipAddress, 200, buffer, sizeof(buffer));
|
||||
|
||||
if (rcvdBytes > 0) {
|
||||
|
||||
if (SNTP_DEBUG)
|
||||
printf("SNTP: received response from %s\n", ipAddress);
|
||||
|
||||
parseResponseMessage(self, buffer, rcvdBytes);
|
||||
}
|
||||
else if (rcvdBytes == -1) {
|
||||
printf("UDP socket error\n");
|
||||
}
|
||||
else {
|
||||
printf("No data!\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
handleThread(void* parameter)
|
||||
{
|
||||
SNTPClient self = (SNTPClient) parameter;
|
||||
|
||||
self->running = true;
|
||||
|
||||
while (self->running) {
|
||||
|
||||
SNTPClient_tick(self);
|
||||
|
||||
if (Handleset_waitReady(self->handleSet, 1000) > 0) {
|
||||
SNTPClient_handleIncomingMessage(self);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_start(SNTPClient self)
|
||||
{
|
||||
if (self) {
|
||||
int sntpPoirt = SNTP_DEFAULT_PORT;
|
||||
|
||||
if (UdpSocket_bind(self->socket, "0.0.0.0", SNTP_DEFAULT_PORT)) {
|
||||
printf("Start NTP thread\n");
|
||||
|
||||
self->thread = Thread_create(handleThread, self, false);
|
||||
|
||||
if (self->thread)
|
||||
Thread_start(self->thread);
|
||||
}
|
||||
else {
|
||||
if (SNTP_DEBUG)
|
||||
printf("SNTP: Failed to bind to port %i\n", sntpPoirt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_stop(SNTPClient self)
|
||||
{
|
||||
if (self->thread) {
|
||||
self->running = false;
|
||||
Thread_destroy(self->thread);
|
||||
self->thread = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SNTPClient_destroy(SNTPClient self)
|
||||
{
|
||||
if (self) {
|
||||
|
||||
SNTPClient_stop(self);
|
||||
|
||||
if (self->serverAddr)
|
||||
GLOBAL_FREEMEM(self->serverAddr);
|
||||
|
||||
if (self->socket) {
|
||||
Socket_destroy((Socket) self->socket);
|
||||
}
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue