blob: d924ba69ccf80ad7a68b68aa76a70521192899b2 [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file contains header for the Time Services feature set, which includes
* both time sync and time zone.
* WEAVE_CONFIG_TIME must be defined if Time Services are needed
*
* TimeZoneUtcOffset: coding and decoding of the UTC offset packed binary format
* TimeChangeNotification: coding and decoding of the Time Change Notification message
* TimeSyncRequest: coding and decoding of the Time Sync Request message
* TimeSyncResponse: coding and decoding of the Time Sync Response message
* TimeSyncServer: protocol engine for Time Sync Server
* TimeSyncClient: protocol engine for Time Sync Client
*
*/
#ifndef WEAVE_TIME_H_
#define WEAVE_TIME_H_
// __STDC_CONSTANT_MACROS must be defined for UINT64_C and INT64_C to be defined for pre-C++11 clib
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif // __STDC_CONSTANT_MACROS
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif // __STDC_LIMIT_MACROS
#include <stdint.h>
#include <Weave/Core/WeaveCore.h>
#include <Weave/Profiles/ProfileCommon.h>
namespace nl {
namespace Weave {
namespace Profiles {
namespace Time {
/// type used to store and handle number of microseconds from different epoch
/// if used to express system time, the epoch is 1970/1/1 0:00:00
typedef int64_t timesync_t;
/// used as a bit mask to be applied to timesync_t
/// the highest 6 bits, including sign bit, must be zero, for valid system time
#define MASK_INVALID_TIMESYNC UINT64_C(0xFC00000000000000)
/// used to initialize timestamp (system time) to some invalid value
#define TIMESYNC_INVALID timesync_t(MASK_INVALID_TIMESYNC)
/// Maximum value can be expressed if used as system time (microsecond).
/// This is the largest value that could pass the masking of #MASK_INVALID_TIMESYNC.
#define TIMESYNC_MAX timesync_t(UINT64_MAX & ~(MASK_INVALID_TIMESYNC))
/// maximum value can be expressed if used as system time (second)
#define MAX_TIMESYNC_SEC int64_t(TIMESYNC_MAX / UINT64_C(1000000))
#if WEAVE_CONFIG_TIME_PROGRESS_LOGGING
#define WEAVE_TIME_PROGRESS_LOG WeaveLogProgress
#else // WEAVE_CONFIG_TIME_PROGRESS_LOGGING
#define WEAVE_TIME_PROGRESS_LOG(...)
#endif // WEAVE_CONFIG_TIME_PROGRESS_LOGGING
/// type of a message, used with Weave Exchange
enum
{
kTimeMessageType_TimeSyncTimeChangeNotification = 0,
kTimeMessageType_TimeSyncRequest = 1,
kTimeMessageType_TimeSyncResponse = 2,
};
/// Profile-specific tags used in WDM queries for timezone information
enum
{
kWdmTagTime_Zone_Name = 0x00, ///< The IANA Timezone name in UTF8-String format
kWdmTagTime_Zone_POSIX_TZ = 0x01, ///< The POSIX TZ environment variable in UTF8-String format
kWdmTagTime_Zone_UTC_Offset = 0x02 ///< The UTC offsets for this timezone, in packed binary format
};
/// Roles a protocol engine can play.
/// for example, a TimeSyncServer could be playing a Server or part of a Coordinator.
/// likewise, a TimeSyncClient could be playing a Client or just part of a Coordinator.
enum TimeSyncRole
{
kTimeSyncRole_Unknown = 0,
kTimeSyncRole_Server = 1,
kTimeSyncRole_Coordinator = 2,
kTimeSyncRole_Client = 3,
};
/// Codec for UTC offset of a timezone
class NL_DLL_EXPORT TimeZoneUtcOffset
{
public:
/// conversion information
struct UtcOffsetRecord
{
timesync_t mBeginAt_usec; ///< UTC time, in usec since standard epoch,
///< of the beginning of this conversion period
int32_t mUtcOffset_sec; ///< Offset, in seconds, from UTC to local time
};
/// number of valid entries in mUtcOffsetRecord
uint8_t mSize;
/// entries of UTC offsets
UtcOffsetRecord mUtcOffsetRecord[WEAVE_CONFIG_TIME_NUM_UTC_OFFSET_RECORD];
TimeZoneUtcOffset() :
mSize(0)
{
}
/**
* convert UTC time to local time, using the UTC offsets stored.
*
* @param[out] aLocalTime A pointer to the resulting local time
*
* @param[in] aUtcTime UTC time
*
* @return WEAVE_NO_ERROR On success. WEAVE_ERROR_KEY_NOT_FOUND if it couldn't find reasonable results
*/
WEAVE_ERROR GetCurrentLocalTime(timesync_t * const aLocalTime, const timesync_t aUtcTime) const;
/**
* decode UTC offsets from a byte string, extracted from Weave TLV.
* data type for size is the same as WeaveTLV.h
*
* @param[in] aInputBuf A pointer to the input data buffer
*
* @param[in] aDataSize number of bytes available
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Decode(const uint8_t * const aInputBuf, const uint32_t aDataSize);
/// TimeZoneUtcOffset::BufferSizeForEncoding is a compile time constant, which can be used to declare byte arrays.
/// Callers shall prepare enough buffer size for encoding to complete successfully, and BufferSizeForEncoding is
/// the longest buffer that could be needed.
static const uint32_t BufferSizeForEncoding = 2 + 8 + 4 + (WEAVE_CONFIG_TIME_NUM_UTC_OFFSET_RECORD - 1) * 8;
/**
* encode UTC offsets into a buffer.
* data type for size is the same as WeaveTLV.h
*
* @param[out] aOutputBuf A pointer to the output data buffer
*
* @param[in/out] aDataSize A pointer to number of bytes available in aOutputBuf at calling and will be changed
* to indicate number of bytes used after the function returns.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Encode(uint8_t * const aOutputBuf, uint32_t * const aDataSize);
};
/// codec for Time Change Notification message
class NL_DLL_EXPORT TimeChangeNotification
{
public:
/// default constructor shall be used with Decode, as all members will be initialized through decoding
TimeChangeNotification(void);
/**
* encode time change notification into an PacketBuffer.
*
* @param[out] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Encode(PacketBuffer* const aMsg);
/**
* decode time change notification from an PacketBuffer.
*
* @param[out] aObject A pointer to the decoded object
*
* @param[in] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
static WEAVE_ERROR Decode(TimeChangeNotification * const aObject, PacketBuffer* const aMsg);
};
// codec for Time Sync Request message
class NL_DLL_EXPORT TimeSyncRequest
{
public:
/// default constructor shall be used with Decode, as all members will be initialized through decoding
TimeSyncRequest(void);
/**
* initialize this object for encoding.
*
* @param[in] aLikelihood intended likelihood of response for this time sync request
*
* @param[in] aIsTimeCoordinator true if the originator of this request is a Time Sync Coordinator
*
* @return WEAVE_NO_ERROR on success
*/
void Init(const uint8_t aLikelihood, const bool aIsTimeCoordinator);
/**
* encode time sync request into an PacketBuffer.
*
* @param[out] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Encode(PacketBuffer* const aMsg);
/**
* decode time sync request from an PacketBuffer.
*
* @param[out] aObject A pointer to the decoded object
*
* @param[in] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
static WEAVE_ERROR Decode(TimeSyncRequest * const aObject, PacketBuffer* const aMsg);
/// minimum and maxiumum settings for the intended likelihood of response
/// for this time sync request.
/// Note that we cannot put check on kLikelihoodForResponse_Min in the Encode and Decode
/// routines because it's 0, so it's not safe to adjust it just at here
enum
{
kLikelihoodForResponse_Min = 0,
kLikelihoodForResponse_Max = 31,
};
// Time Sync Request Payload length
enum
{
kPayloadLen = 2,
};
/// intended likelihood of response for this time sync request.
uint8_t mLikelihoodForResponse;
/// true if the originator of this request is a Time Sync Coordinator
bool mIsTimeCoordinator;
};
// codec for Time Sync Response message
class NL_DLL_EXPORT TimeSyncResponse
{
public:
/// default constructor shall be used with Decode, as all members will be initialized through decoding
TimeSyncResponse(void);
/**
* initialize this object for encoding.
*
* @param[in] aRole the role this responder is playing.
* can be either kTimeSyncRole_Server or kTimeSyncRole_Coordinator
*
* @param[in] aTimeOfRequest the system time when the original request was received
*
* @param[in] aTimeOfResponse the system time when this response is being sent out
*
* @param[in] aNumContributorInLastLocalSync number of nodes contributed in the last local time sync
*
* @param[in] aTimeSinceLastSyncWithServer_min number of minutes passed since last sync with a Server
*
*/
void Init(const TimeSyncRole aRole, const timesync_t aTimeOfRequest, const timesync_t aTimeOfResponse,
const uint8_t aNumContributorInLastLocalSync, const uint16_t aTimeSinceLastSyncWithServer_min);
/// maximum number of contributors in the last successful time sync operation on local fabric
enum
{
kNumberOfContributor_Max = 31,
};
/// time, in number of minutes, since last successful time sync with some proxy of atomic time.
/// kTimeSinceLastSyncWithServer_Invalid means this happened too long ago to be relevant, if ever
enum
{
kTimeSinceLastSyncWithServer_Max = 4094,
kTimeSinceLastSyncWithServer_Invalid = 4095,
};
/**
* encode time sync response into an PacketBuffer.
*
* @param[out] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Encode(PacketBuffer* const aMsg);
/**
* decode time sync response from an PacketBuffer.
*
* @param[out] aObject A pointer to the decoded object
*
* @param[in] aMsg A pointer to the PacketBuffer
*
* @return WEAVE_NO_ERROR on success
*/
static WEAVE_ERROR Decode(TimeSyncResponse * const aObject, PacketBuffer* const aMsg);
/// true if this response is constructed by a coordinator;
/// false implies this response is constructed by a server.
bool mIsTimeCoordinator;
/// number of local contributors (coordinators or servers) used in last successful time sync
uint8_t mNumContributorInLastLocalSync;
/// time, in number of minutes, since last successful time sync with some proxy of atomic time
uint16_t mTimeSinceLastSyncWithServer_min;
/// system time (number of microseconds since 1970/1/1 0:00:00) when the request arrived
timesync_t mTimeOfRequest;
/// system time (number of microseconds since 1970/1/1 0:00:00) when the response was prepared
timesync_t mTimeOfResponse;
};
class _TimeSyncNodeBase
{
protected:
_TimeSyncNodeBase(void);
void Init(WeaveFabricState * const aFabricState,
WeaveExchangeManager * const aExchangeMgr);
public:
WeaveFabricState * GetFabricState(void) const
{
return FabricState;
}
WeaveExchangeManager * GetExchangeMgr(void) const
{
return ExchangeMgr;
}
private:
WeaveFabricState * FabricState;
WeaveExchangeManager *ExchangeMgr;
};
/// This is in the public because the TimeSyncNode::FilterTimeCorrectionContributor callback gives
/// a global view to higher layer.
/// It's put in the open instead of being a nested class to make
/// class declaration of TimeSyncNode shorter, and also the export declaration more explicit.
struct NL_DLL_EXPORT Contact
{
/// contains CommState. casted to uint8_t to save space.
/// always valid
uint8_t mCommState;
/// count the number of communication errors have happened for this contact.
/// only valid when mCommState is not kCommState_Invalid
uint8_t mCountCommError;
/// contains ResponseStatus. casted to uint8_t to save space.
/// only valid when mCommState is not kCommState_Invalid
uint8_t mResponseStatus;
/// contains TimeSyncRole. casted to uint8_t to save space
/// only valid if response is not kResponseStatus_Invalid
uint8_t mRole;
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/// true if this contact is learned from time change notification
/// only valid when mCommState is not kCommState_Invalid
bool mIsTimeChangeNotification;
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/// only valid if response is not kResponseStatus_Invalid
uint8_t mNumberOfContactUsedInLastLocalSync;
/// only valid if response is not kResponseStatus_Invalid
uint16_t mTimeSinceLastSuccessfulSync_min;
/// node ID of this contact
/// only valid when mCommState is not kCommState_Invalid
uint64_t mNodeId;
/// node address of this contact
/// only valid when mCommState is not kCommState_Invalid
IPAddress mNodeAddr;
/// used to store the system time of remote node, when the
/// response message was prepared for transmission.
/// only valid if response is not kResponseStatus_Invalid
timesync_t mRemoteTimestamp_usec;
/// used to store one way flight time.
/// only valid if response is not kResponseStatus_Invalid
int32_t mFlightTime_usec;
/// this is the timestamp when the response was received.
/// only valid if response is not kResponseStatus_Invalid
timesync_t mUnadjTimestampLastContact_usec;
};
/// used to specify contacts for calling SyncWithNodes
/// It's put in the open instead of being a nested class to make
/// class declaration of TimeSyncNode shorter, and also the export declaration more explicit.
struct NL_DLL_EXPORT ServingNode
{
uint64_t mNodeId;
IPAddress mNodeAddr;
};
class NL_DLL_EXPORT TimeSyncNode:
public _TimeSyncNodeBase
{
public:
/// current state of this Time Sync Server
enum ServerState
{
kServerState_Uninitialized = 0,
kServerState_ContructionFailed,
kServerState_Constructed,
kServerState_InitializationFailed,
/// time reserved for the server to sync its system time through some other means
/// only meaningful if aIsAlwaysFresh is true when Init is called
kServerState_UnreliableAfterBoot,
/// the server is ready to respond to requests with normal settings
kServerState_Idle,
kServerState_ShutdownCompleted,
kServerState_ShutdownFailed,
};
/// current state of this Time Sync Client
enum ClientState
{
kClientState_Uninitialized = 0,
kClientState_ContructionFailed,
kClientState_Constructed,
kClientState_InitializationFailed,
kClientState_BeginNormal,
kClientState_Idle,
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
kClientState_Sync_Discovery,
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
kClientState_Sync_1,
kClientState_Sync_2,
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
kClientState_ServiceSync_1,
kClientState_ServiceSync_2,
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
kClientState_EndNormal,
kClientState_ShutdownNeeded,
kClientState_ShutdownCompleted,
kClientState_ShutdownFailed,
};
/// status of communication to a certain contact.
/// This is in the public because Contact is in public
enum CommState
{
kCommState_Invalid = 0,
kCommState_Idle,
kCommState_Active,
kCommState_Completed,
};
/// status of stored response to a certain contact.
/// This is in the public because Contact is in public
enum ResponseStatus
{
kResponseStatus_Invalid = 0,
kResponseStatus_ReliableResponse,
kResponseStatus_LessReliableResponse,
kResponseStatus_UnusableResponse,
};
TimeSyncNode(void);
/**
* stop the service, no matter which role it is playing.
* This function must be called to properly reclaim resources allocated, before
* another call to any of the init functions can be made.
* not available in callbacks.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Shutdown(void);
#if WEAVE_CONFIG_TIME_ENABLE_COORDINATOR
/**
* initialize this coordinator.
*
* @param[in] aExchangeMgr A pointer to system wide Weave Exchange Manager object
*
* @param[in] aEncryptionType encryption type to be used for requests and responses
*
* @param[in] aKeyId key id to be used for requests and responses
*
* @param[in] aSyncPeriod_msec number of msec between syncing
*
* @param[in] aNominalDiscoveryPeriod_msec shortest time between discovery, in msec, if no communication error is observed
*
* @param[in] aShortestDiscoveryPeriod_msec smallest number of msec between discovery, if communication error has been observed
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR InitCoordinator(nl::Weave::WeaveExchangeManager *aExchangeMgr, const uint8_t aEncryptionType =
nl::Weave::kWeaveEncryptionType_None,
const uint16_t aKeyId = nl::Weave::WeaveKeyId::kNone,
const int32_t aSyncPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_SYNC_PERIOD_MSEC
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
,
const int32_t aNominalDiscoveryPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_NOMINAL_DISCOVERY_PERIOD_MSEC,
const int32_t aShortestDiscoveryPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_MINIMUM_DISCOVERY_PERIOD_MSEC
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
);
#endif // WEAVE_CONFIG_TIME_ENABLE_COORDINATOR
#if WEAVE_CONFIG_TIME_ENABLE_SERVER
/**
* initialize for the Server role
* must be called as the first function after object construction
* if the intention is to be a Time Sync Server.
* not available in callbacks
*
* @param[in] aApp A pointer to higher layer data, used in callbacks to higher layer.
*
* @param[in] aExchangeMgr A pointer to system wide Weave Exchange Manager object
*
* @param[in] aIsAlwaysFresh could be set to true to indicate the server is always synced
* except for the initial unreliable time.
* shall be set to false for Coordinator.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR InitServer(void * const aApp, WeaveExchangeManager * const aExchangeMgr,
const bool aIsAlwaysFresh = true);
/**
* callback to indicate we just received a time sync request.
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aMsgInfo A WeaveMessageInfo containing information about the received
* time sync request, including information about the sender.
*
* @param[in] aLikelyhood likelihood of response as requested by the originator
*
* @param[in] aIsTimeCoordinator true if the originating node is a Time Sync Coordinator
*
* @return false and the engine shall ignore this request
*/
typedef bool (*OnSyncRequestReceivedHandler)(void * const aApp, const WeaveMessageInfo *aMsgInfo,
const uint8_t aLikelyhood,
const bool aIsTimeCoordinator);
/// if not set, the default implementation always returns true
OnSyncRequestReceivedHandler OnSyncRequestReceived;
/// simple getter for the server state
ServerState GetServerState(void) const;
/// Called by higher layer to indicate that we just finished a round of time sync
/// with either any Server or through some reliable means like NTP.
/// not available in callbacks.
void RegisterCorrectionFromServerOrNtp(void);
/**
* Called by higher layer to indicate that we just finished a round of time sync with
* other local Coordinators.
* not available in callbacks.
*
* @param[in] aNumContributor number of coordinators contributed to this time sync
*
*/
void RegisterLocalSyncOperation(const uint8_t aNumContributor);
/**
* Called by higher layer to multicast time change notification.
* not available in callbacks.
*
* @param[in] aEncryptionType type of encryption to be used for this notification
*
* @param[in] aKeyId key id to be used for this notification
*
*/
void MulticastTimeChangeNotification(const uint8_t aEncryptionType, const uint16_t aKeyId) const;
#endif // #if WEAVE_CONFIG_TIME_ENABLE_SERVER
#if WEAVE_CONFIG_TIME_ENABLE_CLIENT
/**
* initialize this client.
* not available in callbacks
*
* @param[in] aApp A pointer to higher layer data, used in callbacks to higher layer.
*
* @param[in] aExchangeMgr A pointer to system wide Weave Exchange Manager object
*
* @param[in] aRole can be either kTimeSyncRole_Client or kTimeSyncRole_Coordinator
*
* @param[in] aEncryptionType encryption type to be used for requests and responses
*
* @param[in] aKeyId key id to be used for requests and responses
*
* @param[in] aInitialLikelyhood initial likelihood to be used for discovery stage
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR InitClient(void * const aApp, WeaveExchangeManager *aExchangeMgr,
const uint8_t aEncryptionType = kWeaveEncryptionType_None,
const uint16_t aKeyId = WeaveKeyId::kNone
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
,
const int8_t aInitialLikelyhood = TimeSyncRequest::kLikelihoodForResponse_Min
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
);
/**
* callback to indicate we just received a Time Change Notification.
* if auto sync mode is enabled, a time sync would be scheduled shortly
* after this callback automatically.
* otherwise the application layer can choose to call Sync family of functions
* to directly kick off sync operation not restricted by the normal
* not-available-in-call-back rule. however, it must be noted that
* this special callback is still on top of callback stack of weave exchange
* layer.
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aNodeId requesting node ID
*
* @param[in] aNodeAddr requesting node address
*
*/
typedef void (*TimeChangeNotificationHandler)(void * const aApp, const uint64_t aNodeId,
const IPAddress & aNodeAddr);
TimeChangeNotificationHandler OnTimeChangeNotificationReceived;
/**
* callback happens right before we calculate the time correction from responses.
* application layer could overwrite aContact[i].mResponseStatus to kResponseStatus_Invalid
* so that response would be ignored in the calculation
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aContact array of contacts and response status
*
* @param[in] aSize number of records in the aContact array
*
*/
typedef void (*ContributorFilter)(void * const aApp, Contact aContact[], const int aSize);
ContributorFilter FilterTimeCorrectionContributor;
/**
* callback happens after sync is considered successful, including auto sync,
* but before the result is applied. Note that successful doesn't mean we have
* applicable results. In case no response was received, aNumContributor would
* be set to 0.
* application layer could overwrite aContact[i].mResponseStatus to kResponseStatus_Invalid
* so that response would be ignored in the calculation
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aOffsetUsec amount of correction in usec
*
* @param[in] aIsReliable is the correction considered reliable by the built-in logic
*
* @param[in] aIsServer does the correction come from Server(s)
*
* @param[in] aNumContributor number of nodes which contributed to this correction.
* 0 means there is no results from sync operation.
*
* @return true if this offset shall be used to adjust system time.
* in case aNumContributor is 0, the return value would be
* ignored.
*
*/
typedef bool (*SyncSucceededHandler)(void * const aApp, const timesync_t aOffsetUsec, const bool aIsReliable,
const bool aIsServer, const uint8_t aNumContributor);
/// if not set, the default behavior is taking all results, except for very small server corrections
SyncSucceededHandler OnSyncSucceeded;
/**
* callback happens when sync is considered failed, including auto sync.
* note that callback doesn't happen if Abort is called to stop syncing
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aErrorCode reason for the failure
*
*/
typedef void (*SyncFailedHandler)(void * const aApp, const WEAVE_ERROR aErrorCode);
SyncFailedHandler OnSyncFailed;
/// simple getter for client state
ClientState GetClientState(void) const;
/// simple getter for the maximum number of contacts this engine is configured to store
int GetCapacityOfContactList(void) const;
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/**
* extract the likelihood for persistent.
* the result would only be valid after sync operation is completed, within callbacks of OnSyncSucceeded and OnSyncFailed.
* otherwise it's transient and might be the current Likelihood rather than the next one to be used.
*
* @return likelihood for response to be used in the next request
*
*/
int8_t GetNextLikelihood(void) const;
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/**
* enable auto sync.
* only available in idle state.
* discovery happens right away.
* not available in callbacks.
*
* @param[in] aSyncPeriod_msec number of msec between syncing
*
* @param[in] aNominalDiscoveryPeriod_msec number of msec between discovery, if no communication error is observed
*
* @param[in] aShortestDiscoveryPeriod_msec shortest time between discovery, in msec, if communication error has been observed
*
* @return WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR EnableAutoSync(
const int32_t aSyncPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_SYNC_PERIOD_MSEC
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
,
const int32_t aNominalDiscoveryPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_NOMINAL_DISCOVERY_PERIOD_MSEC,
const int32_t aShortestDiscoveryPeriod_msec = WEAVE_CONFIG_TIME_CLIENT_MINIMUM_DISCOVERY_PERIOD_MSEC
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
);
/// disable auto sync.
/// only available in idle state.
/// not available in callbacks.
void DisableAutoSync(void);
/**
* sync using existing contacts.
* sync operation could fail if there is no valid contacts available.
* set aForceDiscoverAgain to true to force discovery immediately.
* only available in idle state.
* not available in callbacks.
*
* @param[in] aForceDiscoverAgain true if all existing contacts shall be flushed and
* discovery operation performed
*
* @return WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR Sync(
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
const bool aForceDiscoverAgain = false
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
);
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
/**
* sync using the given TCP connection and associated encryption and key id.
* caller must take ownership of the TCP connection after sync finishes.
* no callback would be overwritten for the TCP connection, as a new Weave Exchange
* would be created and callbacks set on top of that context
* only available in idle state.
* not available in callbacks.
*
* @param[in] aConnection A pointer to Weave connection
*
* @return WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR SyncWithService(WeaveConnection * const aConnection);
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
/**
* sync using the given list of contacts.
* existing contact list would be flushed.
* only available in idle state.
* not available in callbacks.
*
* @param[in] aNumNode number of contact in array aNodes
*
* @param[in] aNodes array of contact records
*
* @return WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR SyncWithNodes(const int16_t aNumNode, const ServingNode aNodes[]);
/**
* force the engine to go back to idle state, aborting anything it is doing.
* note no sync success or failure would be called.
* all Weave Exchanges would be closed.
* TCP connections would not be touched further.
* no operation if we're already in idle state.
* not available in callbacks.
*
* @return WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR Abort(void);
/// encryption method for local communication
uint8_t mEncryptionType;
/// key id used for local communication
uint16_t mKeyId;
#endif // WEAVE_CONFIG_TIME_ENABLE_CLIENT
protected:
/// pointer to higher layer data
void * mApp;
/// Actual role of this node
TimeSyncRole mRole;
/// true if we're in a callback to higher layer
bool mIsInCallback;
WEAVE_ERROR InitState(const TimeSyncRole aRole,
void * const aApp, WeaveExchangeManager * const aExchangeMgr);
void ClearState(void);
#if WEAVE_CONFIG_TIME_ENABLE_COORDINATOR
/**
* stop the coordinator
* not available in callbacks.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR _ShutdownCoordinator(void);
static bool _OnSyncSucceeded(void * const aApp, const nl::Weave::Profiles::Time::timesync_t aOffsetUsec,
const bool aIsReliable,
const bool aIsServer, const uint8_t aNumContributor);
#endif // WEAVE_CONFIG_TIME_ENABLE_COORDINATOR
#if WEAVE_CONFIG_TIME_ENABLE_SERVER
ServerState mServerState;
bool mIsAlwaysFresh;
uint8_t mNumContributorInLastLocalSync;
/// note it has to be boot time as we need compensation for sleep time
timesync_t mTimestampLastCorrectionFromServerOrNtp_usec;
/// note it has to be boot time as we need compensation for sleep time
timesync_t mTimestampLastLocalSync_usec;
/**
* initialize for the Server role.
* Intended to be used internally by Init family of public functions.
* Must set mClientState before return.
* not available in callbacks
*
* @param[in] aIsAlwaysFresh could be set to true to indicate the server is always synced
* except for the initial unreliable time.
* shall be set to false for Coordinator.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR _InitServer(const bool aIsAlwaysFresh);
/**
* stop the server
* not available in callbacks.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR _ShutdownServer(void);
/// callback from Weave Exchange when a time sync request arrives
static void HandleSyncRequest(ExchangeContext *ec, const IPPacketInfo *pktInfo, const WeaveMessageInfo *msgInfo,
uint32_t profileId, uint8_t msgType, PacketBuffer *payload);
/// callback from Weave Timer when we passed the unreliable after boot barrier
static void HandleUnreliableAfterBootTimer(System::Layer* aSystemLayer, void* aAppState, System::Error aError);
#endif // WEAVE_CONFIG_TIME_ENABLE_SERVER
#if WEAVE_CONFIG_TIME_ENABLE_CLIENT
ClientState mClientState;
//@{
/// states used for auto sync feature.
bool mIsAutoSyncEnabled;
int32_t mSyncPeriod_msec;
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
bool mIsUrgentDiscoveryPending;
int32_t mNominalDiscoveryPeriod_msec;
int32_t mShortestDiscoveryPeriod_msec;
timesync_t mBootTimeForNextAutoDiscovery_usec;
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
//@}
/// Contact information learned throughout discovery
Contact mContacts[WEAVE_CONFIG_TIME_CLIENT_MAX_NUM_CONTACTS];
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
// contact information for talking to the service. this is independent from mContacts, so talking to the service
// doesn't wipe out results learned from discovery
Contact mServiceContact;
/// TCP connection used to talk to the service
WeaveConnection * mConnectionToService;
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
//@{
/// communication context.
//CommToken mCommToken[WEAVE_CONFIG_TIME_CLIENT_MAX_NUM_EXCHANGE_CONTEXTS];
Contact * mActiveContact;
ExchangeContext * mExchangeContext;
timesync_t mUnadjTimestampLastSent_usec;
//@}
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
int8_t mLastLikelihoodSent;
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/**
* initialize for the Client role.
* Intended to be used internally by Init family of public functions.
* Must set mClientState before return.
* not available in callbacks
*
* @param[in] aEncryptionType encryption type to be used for requests and responses
*
* @param[in] aKeyId key id to be used for requests and responses
*
* @param[in] aInitialLikelyhood initial likelihood to be used for discovery stage
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR _InitClient(
const uint8_t aEncryptionType,
const uint16_t aKeyId
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
,
const int8_t aInitialLikelyhood
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
);
/**
* stop the client
* not available in callbacks.
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR _ShutdownClient(void);
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
/// invalidate contact to the service
void InvalidateServiceContact(void);
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
/// invalidate all local contacts
void InvalidateAllContacts(void);
/// set all valid local contacts to idle state and clear the response.
/// this is called before we start contacting them one by one
int16_t SetAllValidContactsToIdleAndInvalidateResponse(void);
/// reset all completed contacts to idle state again, but don't touch the response.
/// this is called between two rounds of communication to the same node
int16_t SetAllCompletedContactsToIdle(void);
/// get the number of contacts that are valid, but we haven't talk to them yet.
int16_t GetNumNotYetCompletedContacts(void);
/// get the number of 'reliable' responses collected so far.
/// called to determine if we have collected enough number of responses
int16_t GetNumReliableResponses(void);
/// get the next valid and idle contact to talk to
Contact * GetNextIdleContact(void);
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/// return a slot to store contact information
Contact * FindReplaceableContact(const uint64_t aNodeId, const IPAddress & aNodeAddr,
bool aIsTimeChangeNotification = false);
/// process a response coming back from a multicast request
void UpdateMulticastSyncResponse(const uint64_t aNodeId,
const IPAddress & aNodeAddr, const TimeSyncResponse & aResponse);
/// store the contact information of a node who just sent us a time change notification
void StoreNotifyingContact(const uint64_t aNodeId, const IPAddress & aNodeAddr);
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
/// process a response coming back from a unicast request
void UpdateUnicastSyncResponse(const TimeSyncResponse & aResponse);
// wrap up a local sync and calculate the correction
void EndLocalSyncAndTryCalculateTimeFix(void);
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
// wrap up a sync with the service and calculate the correction
void EndServiceSyncAndTryCalculateTimeFix(void);
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
/// induce callback to the application layer.
/// set aIsSuccessful to false to induce the error callback
WEAVE_ERROR CallbackForSyncCompletion(const bool aIsSuccessful, bool aShouldUpdate,
const bool aIsCorrectionReliable, const bool aIsFromServer, const uint8_t aNumContributor,
const timesync_t aSystemTimestamp_usec, const timesync_t aDiffTime_usec);
/// internal abort if aCode is not WEAVE_NO_ERROR
void AbortOnError(const WEAVE_ERROR aCode);
/// register communication error on a certain contact, and shorten auto discovery period if needed
/// aContact can be NULL to indicate we have no one to talk to, and hence just shorten the auto discovery period
void RegisterCommError(Contact * const aContact = NULL);
/// close the Weave ExchangeContext
bool DestroyCommContext(void);
/// create new Weave Exchange for unicast communication
WEAVE_ERROR SetupUnicastCommContext(Contact * const aContact);
/// send unicast sync request to a contact.
/// *rIsMessageSent will be set to indicate if the message has been sent out.
/// communication errors like address not reachable is not returned,
/// so caller shall check both the return code and *rIsMessageSent.
WEAVE_ERROR SendSyncRequest(bool * const rIsMessageSent, Contact * const aContact);
void SetClientState(const ClientState state);
const char * const GetClientStateName(void) const;
/// internal function to kick off an auto sync session
void AutoSyncNow(void);
//@{
/// these state transition functions are internal and cannot return error code as the previous state would have no way
/// to handle them. any failure shall result to eventually another state transition (could be timeout)
/// if even the timer fails, we are out of trick and could hang in some wrong state
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
void EnterState_Discover(void);
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
void EnterState_Sync_1(void);
void EnterState_Sync_2(void);
#if WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
void EnterState_ServiceSync_1(void);
void EnterState_ServiceSync_2(void);
#endif // WEAVE_CONFIG_TIME_CLIENT_CONNECTION_FOR_SERVICE
//@}
static void HandleTimeChangeNotification(ExchangeContext *ec, const IPPacketInfo *pktInfo,
const WeaveMessageInfo *msgInfo, uint32_t profileId, uint8_t msgType, PacketBuffer *payload);
static void HandleUnicastSyncResponse(ExchangeContext *ec, const IPPacketInfo *pktInfo,
const WeaveMessageInfo *msgInfo, uint32_t profileId, uint8_t msgType, PacketBuffer *payload);
#if WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
static void HandleMulticastResponseTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError);
static void HandleMulticastSyncResponse(ExchangeContext *ec, const IPPacketInfo *pktInfo,
const WeaveMessageInfo *msgInfo, uint32_t profileId, uint8_t msgType, PacketBuffer *payload);
static void HandleAutoDiscoveryTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError);
#endif // WEAVE_CONFIG_TIME_CLIENT_FABRIC_LOCAL_DISCOVERY
static void HandleUnicastResponseTimeout(ExchangeContext * const ec);
static void HandleAutoSyncTimeout(System::Layer* aSystemLayer, void* aAppState, System::Error aError);
#endif // WEAVE_CONFIG_TIME_ENABLE_CLIENT
};
class NL_DLL_EXPORT SingleSourceTimeSyncClient
{
public:
/// current state of this Time Sync Client
enum ClientState
{
kClientState_Idle, //< Initialized, waiting for Time Change Notification, but no actual time sync operation is happening
kClientState_Sync_1, //< Working on the first time sync attempt
kClientState_Sync_2, //< Working on the second time sync attempt
};
/**
* @brief
* Retrieve current state of this client
*
* @return current state
*/
ClientState GetClientState(void) const { return mClientState; };
/**
* @brief
* Initialize this client. Must be called before other functions can be used.
* Zero/NULL initialize all internal data and register with Time Change Notification.
*
* @param[in] aApp A pointer to higher layer data, used in callbacks to higher layer.
*
* @param[in] aExchangeMgr A pointer to Exchange Manager, which would be used in registering for Time Change Notification message handler
*
* @return WEAVE_NO_ERROR on success
*/
WEAVE_ERROR Init(void * const aApp, WeaveExchangeManager * const aExchangeMgr);
/**
* @brief
* Abort current time sync operation. Release Binding. Abort active exchange. Move back to idle state.
*
*/
void Abort(void);
/**
* @brief
* Callback to indicate we just received a Time Change Notification.
* Set to NULL at #Init. If not set, Time Change Notification would be ignored.
* App layer is allowed to call Abort and Sync in this callback.
*
* @param[in] aApp A pointer to app layer data, set in Init.
* @param[in] aEC Exchange context used for this incoming message, which can be used to validate its authenticity
*
*/
typedef void (*TimeChangeNotificationHandler)(void * const aApp, ExchangeContext * aEC);
TimeChangeNotificationHandler OnTimeChangeNotificationReceived;
/**
* @brief
* Callback after both time sync attempts have been completed. If #aErrorCode is WEAVE_NO_ERROR,
* at least one attempt has succeeded. Otherwise both failed at #aErrorCode indicates the latest failure.
*
* @param[in] aApp A pointer to app layer data, set in Init.
*
* @param[in] aErrorCode #WEAVE_NO_ERROR if at least one time sync operation is successful
*
* @param[in] aCorrectedSystemTime Only valid if #aErrorCode is #WEAVE_NO_ERROR
*/
typedef void (*SyncCompletionHandler)(void * const aApp, const WEAVE_ERROR aErrorCode, const timesync_t aCorrectedSystemTime);
/**
* @brief
* Sync using the given Binding and makes a callback using the pointer provided.
* If there is a time sync operation going on, it would be aborted implicitly without callback being made.
* Not available in callback #OnSyncCompleted, but allowed in #OnTimeChangeNotificationReceived .
* On error, Abort would be called implicitly before returning from this function.
*
* @param[in] aBinding Binding to be used in contacting the time server
*
* @param[in] OnSyncCompleted Callback function to be used after the time sync operations are completed
*
* @return #WEAVE_NO_ERROR on success
*
*/
WEAVE_ERROR Sync(Binding * const aBinding, SyncCompletionHandler OnSyncCompleted);
protected:
enum
{
kFlightTimeMinimum = 0,
kFlightTimeInvalid = -1
};
void * mApp;
WeaveExchangeManager * mExchangeMgr;
Binding * mBinding;
bool mIsInCallback;
ClientState mClientState;
ExchangeContext * mExchangeContext;
/// used to store one way flight time.
int32_t mFlightTime_usec;
timesync_t mUnadjTimestampLastSent_usec;
/// used to store the system time of remote node, when the
/// response message was about to be sent
timesync_t mRemoteTimestamp_usec;
/// used to store Timestamp when a result is registered
timesync_t mRegisterSyncResult_usec;
SyncCompletionHandler mOnSyncCompleted;
void RegisterSyncResultIfNewOrBetter(const timesync_t aNow_usec, const timesync_t aRemoteTimestamp_usec, const int32_t aFlightTime_usec);
void _AbortWithCallback(const WEAVE_ERROR aErrorCode);
WEAVE_ERROR SendSyncRequest(void);
void SetClientState(const ClientState state);
const char * const GetClientStateName(void) const;
static void HandleTimeChangeNotification(ExchangeContext *aEC, const IPPacketInfo *aPktInfo,
const WeaveMessageInfo *aMsgInfo, uint32_t aProfileId, uint8_t aMsgType, PacketBuffer *aPayload);
static void HandleSyncResponse(ExchangeContext *aEC, const IPPacketInfo *aPktInfo,
const WeaveMessageInfo *aMsgInfo, uint32_t aProfileId, uint8_t aMsgType, PacketBuffer *aPayload);
void OnSyncResponse(uint32_t aProfileId, uint8_t aMsgType, PacketBuffer *aPayload);
static void HandleResponseTimeout(ExchangeContext *aEC);
void OnResponseTimeout(void);
/// Invalidate the registered information for time correction
inline void InvalidateRegisteredResult(void) { mFlightTime_usec = kFlightTimeInvalid; };
/// Check if the registered information for time correction is valid
inline bool IsRegisteredResultValid(void) { return mFlightTime_usec >= kFlightTimeMinimum; };
void ProceedToNextState(void);
void EnterSync2(void);
void FinalProcessing(void);
};
} // namespace Time
} // namespace Profiles
/// Platform-provided time routines.
namespace Platform
{
namespace Time
{
/**
* get CLOCK_MONOTONIC_RAW, CLOCK_MONOTONIC, or equivalent clock reading.
* This clock is used to timestamp events that happen with short time in between them.
* Higher resolution is expected but doesn't have to be compensated for sleep time.
* Note that it is okay if it comes with sleep time compensation, but higher resolution is the key.
* Without better alternatives, this can be implemented by GetSleepCompensatedMonotonicTime.
*
* @param[out] p_timestamp_usec A pointer to the clock reading
*
* @return WEAVE_NO_ERROR on success
*/
NL_DLL_EXPORT WEAVE_ERROR GetMonotonicRawTime(Profiles::Time::timesync_t * const p_timestamp_usec);
/**
* get CLOCK_REALTIME or equivalent clock reading.
*
* @param[out] p_timestamp_usec A pointer to the clock reading
*
* @return WEAVE_NO_ERROR on success
*/
NL_DLL_EXPORT WEAVE_ERROR GetSystemTime(Profiles::Time::timesync_t * const p_timestamp_usec);
/**
* get CLOCK_REALTIME or equivalent clock reading in ms.
*
* @param[out] p_timestamp_msec A pointer to the clock reading
*
* @return WEAVE_NO_ERROR on success
*/
NL_DLL_EXPORT WEAVE_ERROR GetSystemTimeMs(Profiles::Time::timesync_t * const p_timestamp_msec);
/**
* set CLOCK_REALTIME or equivalent clock.
*
* @param[in] timestamp_usec intended new value for CLOCK_REALTIME
*
* @return WEAVE_NO_ERROR on success
*/
NL_DLL_EXPORT WEAVE_ERROR SetSystemTime(const Profiles::Time::timesync_t timestamp_usec);
/**
* get CLOCK_BOOTTIME or equivalent clock reading.
* This clock is used to timestamp events that happen long time apart.
* Highest resolution is not the concern but it must be compensated for sleep time.
*
* @param[out] p_timestamp_usec A pointer to the clock reading
*
* @return WEAVE_NO_ERROR on success
*/
NL_DLL_EXPORT WEAVE_ERROR GetSleepCompensatedMonotonicTime(Profiles::Time::timesync_t * const p_timestamp_usec);
} // namespace Time
} // namespace Platform
} // namespace Weave
} // namespace nl
#endif // WEAVE_TIME_H_