text: /*
   *  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 <openthread/sntp.h>
  
  #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 {
  
  using ot::Encoding::BigEndian::HostSwap32;
  
  /**
   * 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,
      };
  
      /**
       * Kiss code length.
       *
       */
      enum
      {
          kKissCodeLength = 4, ///< Length of the kiss code in ASCII format
      };
  
      /**
       * 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<Mode>((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 root delay field.
       *
       */
      uint32_t GetRootDelay(void) const { return HostSwap32(mRootDelay); }
  
      /**
       * This method sets the root delay field.
       *
       * @param[in]  aRootDelay  The value of the root delay field.
       *
       */
      void SetRootDelay(uint32_t aRootDelay) { mRootDelay = HostSwap32(aRootDelay); }
  
      /**
       * This method returns the root dispersion field value.
       *
       * @returns Value of the root dispersion field.
       *
       */
      uint32_t GetRootDispersion(void) const { return HostSwap32(mRootDispersion); }
  
      /**
       * This method sets the root dispersion field.
       *
       * @param[in]  aRootDispersion  The value of the root dispersion field.
       *
       */
      void SetRootDispersion(uint32_t aRootDispersion) { mRootDispersion = HostSwap32(aRootDispersion); }
  
      /**
       * This method returns the reference identifier field value.
       *
       * @returns Value of the reference identifier field.
       *
       */
      uint32_t GetReferenceId(void) const { return HostSwap32(mReferenceId); }
  
      /**
       * This method sets the reference identifier field.
       *
       * @param[in]  aReferenceId  The value of 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 reference identifier field in ASCII format.
       *
       */
      char *GetKissCode(void) { return reinterpret_cast<char *>(&mReferenceId); }
  
      /**
       * 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<int32_t>(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<int32_t>(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.
       *
       */
      explicit Client(Ip6::Netif &aNetif);
  
      /**
       * 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_
  