diff --git a/.travis/script.sh b/.travis/script.sh index 76b65a11f..a0ddb2564 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -75,6 +75,7 @@ python --version || die --enable-radio-only \ --enable-raw-link-api \ --enable-service \ + --enable-sntp-client \ --enable-udp-proxy \ --with-examples=posix || die @@ -118,7 +119,7 @@ python --version || die git checkout -- . || die git clean -xfd || die ./bootstrap || die - BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 LINK_RAW=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die + BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 LINK_RAW=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 SNTP_CLIENT=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die arm-none-eabi-size output/nrf52840/bin/ot-cli-ftd || die arm-none-eabi-size output/nrf52840/bin/ot-cli-mtd || die arm-none-eabi-size output/nrf52840/bin/ot-ncp-ftd || die @@ -196,7 +197,7 @@ python --version || die git checkout -- . || die git clean -xfd || die ./bootstrap || die - BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die + BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 SNTP_CLIENT=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die arm-none-eabi-size output/nrf52840/bin/ot-cli-ftd || die arm-none-eabi-size output/nrf52840/bin/ot-cli-mtd || die arm-none-eabi-size output/nrf52840/bin/ot-ncp-ftd || die @@ -274,7 +275,7 @@ python --version || die git checkout -- . || die git clean -xfd || die ./bootstrap || die - BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die + BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 ECDSA=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 SNTP_CLIENT=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die arm-none-eabi-size output/nrf52840/bin/ot-cli-ftd || die arm-none-eabi-size output/nrf52840/bin/ot-cli-mtd || die arm-none-eabi-size output/nrf52840/bin/ot-ncp-ftd || die @@ -363,7 +364,7 @@ python --version || die git checkout -- . || die git clean -xfd || die ./bootstrap || die - BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die + BORDER_ROUTER=1 COAP=1 COMMISSIONER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 DNS_CLIENT=1 FULL_LOGS=1 JOINER=1 MAC_FILTER=1 MTD_NETDIAG=1 SERVICE=1 SNTP_CLIENT=1 UDP_PROXY=1 make -f examples/Makefile-nrf52840 || die arm-none-eabi-size output/nrf52840/bin/ot-cli-ftd || die arm-none-eabi-size output/nrf52840/bin/ot-cli-mtd || die arm-none-eabi-size output/nrf52840/bin/ot-ncp-ftd || die diff --git a/configure.ac b/configure.ac index d58935180..bec4bc51c 100644 --- a/configure.ac +++ b/configure.ac @@ -1336,6 +1336,36 @@ AC_SUBST(OPENTHREAD_ENABLE_DNS_CLIENT) AM_CONDITIONAL([OPENTHREAD_ENABLE_DNS_CLIENT], [test "${enable_dns_client}" = "yes"]) AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_DNS_CLIENT],[${OPENTHREAD_ENABLE_DNS_CLIENT}],[Define to 1 if you want to enable DNS Client]) +# +# SNTP Client +# + +AC_ARG_ENABLE(sntp_client, + [AS_HELP_STRING([--enable-sntp-client],[Enable SNTP client support @<:@default=no@:>@.])], + [ + case "${enableval}" in + + no|yes) + enable_sntp_client=${enableval} + ;; + + *) + AC_MSG_ERROR([Invalid value ${enable_sntp_client} for --enable-sntp-client]) + ;; + esac + ], + [enable_sntp_client=no]) + +if test "$enable_sntp_client" = "yes"; then + OPENTHREAD_ENABLE_SNTP_CLIENT=1 +else + OPENTHREAD_ENABLE_SNTP_CLIENT=0 +fi + +AC_SUBST(OPENTHREAD_ENABLE_SNTP_CLIENT) +AM_CONDITIONAL([OPENTHREAD_ENABLE_SNTP_CLIENT], [test "${enable_sntp_client}" = "yes"]) +AC_DEFINE_UNQUOTED([OPENTHREAD_ENABLE_SNTP_CLIENT],[${OPENTHREAD_ENABLE_SNTP_CLIENT}],[Define to 1 if you want to enable SNTP Client]) + # # Application CoAP # @@ -1915,6 +1945,7 @@ AC_MSG_NOTICE([ OpenThread DHCPv6 Server support : ${enable_dhcp6_server} OpenThread DHCPv6 Client support : ${enable_dhcp6_client} OpenThread DNS Client support : ${enable_dns_client} + OpenThread SNTP Client support : ${enable_sntp_client} OpenThread Application CoAP support : ${enable_application_coap} OpenThread Application CoAP Secure support: ${enable_application_coap_secure} OpenThread Raw Link-Layer support : ${enable_raw_link_api} diff --git a/examples/common-switches.mk b/examples/common-switches.mk index 90ad49e4e..674a6afbf 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -119,6 +119,10 @@ ifeq ($(SERVICE),1) configure_OPTIONS += --enable-service endif +ifeq ($(SNTP_CLIENT),1) +configure_OPTIONS += --enable-sntp-client +endif + ifeq ($(UDP_PROXY),1) configure_OPTIONS += --enable-udp-proxy endif diff --git a/include/openthread/Makefile.am b/include/openthread/Makefile.am index 044c72649..6df7f0b07 100644 --- a/include/openthread/Makefile.am +++ b/include/openthread/Makefile.am @@ -78,6 +78,7 @@ openthread_headers = \ netdata.h \ network_time.h \ server.h \ + sntp.h \ tasklet.h \ thread.h \ thread_ftd.h \ diff --git a/include/openthread/error.h b/include/openthread/error.h index 95f6b5c7e..497041893 100644 --- a/include/openthread/error.h +++ b/include/openthread/error.h @@ -314,7 +314,7 @@ typedef enum otError OT_ERROR_NOT_CAPABLE = 27, /** - * Coap response or acknowledgment or DNS response not received. + * Coap response or acknowledgment or DNS, SNTP response not received. */ OT_ERROR_RESPONSE_TIMEOUT = 28, diff --git a/include/openthread/sntp.h b/include/openthread/sntp.h new file mode 100644 index 000000000..003fb5427 --- /dev/null +++ b/include/openthread/sntp.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file defines the top-level sntp functions for the OpenThread library. + */ + +#ifndef OPENTHREAD_SNTP_H_ +#define OPENTHREAD_SNTP_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-sntp + * + * @brief + * This module includes functions that control SNTP communication. + * + * @{ + * + */ + +#define OT_SNTP_DEFAULT_SERVER_IP "2001:4860:4806:8::" ///< Defines default SNTP Server address - Google NTP Server. +#define OT_SNTP_DEFAULT_SERVER_PORT 123 ///< Defines default SNTP Server port. + +/** + * This structure implements SNTP Query parameters. + * + */ +typedef struct otSntpQuery +{ + const otMessageInfo *mMessageInfo; ///< A reference to the message info related with SNTP Server. +} otSntpQuery; + +/** + * This function pointer is called when a SNTP response is received. + * + * @param[in] aContext A pointer to application-specific context. + * @param[in] aTime Specifies the at the server when the response left for the client, in NTP timestamp format. + * @param[in] aResult A result of the SNTP transaction. + * + * @retval OT_ERROR_NONE A response was received successfully and time is provided + * in @p aTime. + * @retval OT_ERROR_ABORT A SNTP transaction was aborted by stack. + * @retval OT_ERROR_BUSY The Kiss-o'-death packet has been received. + * @retval OT_ERROR_RESPONSE_TIMEOUT No SNTP response has been received within timeout. + * @retval OT_ERROR_FAILED A response was received but contains incorrect data. + * + */ +typedef void (*otSntpResponseHandler)(void *aContext, uint64_t aTime, otError aResult); + +/** + * This function sends a SNTP query. + * + * This function is available only if feature `OPENTHREAD_ENABLE_SNTP_CLIENT` is enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aQuery A pointer to specify SNTP query parameters. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + */ +otError otSntpClientQuery(otInstance * aInstance, + const otSntpQuery * aQuery, + otSntpResponseHandler aHandler, + void * aContext); + +/** + * This function sets the unix era number. + * + * The default value of unix era is set to 0. The subsequent eras start after year 2106. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aUnixEra Unix era number. + * + */ +void otSntpClientSetUnixEra(otInstance *aInstance, uint32_t aUnixEra); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_SNTP_H_ diff --git a/src/cli/README.md b/src/cli/README.md index cc1d6e244..2d7eb570b 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -67,6 +67,7 @@ OpenThread test scripts use the CLI to execute test cases. * [routerupgradethreshold](#routerupgradethreshold) * [scan](#scan) * [singleton](#singleton) +* [sntp](#sntp-query-sntp-server-ip-sntp-server-port) * [state](#state) * [thread](#thread-start) * [txpower](#txpower) @@ -1735,6 +1736,24 @@ true or false Done ``` +### sntp query \[SNTP server IP\] \[SNTP server port\] + +Send SNTP Query to obtain current unix epoch time (from 1st January 1970). +The latter two parameters have following default values: + * NTP server IP: 2001:4860:4806:8:: (Google IPv6 NTP Server) + * NTP server port: 123 + +```bash +> sntp query +> SNTP response - Unix time: 1540894725 (era: 0) +``` + +You can use NAT64 of OpenThread Border Router to reach e.g. Google IPv4 NTP Server: +```bash +> sntp query 64:ff9b::d8ef:2308 +> SNTP response - Unix time: 1540898611 (era: 0) +``` + ### state Return state of current state. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 1d70ebd92..5759ed7f9 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -226,6 +226,9 @@ const struct Command Interpreter::sCommands[] = { {"service", &Interpreter::ProcessService}, #endif {"singleton", &Interpreter::ProcessSingleton}, +#if OPENTHREAD_ENABLE_SNTP_CLIENT + {"sntp", &Interpreter::ProcessSntp}, +#endif {"state", &Interpreter::ProcessState}, {"thread", &Interpreter::ProcessThread}, #ifndef OTDLL @@ -2706,6 +2709,80 @@ void Interpreter::ProcessSingleton(int argc, char *argv[]) AppendResult(error); } +#if OPENTHREAD_ENABLE_SNTP_CLIENT +void Interpreter::ProcessSntp(int argc, char *argv[]) +{ + otError error = OT_ERROR_NONE; + long port = OT_SNTP_DEFAULT_SERVER_PORT; + Ip6::MessageInfo messageInfo; + otSntpQuery query; + + VerifyOrExit(argc > 0, error = OT_ERROR_INVALID_ARGS); + + if (strcmp(argv[0], "query") == 0) + { + VerifyOrExit(!mSntpQueryingInProgress, error = OT_ERROR_BUSY); + + messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD; + + if (argc > 1) + { + SuccessOrExit(error = messageInfo.GetPeerAddr().FromString(argv[1])); + } + else + { + // Use IPv6 address of default SNTP server. + SuccessOrExit(error = messageInfo.GetPeerAddr().FromString(OT_SNTP_DEFAULT_SERVER_IP)); + } + + if (argc > 2) + { + SuccessOrExit(error = ParseLong(argv[2], port)); + } + + messageInfo.SetPeerPort(static_cast(port)); + + query.mMessageInfo = static_cast(&messageInfo); + + SuccessOrExit(error = otSntpClientQuery(mInstance, &query, &Interpreter::s_HandleSntpResponse, this)); + + mSntpQueryingInProgress = true; + } + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + +exit: + if (error != OT_ERROR_NONE) + { + AppendResult(error); + } +} + +void Interpreter::s_HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult) +{ + static_cast(aContext)->HandleSntpResponse(aTime, aResult); +} + +void Interpreter::HandleSntpResponse(uint64_t aTime, otError aResult) +{ + if (aResult == OT_ERROR_NONE) + { + // Some Embedded C libraries do not support printing of 64-bit unsigned integers. + // To simplify, unix epoch time and era number are printed separately. + mServer->OutputFormat("SNTP response - Unix time: %ld (era: %ld)\r\n", + static_cast(aTime & UINT32_MAX), static_cast(aTime >> 32)); + } + else + { + mServer->OutputFormat("SNTP error - %s\r\n", otThreadErrorToString(aResult)); + } + + mSntpQueryingInProgress = false; +} +#endif + void Interpreter::ProcessState(int argc, char *argv[]) { otError error = OT_ERROR_NONE; diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index d4be3917e..c020f8323 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -61,6 +61,7 @@ #ifndef OTDLL #include #include +#include #include "common/timer.hpp" #include "net/icmp6.hpp" #endif @@ -312,6 +313,9 @@ private: void ProcessRloc16(int argc, char *argv[]); void ProcessScan(int argc, char *argv[]); void ProcessSingleton(int argc, char *argv[]); +#if OPENTHREAD_ENABLE_SNTP_CLIENT + void ProcessSntp(int argc, char *argv[]); +#endif void ProcessState(int argc, char *argv[]); void ProcessThread(int argc, char *argv[]); #ifndef OTDLL @@ -366,6 +370,10 @@ private: otError aResult); #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT + static void s_HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult); +#endif + #ifndef OTDLL void HandleIcmpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const otIcmp6Header &aIcmpHeader); void HandlePingTimer(); @@ -386,11 +394,12 @@ private: void HandleDiagnosticGetResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); #endif void HandleJoinerCallback(otError aError); - #if OPENTHREAD_ENABLE_DNS_CLIENT void HandleDnsResponse(const char *aHostname, Ip6::Address &aAddress, uint32_t aTtl, otError aResult); #endif - +#if OPENTHREAD_ENABLE_SNTP_CLIENT + void HandleSntpResponse(uint64_t aTime, otError aResult); +#endif static Interpreter &GetOwner(OwnerLocator &aOwnerLocator); static const struct Command sCommands[]; @@ -434,6 +443,10 @@ private: char mResolvingHostname[OT_DNS_MAX_HOSTNAME_LENGTH]; #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT + bool mSntpQueryingInProgress; +#endif + UdpExample mUdp; #endif diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 995317835..65c71982f 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -126,6 +126,7 @@ SOURCES_COMMON = \ api/netdata_api.cpp \ api/network_time_api.cpp \ api/server_api.cpp \ + api/sntp_api.cpp \ api/tasklet_api.cpp \ api/thread_api.cpp \ api/thread_ftd_api.cpp \ @@ -184,6 +185,7 @@ SOURCES_COMMON = \ net/ip6_mpl.cpp \ net/ip6_routes.cpp \ net/netif.cpp \ + net/sntp_client.cpp \ net/udp6.cpp \ thread/address_resolver.cpp \ thread/announce_begin_server.cpp \ @@ -344,6 +346,7 @@ HEADERS_COMMON = \ net/ip6_mpl.hpp \ net/ip6_routes.hpp \ net/netif.hpp \ + net/sntp_client.hpp \ net/socket.hpp \ net/tcp.hpp \ net/udp6.hpp \ diff --git a/src/core/api/sntp_api.cpp b/src/core/api/sntp_api.cpp new file mode 100644 index 000000000..c4a46700b --- /dev/null +++ b/src/core/api/sntp_api.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread SNTP API. + */ + +#include "openthread-core-config.h" + +#include + +#include "common/instance.hpp" + +using namespace ot; + +#if OPENTHREAD_ENABLE_SNTP_CLIENT +otError otSntpClientQuery(otInstance * aInstance, + const otSntpQuery * aQuery, + otSntpResponseHandler aHandler, + void * aContext) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetThreadNetif().GetSntpClient().Query(aQuery, aHandler, aContext); +} + +void otSntpClientSetUnixEra(otInstance *aInstance, uint32_t aUnixEra) +{ + Instance &instance = *static_cast(aInstance); + + return instance.GetThreadNetif().GetSntpClient().SetUnixEra(aUnixEra); +} +#endif diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index d3e4477f7..fb8601457 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -648,6 +648,13 @@ template <> inline Utils::JamDetector &Instance::Get(void) } #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT +template <> inline Sntp::Client &Instance::Get(void) +{ + return GetThreadNetif().GetSntpClient(); +} +#endif + template <> inline Utils::ChildSupervisor &Instance::Get(void) { return GetThreadNetif().GetChildSupervisor(); diff --git a/src/core/net/sntp_client.cpp b/src/core/net/sntp_client.cpp new file mode 100644 index 000000000..acb43d7b0 --- /dev/null +++ b/src/core/net/sntp_client.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sntp_client.hpp" + +#include "utils/wrap_string.h" + +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" +#include "common/logging.hpp" +#include "common/owner-locator.hpp" +#include "net/udp6.hpp" +#include "thread/thread_netif.hpp" + +#if OPENTHREAD_ENABLE_SNTP_CLIENT + +/** + * @file + * This file implements the SNTP client. + */ + +using ot::Encoding::BigEndian::HostSwap16; + +namespace ot { +namespace Sntp { + +otError Client::Start(void) +{ + otError error; + Ip6::SockAddr addr; + + SuccessOrExit(error = mSocket.Open(&Client::HandleUdpReceive, this)); + SuccessOrExit(error = mSocket.Bind(addr)); + +exit: + return error; +} + +otError Client::Stop(void) +{ + Message * message = mPendingQueries.GetHead(); + Message * messageToRemove; + QueryMetadata queryMetadata; + + // Remove all pending queries. + while (message != NULL) + { + messageToRemove = message; + message = message->GetNext(); + + queryMetadata.ReadFrom(*messageToRemove); + FinalizeSntpTransaction(*messageToRemove, queryMetadata, 0, OT_ERROR_ABORT); + } + + return mSocket.Close(); +} + +otError Client::Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext) +{ + otError error; + QueryMetadata queryMetadata(aHandler, aContext); + Message * message = NULL; + Message * messageCopy = NULL; + Header header; + const Ip6::MessageInfo *messageInfo; + + VerifyOrExit(aQuery->mMessageInfo != NULL, error = OT_ERROR_INVALID_ARGS); + + // Originate timestamp is used only as a unique token. + header.SetTransmitTimestampSeconds(TimerMilli::GetNow() / 1000 + kTimeAt1970); + + VerifyOrExit((message = NewMessage(header)) != NULL, error = OT_ERROR_NO_BUFS); + + messageInfo = static_cast(aQuery->mMessageInfo); + + queryMetadata.mTransmitTimestamp = header.GetTransmitTimestampSeconds(); + queryMetadata.mTransmissionTime = TimerMilli::GetNow() + kResponseTimeout; + queryMetadata.mSourceAddress = messageInfo->GetSockAddr(); + queryMetadata.mDestinationPort = messageInfo->GetPeerPort(); + queryMetadata.mDestinationAddress = messageInfo->GetPeerAddr(); + queryMetadata.mRetransmissionCount = 0; + + VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != NULL, error = OT_ERROR_NO_BUFS); + SuccessOrExit(error = SendMessage(*message, *messageInfo)); + +exit: + + if (error != OT_ERROR_NONE) + { + if (message) + { + message->Free(); + } + + if (messageCopy) + { + DequeueMessage(*messageCopy); + } + } + + return error; +} + +Message *Client::NewMessage(const Header &aHeader) +{ + Message *message = NULL; + + VerifyOrExit((message = mSocket.NewMessage(sizeof(aHeader))) != NULL); + message->Prepend(&aHeader, sizeof(aHeader)); + message->SetOffset(0); + +exit: + return message; +} + +Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata) +{ + otError error = OT_ERROR_NONE; + uint32_t now = TimerMilli::GetNow(); + Message *messageCopy = NULL; + uint32_t nextTransmissionTime; + + // Create a message copy for further retransmissions. + VerifyOrExit((messageCopy = aMessage.Clone()) != NULL, error = OT_ERROR_NO_BUFS); + + // Append the copy with retransmission data and add it to the queue. + SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy)); + mPendingQueries.Enqueue(*messageCopy); + + // Setup the timer. + if (mRetransmissionTimer.IsRunning()) + { + // If timer is already running, check if it should be restarted with earlier fire time. + nextTransmissionTime = mRetransmissionTimer.GetFireTime(); + + if (aQueryMetadata.IsEarlier(nextTransmissionTime)) + { + mRetransmissionTimer.Start(aQueryMetadata.mTransmissionTime - now); + } + } + else + { + mRetransmissionTimer.Start(aQueryMetadata.mTransmissionTime - now); + } + +exit: + + if (error != OT_ERROR_NONE && messageCopy != NULL) + { + messageCopy->Free(); + messageCopy = NULL; + } + + return messageCopy; +} + +void Client::DequeueMessage(Message &aMessage) +{ + mPendingQueries.Dequeue(aMessage); + + if (mRetransmissionTimer.IsRunning() && (mPendingQueries.GetHead() == NULL)) + { + // No more requests pending, stop the timer. + mRetransmissionTimer.Stop(); + } + + // Free the message memory. + aMessage.Free(); +} + +otError Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + return mSocket.SendTo(aMessage, aMessageInfo); +} + +otError Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error; + Message *messageCopy = NULL; + + // Create a message copy for lower layers. + VerifyOrExit((messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata))) != NULL, + error = OT_ERROR_NO_BUFS); + + // Send the copy. + SuccessOrExit(error = SendMessage(*messageCopy, aMessageInfo)); + +exit: + + if (error != OT_ERROR_NONE && messageCopy != NULL) + { + messageCopy->Free(); + } + + return error; +} + +Message *Client::FindRelatedQuery(const Header &aResponseHeader, QueryMetadata &aQueryMetadata) +{ + Header header; + Message *message = mPendingQueries.GetHead(); + + while (message != NULL) + { + // Read originate timestamp. + uint16_t count = aQueryMetadata.ReadFrom(*message); + assert(count == sizeof(aQueryMetadata)); + + if (aQueryMetadata.mTransmitTimestamp == aResponseHeader.GetOriginateTimestampSeconds()) + { + aQueryMetadata.ReadFrom(*message); + ExitNow(); + } + + message = message->GetNext(); + } + +exit: + return message; +} + +void Client::FinalizeSntpTransaction(Message & aQuery, + const QueryMetadata &aQueryMetadata, + uint64_t aTime, + otError aResult) +{ + DequeueMessage(aQuery); + + if (aQueryMetadata.mResponseHandler != NULL) + { + aQueryMetadata.mResponseHandler(aQueryMetadata.mResponseContext, aTime, aResult); + } +} + +void Client::HandleRetransmissionTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleRetransmissionTimer(); +} + +void Client::HandleRetransmissionTimer(void) +{ + uint32_t now = TimerMilli::GetNow(); + uint32_t nextDelta = 0xffffffff; + QueryMetadata queryMetadata; + Message * message = mPendingQueries.GetHead(); + Message * nextMessage = NULL; + Ip6::MessageInfo messageInfo; + + while (message != NULL) + { + nextMessage = message->GetNext(); + queryMetadata.ReadFrom(*message); + + if (queryMetadata.IsLater(now)) + { + // Calculate the next delay and choose the lowest. + if (queryMetadata.mTransmissionTime - now < nextDelta) + { + nextDelta = queryMetadata.mTransmissionTime - now; + } + } + else if (queryMetadata.mRetransmissionCount < kMaxRetransmit) + { + // Increment retransmission counter and timer. + queryMetadata.mRetransmissionCount++; + queryMetadata.mTransmissionTime = now + kResponseTimeout; + queryMetadata.UpdateIn(*message); + + // Check if retransmission time is lower than current lowest. + if (queryMetadata.mTransmissionTime - now < nextDelta) + { + nextDelta = queryMetadata.mTransmissionTime - now; + } + + // Retransmit + messageInfo.SetPeerAddr(queryMetadata.mDestinationAddress); + messageInfo.SetPeerPort(queryMetadata.mDestinationPort); + messageInfo.SetSockAddr(queryMetadata.mSourceAddress); + + SendCopy(*message, messageInfo); + } + else + { + // No expected response. + FinalizeSntpTransaction(*message, queryMetadata, 0, OT_ERROR_RESPONSE_TIMEOUT); + } + + message = nextMessage; + } + + if (nextDelta != 0xffffffff) + { + mRetransmissionTimer.Start(nextDelta); + } +} + +void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) +{ + static_cast(aContext)->HandleUdpReceive(*static_cast(aMessage), + *static_cast(aMessageInfo)); +} + +void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + otError error = OT_ERROR_NONE; + Header responseHeader; + QueryMetadata queryMetadata; + Message * message = NULL; + uint64_t unixTime = 0; + + OT_UNUSED_VARIABLE(aMessageInfo); + + VerifyOrExit(aMessage.Read(aMessage.GetOffset(), sizeof(responseHeader), &responseHeader) == + sizeof(responseHeader)); + + VerifyOrExit((message = FindRelatedQuery(responseHeader, queryMetadata)) != NULL); + + // Check if response came from the server. + VerifyOrExit(responseHeader.GetMode() == Header::kModeServer, error = OT_ERROR_FAILED); + + // Check the Kiss-o'-death packet. + if (!responseHeader.GetStratum()) + { + char kissCode[Header::GetKissCodeLength() + 1]; + + memcpy(kissCode, responseHeader.GetKissCode(), Header::GetKissCodeLength()); + kissCode[Header::GetKissCodeLength()] = 0; + + otLogInfoIp6("SNTP response contains the Kiss-o'-death packet with %s code", kissCode); + ExitNow(error = OT_ERROR_BUSY); + } + + // Check if timestamp has been set. + VerifyOrExit(responseHeader.GetTransmitTimestampSeconds() != 0 && + responseHeader.GetTransmitTimestampFraction() != 0, + error = OT_ERROR_FAILED); + + // The NTP time starts at 1900 while the unix epoch starts at 1970. + // Due to NTP protocol limitation, this module stops working correctly after around year 2106, if + // unix era is not updated. This seems to be a reasonable limitation for now. Era number cannot be + // obtained using NTP protocol, and client of this module is responsible to set it properly. + unixTime = GetUnixEra() * (UINT32_MAX + 1); + + if (responseHeader.GetTransmitTimestampSeconds() > kTimeAt1970) + { + unixTime += static_cast(responseHeader.GetTransmitTimestampSeconds()) - kTimeAt1970; + } + else + { + unixTime += + static_cast(responseHeader.GetTransmitTimestampSeconds()) + (UINT32_MAX + 1) - kTimeAt1970; + } + + // Return the time since 1970. + FinalizeSntpTransaction(*message, queryMetadata, unixTime, OT_ERROR_NONE); + +exit: + + if (message != NULL && error != OT_ERROR_NONE) + { + FinalizeSntpTransaction(*message, queryMetadata, 0, error); + } + + return; +} + +} // namespace Sntp +} // namespace ot + +#endif // OPENTHREAD_ENABLE_SNTP_CLIENT diff --git a/src/core/net/sntp_client.hpp b/src/core/net/sntp_client.hpp new file mode 100644 index 000000000..00395e3f5 --- /dev/null +++ b/src/core/net/sntp_client.hpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2018, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SNTP_CLIENT_HPP_ +#define SNTP_CLIENT_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "common/message.hpp" +#include "common/timer.hpp" +#include "net/ip6.hpp" +#include "net/netif.hpp" + +/** + * @file + * This file includes definitions for the SNTP client. + */ + +namespace ot { +namespace Sntp { + +/** + * This class implements SNTP header generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class Header +{ +public: + /** + * Default constructor for SNTP Header. + * + */ + Header(void) + { + memset(this, 0, sizeof(*this)); + + // Set default value of flags field. + SetFlags(kNtpVersion << kVersionOffset | kModeClient << kModeOffset); + } + + /** + * Defines supported SNTP modes. + * + */ + enum Mode + { + kModeClient = 3, + kModeServer = 4, + }; + + /** + * This method returns the flags field value. + * + * @returns Value of the flags field (LI, VN and Mode). + * + */ + uint8_t GetFlags(void) const { return mFlags; } + + /** + * This method sets the flags field. + * + * @param[in] aFlags The value of the flags field. + * + */ + void SetFlags(uint8_t aFlags) { mFlags = aFlags; } + + /** + * This method returns the SNTP operational mode. + * + * @returns SNTP operational mode. + * + */ + Mode GetMode(void) const { return static_cast((mFlags & kModeMask) >> kModeOffset); } + + /** + * This method returns the packet stratum field value. + * + * @returns Value of the packet stratum. + * + */ + uint8_t GetStratum(void) const { return mStratum; } + + /** + * This method sets the packet stratum field value. + * + * @param[in] aStratum The value of the packet stratum field. + * + */ + void SetStratum(uint8_t aStratum) { mStratum = aStratum; } + + /** + * This method returns the poll field value. + * + * @returns Value of the poll field. + * + */ + uint8_t GetPoll(void) const { return mPoll; } + + /** + * This method sets the poll field. + * + * @param[in] aPoll The value of the poll field. + * + */ + void SetPoll(uint8_t aPoll) { mPoll = aPoll; } + + /** + * This method returns the precision field value. + * + * @returns Value of the precision field. + * + */ + uint8_t GetPrecision(void) const { return mPrecision; } + + /** + * This method sets the precision field. + * + * @param[in] aPrecision The value of the precision field. + * + */ + void SetPrecision(uint8_t aPrecision) { mPrecision = aPrecision; } + + /** + * This method returns the root delay field value. + * + * @returns Value of the the root delay field. + * + */ + uint32_t GetRootDelay(void) const { return HostSwap32(mRootDelay); } + + /** + * This method sets the the root delay field. + * + * @param[in] aRootDelay The value of the the root delay field. + * + */ + void SetRootDelay(uint32_t aRootDelay) { mRootDelay = HostSwap32(aRootDelay); } + + /** + * This method returns the root dispersion field value. + * + * @returns Value of the the root dispersion field. + * + */ + uint32_t GetRootDispersion(void) const { return HostSwap32(mRootDispersion); } + + /** + * This method sets the the root dispersion field. + * + * @param[in] aRootDispersion The value of the the root dispersion field. + * + */ + void SetRootDispersion(uint32_t aRootDispersion) { mRootDispersion = HostSwap32(aRootDispersion); } + + /** + * This method returns the reference identifier field value. + * + * @returns Value of the the reference identifier field. + * + */ + uint32_t GetReferenceId(void) const { return HostSwap32(mReferenceId); } + + /** + * This method sets the the reference identifier field. + * + * @param[in] aReferenceId The value of the the reference identifier field. + * + */ + void SetReferenceId(uint32_t aReferenceId) { mReferenceId = HostSwap32(aReferenceId); } + + /** + * This method returns the kiss code in ASCII format. + * + * @returns Value of the the reference identifier field in ASCII format. + * + */ + char *GetKissCode(void) { return reinterpret_cast(&mReferenceId); } + + /** + * This method returns the length of the kiss code in ASCII format. + * + * @returns The length of the kiss code in ASCII format. + * + */ + static uint16_t GetKissCodeLength(void) { return sizeof(uint32_t); }; + + /** + * This method returns the reference timestamp seconds field. + * + * @returns Value of the reference timestamp seconds field. + * + */ + uint32_t GetReferenceTimestampSeconds(void) const { return HostSwap32(mReferenceTimestampSeconds); } + + /** + * This method sets the reference timestamp seconds field. + * + * @param[in] aReferenceTimestampSeconds Value of the reference timestamp seconds field. + * + */ + void SetReferenceTimestampSeconds(uint32_t aReferenceTimestampSeconds) + { + mReferenceTimestampSeconds = HostSwap32(aReferenceTimestampSeconds); + } + + /** + * This method returns the reference timestamp fraction field. + * + * @returns Value of the reference timestamp fraction field. + * + */ + uint32_t GetReferenceTimestampFraction(void) const { return HostSwap32(mReferenceTimestampFraction); } + + /** + * This method sets the reference timestamp fraction field. + * + * @param[in] aReferenceTimestampFraction Value of the reference timestamp fraction field. + * + */ + void SetReferenceTimestampFraction(uint32_t aReferenceTimestampFraction) + { + mReferenceTimestampFraction = HostSwap32(aReferenceTimestampFraction); + } + + /** + * This method returns the originate timestamp seconds field. + * + * @returns Value of the originate timestamp seconds field. + * + */ + uint32_t GetOriginateTimestampSeconds(void) const { return HostSwap32(mOriginateTimestampSeconds); } + + /** + * This method sets the originate timestamp seconds field. + * + * @param[in] aOriginateTimestampSeconds Value of the originate timestamp seconds field. + * + */ + void SetOriginateTimestampSeconds(uint32_t aOriginateTimestampSeconds) + { + mOriginateTimestampSeconds = HostSwap32(aOriginateTimestampSeconds); + } + + /** + * This method returns the originate timestamp fraction field. + * + * @returns Value of the originate timestamp fraction field. + * + */ + uint32_t GetOriginateTimestampFraction(void) const { return HostSwap32(mOriginateTimestampFraction); } + + /** + * This method sets the originate timestamp fraction field. + * + * @param[in] aOriginateTimestampFraction Value of the originate timestamp fraction field. + * + */ + void SetOriginateTimestampFraction(uint32_t aOriginateTimestampFraction) + { + mOriginateTimestampFraction = HostSwap32(aOriginateTimestampFraction); + } + + /** + * This method returns the receive timestamp seconds field. + * + * @returns Value of the receive timestamp seconds field. + * + */ + uint32_t GetReceiveTimestampSeconds(void) const { return HostSwap32(mReceiveTimestampSeconds); } + + /** + * This method sets the receive timestamp seconds field. + * + * @param[in] aReceiveTimestampSeconds Value of the receive timestamp seconds field. + * + */ + void SetReceiveTimestampSeconds(uint32_t aReceiveTimestampSeconds) + { + mReceiveTimestampSeconds = HostSwap32(aReceiveTimestampSeconds); + } + + /** + * This method returns the receive timestamp fraction field. + * + * @returns Value of the receive timestamp fraction field. + * + */ + uint32_t GetReceiveTimestampFraction(void) const { return HostSwap32(mReceiveTimestampFraction); } + + /** + * This method sets the receive timestamp fraction field. + * + * @param[in] aReceiveTimestampFraction Value of the receive timestamp fraction field. + * + */ + void SetReceiveTimestampFraction(uint32_t aReceiveTimestampFraction) + { + mReceiveTimestampFraction = HostSwap32(aReceiveTimestampFraction); + } + + /** + * This method returns the transmit timestamp seconds field. + * + * @returns Value of the transmit timestamp seconds field. + * + */ + uint32_t GetTransmitTimestampSeconds(void) const { return HostSwap32(mTransmitTimestampSeconds); } + + /** + * This method sets the transmit timestamp seconds field. + * + * @param[in] aTransmitTimestampSeconds Value of the transmit timestamp seconds field. + * + */ + void SetTransmitTimestampSeconds(uint32_t aTransmitTimestampSeconds) + { + mTransmitTimestampSeconds = HostSwap32(aTransmitTimestampSeconds); + } + + /** + * This method returns the transmit timestamp fraction field. + * + * @returns Value of the transmit timestamp fraction field. + * + */ + uint32_t GetTransmitTimestampFraction(void) const { return HostSwap32(mTransmitTimestampFraction); } + + /** + * This method sets the transmit timestamp fraction field. + * + * @param[in] aTransmitTimestampFraction Value of the transmit timestamp fraction field. + * + */ + void SetTransmitTimestampFraction(uint32_t aTransmitTimestampFraction) + { + mTransmitTimestampFraction = HostSwap32(aTransmitTimestampFraction); + } + +private: + /** + * Protocol Constants (RFC 5905). + * + */ + enum + { + /** + * Current NTP version. + * + */ + kNtpVersion = 4, + + /** + * Flags offsets and masks. + * + */ + kLeapOffset = 6, ///< Leap Indicator field offset. + kLeapMask = 0x03 << kLeapOffset, ///< Leap Indicator field mask. + kVersionOffset = 3, ///< Version field offset. + kVersionMask = 0x07 << kVersionOffset, ///< Version field mask. + kModeOffset = 0, ///< Mode field offset. + kModeMask = 0x07 << kModeOffset, ///< Mode filed mask. + }; + + /** + * SNTP Header fields. + * + */ + uint8_t mFlags; ///< SNTP flags: LI Leap Indicator, VN Version Number and Mode. + uint8_t mStratum; ///< Packet Stratum. + uint8_t mPoll; ///< Maximum interval between successive messages, in log2 seconds. + uint8_t mPrecision; ///< The precision of the system clock, in log2 seconds. + uint32_t mRootDelay; ///< Total round-trip delay to the reference clock, in NTP short format. + uint32_t mRootDispersion; ///< Total dispersion to the reference clock. + uint32_t mReferenceId; ///< ID identifying the particular server or reference clock. + uint32_t mReferenceTimestampSeconds; ///< Time when the system clock was last set or corrected, in NTP + ///< timestamp format. + uint32_t mReferenceTimestampFraction; ///< Fraction part of above value. + uint32_t mOriginateTimestampSeconds; ///< Time at the client when the request departed for the server, in NTP + ///< timestamp format. + uint32_t mOriginateTimestampFraction; ///< Fraction part of above value. + uint32_t mReceiveTimestampSeconds; ///< Time at the server when the request arrived from the client, in NTP + ///< timestamp format. + uint32_t mReceiveTimestampFraction; ///< Fraction part of above value. + uint32_t mTransmitTimestampSeconds; ///< Time at the server when the response left for the client, in NTP + ///< timestamp format. + uint32_t mTransmitTimestampFraction; ///< Fraction part of above value. +} OT_TOOL_PACKED_END; + +/** + * This class implements metadata required for SNTP retransmission. + * + */ +OT_TOOL_PACKED_BEGIN +class QueryMetadata +{ + friend class Client; + +public: + /** + * Default constructor for the object. + * + */ + QueryMetadata(void) { memset(this, 0, sizeof(*this)); }; + + /** + * This constructor initializes the object with specific values. + * + * @param[in] aHandler Pointer to a handler function for the response. + * @param[in] aContext Context for the handler function. + * + */ + QueryMetadata(otSntpResponseHandler aHandler, void *aContext) + { + memset(this, 0, sizeof(*this)); + mResponseHandler = aHandler; + mResponseContext = aContext; + }; + + /** + * This method appends request data to the message. + * + * @param[in] aMessage A reference to the message. + * + * @retval OT_ERROR_NONE Successfully appended the bytes. + * @retval OT_ERROR_NO_BUFS Insufficient available buffers to grow the message. + * + */ + otError AppendTo(Message &aMessage) const { return aMessage.Append(this, sizeof(*this)); }; + + /** + * This method reads request data from the message. + * + * @param[in] aMessage A reference to the message. + * + * @returns The number of bytes read. + * + */ + uint16_t ReadFrom(const Message &aMessage) + { + return aMessage.Read(aMessage.GetLength() - sizeof(*this), sizeof(*this), this); + }; + + /** + * This method updates request data in the message. + * + * @param[in] aMessage A reference to the message. + * + * @returns The number of bytes updated. + * + */ + int UpdateIn(Message &aMessage) const + { + return aMessage.Write(aMessage.GetLength() - sizeof(*this), sizeof(*this), this); + } + + /** + * This method checks if the message shall be sent before the given time. + * + * @param[in] aTime A time to compare. + * + * @retval TRUE If the message shall be sent before the given time. + * @retval FALSE Otherwise. + */ + bool IsEarlier(uint32_t aTime) const { return (static_cast(aTime - mTransmissionTime) > 0); }; + + /** + * This method checks if the message shall be sent after the given time. + * + * @param[in] aTime A time to compare. + * + * @retval TRUE If the message shall be sent after the given time. + * @retval FALSE Otherwise. + */ + bool IsLater(uint32_t aTime) const { return (static_cast(aTime - mTransmissionTime) < 0); }; + +private: + uint32_t mTransmitTimestamp; ///< Time at the client when the request departed for the server. + otSntpResponseHandler mResponseHandler; ///< A function pointer that is called on response reception. + void * mResponseContext; ///< A pointer to arbitrary context information. + uint32_t mTransmissionTime; ///< Time when the timer should shoot for this message. + Ip6::Address mSourceAddress; ///< IPv6 address of the message source. + Ip6::Address mDestinationAddress; ///< IPv6 address of the message destination. + uint16_t mDestinationPort; ///< UDP port of the message destination. + uint8_t mRetransmissionCount; ///< Number of retransmissions. +} OT_TOOL_PACKED_END; + +/** + * This class implements Sntp client. + * + */ +class Client +{ +public: + /** + * This constructor initializes the object. + * + * @param[in] aNetif A reference to the network interface that SNTP client should be assigned to. + * + */ + Client(Ip6::Netif &aNetif) + : mSocket(aNetif.GetIp6().GetUdp()) + , mRetransmissionTimer(aNetif.GetInstance(), &Client::HandleRetransmissionTimer, this) + , mUnixEra(0){}; + + /** + * This method starts the SNTP client. + * + * @retval OT_ERROR_NONE Successfully started the SNTP client. + * @retval OT_ERROR_ALREADY The socket is already open. + */ + otError Start(void); + + /** + * This method stops the SNTP client. + * + * @retval OT_ERROR_NONE Successfully stopped the SNTP client. + * + */ + otError Stop(void); + + /** + * This method returns the unix era number. + * + * @returns The unix era number. + * + */ + uint32_t GetUnixEra(void) const { return mUnixEra; } + + /** + * This method sets the unix era number. + * + * @param[in] aUnixEra The unix era number. + * + */ + void SetUnixEra(uint32_t aUnixEra) { mUnixEra = aUnixEra; } + + /** + * This method sends an SNTP query. + * + * @param[in] aQuery A pointer to specify SNTP query parameters. + * @param[in] aHandler A function pointer that shall be called on response reception or time-out. + * @param[in] aContext A pointer to arbitrary context information. + * + * @retval OT_ERROR_NONE Successfully sent SNTP query. + * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data. + * @retval OT_ERROR_INVALID_ARGS Invalid arguments supplied. + * + */ + otError Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext); + +private: + /** + * Protocol Constants (RFC 5905). + * + */ + enum + { + /** + * Number of seconds between 1st January 1900 and 1st January 1970. + * + */ + kTimeAt1970 = 2208988800UL, + }; + + /** + * Retransmission parameters. + * + */ + enum + { + kResponseTimeout = OPENTHREAD_CONFIG_SNTP_RESPONSE_TIMEOUT, + kMaxRetransmit = OPENTHREAD_CONFIG_SNTP_MAX_RETRANSMIT, + }; + + Message *NewMessage(const Header &aHeader); + Message *CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata); + void DequeueMessage(Message &aMessage); + otError SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + otError SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + + Message *FindRelatedQuery(const Header &aResponseHeader, QueryMetadata &aQueryMetadata); + void FinalizeSntpTransaction(Message &aQuery, const QueryMetadata &aQueryMetadata, uint64_t aTime, otError aResult); + + static void HandleRetransmissionTimer(Timer &aTimer); + void HandleRetransmissionTimer(void); + + static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); + void HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + + Ip6::UdpSocket mSocket; + + MessageQueue mPendingQueries; + TimerMilli mRetransmissionTimer; + + uint32_t mUnixEra; +}; + +} // namespace Sntp +} // namespace ot + +#endif // SNTP_CLIENT_HPP_ diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index f06d80586..57ca980cd 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -543,6 +543,26 @@ #define OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT 2 #endif +/** + * @def OPENTHREAD_CONFIG_SNTP_RESPONSE_TIMEOUT + * + * Maximum time that SNTP Client waits for response in milliseconds. + * + */ +#ifndef OPENTHREAD_CONFIG_SNTP_RESPONSE_TIMEOUT +#define OPENTHREAD_CONFIG_SNTP_RESPONSE_TIMEOUT 3000 +#endif + +/** + * @def OPENTHREAD_CONFIG_SNTP_MAX_RETRANSMIT + * + * Maximum number of retransmissions for SNTP client. + * + */ +#ifndef OPENTHREAD_CONFIG_SNTP_MAX_RETRANSMIT +#define OPENTHREAD_CONFIG_SNTP_MAX_RETRANSMIT 2 +#endif + /** * @def OPENTHREAD_CONFIG_JOIN_BEACON_VERSION * diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp index 757fdd776..cd020bdd9 100644 --- a/src/core/thread/thread_netif.cpp +++ b/src/core/thread/thread_netif.cpp @@ -61,6 +61,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance) #if OPENTHREAD_ENABLE_DNS_CLIENT , mDnsClient(aInstance.GetThreadNetif()) #endif // OPENTHREAD_ENABLE_DNS_CLIENT +#if OPENTHREAD_ENABLE_SNTP_CLIENT + , mSntpClient(aInstance.GetThreadNetif()) +#endif // OPENTHREAD_ENABLE_SNTP_CLIENT , mActiveDataset(aInstance) , mPendingDataset(aInstance) , mKeyManager(aInstance) @@ -121,6 +124,9 @@ otError ThreadNetif::Up(void) #if OPENTHREAD_ENABLE_DNS_CLIENT mDnsClient.Start(); #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT + mSntpClient.Start(); +#endif #if OPENTHREAD_ENABLE_CHANNEL_MONITOR GetInstance().GetChannelMonitor().Start(); #endif @@ -137,6 +143,9 @@ otError ThreadNetif::Down(void) #if OPENTHREAD_ENABLE_DNS_CLIENT mDnsClient.Stop(); #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT + mSntpClient.Stop(); +#endif #if OPENTHREAD_ENABLE_CHANNEL_MONITOR GetInstance().GetChannelMonitor().Stop(); #endif diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index 083d9a344..5c3fe2b34 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -65,6 +65,7 @@ #include "net/dns_client.hpp" #include "net/ip6_filter.hpp" #include "net/netif.hpp" +#include "net/sntp_client.hpp" #include "thread/address_resolver.hpp" #include "thread/announce_begin_server.hpp" #include "thread/energy_scan_server.hpp" @@ -419,6 +420,16 @@ public: TimeSync &GetTimeSync(void) { return mTimeSync; } #endif +#if OPENTHREAD_ENABLE_SNTP_CLIENT + /** + * This method returns a reference to the SNTP client object. + * + * @returns A reference to the SNTP client object. + * + */ + Sntp::Client &GetSntpClient(void) { return mSntpClient; } +#endif // OPENTHREAD_ENABLE_SNTP_CLIENT + /** * This method returns whether Thread Management Framework Addressing Rules are met. * @@ -441,6 +452,9 @@ private: #if OPENTHREAD_ENABLE_DNS_CLIENT Dns::Client mDnsClient; #endif // OPENTHREAD_ENABLE_DNS_CLIENT +#if OPENTHREAD_ENABLE_SNTP_CLIENT + Sntp::Client mSntpClient; +#endif // OPENTHREAD_ENABLE_SNTP_CLIENT MeshCoP::ActiveDataset mActiveDataset; MeshCoP::PendingDataset mPendingDataset; Ip6::Filter mIp6Filter;