1 /*
2  *  Copyright (c) 2018, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements OpenThread Time Synchronization Service.
32  */
33 
34 #include "openthread-core-config.h"
35 
36 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
37 
38 #include "time_sync_service.hpp"
39 
40 #include <openthread/platform/alarm-micro.h>
41 #include <openthread/platform/alarm-milli.h>
42 #include <openthread/platform/time.h>
43 
44 #include "common/instance.hpp"
45 #include "common/locator_getters.hpp"
46 #include "common/logging.hpp"
47 
48 #define ABS(value) (((value) >= 0) ? (value) : -(value))
49 
50 namespace ot {
51 
TimeSync(Instance & aInstance)52 TimeSync::TimeSync(Instance &aInstance)
53     : InstanceLocator(aInstance)
54     , mTimeSyncRequired(false)
55     , mTimeSyncSeq(OT_TIME_SYNC_INVALID_SEQ)
56     , mTimeSyncPeriod(OPENTHREAD_CONFIG_TIME_SYNC_PERIOD)
57     , mXtalThreshold(OPENTHREAD_CONFIG_TIME_SYNC_XTAL_THRESHOLD)
58 #if OPENTHREAD_FTD
59     , mLastTimeSyncSent(0)
60 #endif
61     , mLastTimeSyncReceived(0)
62     , mNetworkTimeOffset(0)
63     , mTimeSyncCallback(nullptr)
64     , mTimeSyncCallbackContext(nullptr)
65     , mTimer(aInstance, HandleTimeout)
66     , mCurrentStatus(OT_NETWORK_TIME_UNSYNCHRONIZED)
67 {
68     CheckAndHandleChanges(false);
69 }
70 
GetTime(uint64_t & aNetworkTime) const71 otNetworkTimeStatus TimeSync::GetTime(uint64_t &aNetworkTime) const
72 {
73     aNetworkTime = static_cast<uint64_t>(static_cast<int64_t>(otPlatTimeGet()) + mNetworkTimeOffset);
74 
75     return mCurrentStatus;
76 }
77 
HandleTimeSyncMessage(const Message & aMessage)78 void TimeSync::HandleTimeSyncMessage(const Message &aMessage)
79 {
80     const int64_t origNetworkTimeOffset = mNetworkTimeOffset;
81     int8_t        timeSyncSeqDelta;
82 
83     VerifyOrExit(aMessage.GetTimeSyncSeq() != OT_TIME_SYNC_INVALID_SEQ);
84 
85     timeSyncSeqDelta = static_cast<int8_t>(aMessage.GetTimeSyncSeq() - mTimeSyncSeq);
86 
87     if (mTimeSyncSeq != OT_TIME_SYNC_INVALID_SEQ && timeSyncSeqDelta < 0)
88     {
89         // An older time sync sequence was received. This indicates that there is a device that still needs to be
90         // synchronized with the current sequence, so forward it.
91         mTimeSyncRequired = true;
92 
93         otLogInfoCore("Older time sync seq received:%u. Forwarding current seq:%u", aMessage.GetTimeSyncSeq(),
94                       mTimeSyncSeq);
95     }
96     else if (Get<Mle::MleRouter>().IsLeader() && timeSyncSeqDelta > 0)
97     {
98         // Another device is forwarding a later time sync sequence, perhaps because it merged from a different
99         // partition. The leader is authoritative, so ensure all devices synchronize to the time being seeded by this
100         // leader instead.
101         mTimeSyncSeq      = aMessage.GetTimeSyncSeq() + 1;
102         mTimeSyncRequired = true;
103 
104         otLogInfoCore("Newer time sync seq:%u received by leader. Setting current seq to:%u and forwarding",
105                       aMessage.GetTimeSyncSeq(), mTimeSyncSeq);
106     }
107     else if (!Get<Mle::MleRouter>().IsLeader())
108     {
109         // For all devices aside from the leader, update network time in following three cases:
110         //  1. During first attach.
111         //  2. Already attached, and a newer time sync sequence was received.
112         //  3. During reattach or migration process.
113         if (mTimeSyncSeq == OT_TIME_SYNC_INVALID_SEQ || timeSyncSeqDelta > 0 || Get<Mle::MleRouter>().IsDetached())
114         {
115             // Update network time and forward it.
116             mLastTimeSyncReceived = TimerMilli::GetNow();
117             mTimeSyncSeq          = aMessage.GetTimeSyncSeq();
118             mNetworkTimeOffset    = aMessage.GetNetworkTimeOffset();
119             mTimeSyncRequired     = true;
120 
121             otLogInfoCore("Newer time sync seq:%u received. Forwarding", mTimeSyncSeq);
122 
123             // Only notify listeners of an update for network time offset jumps of more than
124             // OPENTHREAD_CONFIG_TIME_SYNC_JUMP_NOTIF_MIN_US but notify listeners regardless if the status changes.
125             CheckAndHandleChanges(ABS(mNetworkTimeOffset - origNetworkTimeOffset) >=
126                                   OPENTHREAD_CONFIG_TIME_SYNC_JUMP_NOTIF_MIN_US);
127         }
128     }
129 
130 exit:
131     return;
132 }
133 
IncrementTimeSyncSeq(void)134 void TimeSync::IncrementTimeSyncSeq(void)
135 {
136     if (++mTimeSyncSeq == OT_TIME_SYNC_INVALID_SEQ)
137     {
138         ++mTimeSyncSeq;
139     }
140 }
141 
NotifyTimeSyncCallback(void)142 void TimeSync::NotifyTimeSyncCallback(void)
143 {
144     if (mTimeSyncCallback != nullptr)
145     {
146         mTimeSyncCallback(mTimeSyncCallbackContext);
147     }
148 }
149 
150 #if OPENTHREAD_FTD
ProcessTimeSync(void)151 void TimeSync::ProcessTimeSync(void)
152 {
153     if (Get<Mle::MleRouter>().IsLeader() &&
154         (TimerMilli::GetNow() - mLastTimeSyncSent > Time::SecToMsec(mTimeSyncPeriod)))
155     {
156         IncrementTimeSyncSeq();
157         mTimeSyncRequired = true;
158 
159         otLogInfoCore("Leader seeding new time sync seq:%u", mTimeSyncSeq);
160     }
161 
162     if (mTimeSyncRequired)
163     {
164         VerifyOrExit(Get<Mle::MleRouter>().SendTimeSync() == kErrorNone);
165 
166         mLastTimeSyncSent = TimerMilli::GetNow();
167         mTimeSyncRequired = false;
168     }
169 
170 exit:
171     return;
172 }
173 #endif // OPENTHREAD_FTD
174 
HandleNotifierEvents(Events aEvents)175 void TimeSync::HandleNotifierEvents(Events aEvents)
176 {
177     bool stateChanged = false;
178 
179     if (aEvents.Contains(kEventThreadRoleChanged))
180     {
181         stateChanged = true;
182     }
183 
184     if (aEvents.Contains(kEventThreadPartitionIdChanged) && !Get<Mle::MleRouter>().IsLeader())
185     {
186         // Partition has changed. Accept any network time currently being seeded on the new partition
187         // and don't attempt to forward the currently held network time from the previous partition.
188         mTimeSyncSeq      = OT_TIME_SYNC_INVALID_SEQ;
189         mTimeSyncRequired = false;
190 
191         // Network time status will become OT_NETWORK_TIME_UNSYNCHRONIZED because no network time has yet been received
192         // on the new partition.
193         mLastTimeSyncReceived.SetValue(0);
194 
195         stateChanged = true;
196 
197         otLogInfoCore("Resetting time sync seq, partition changed");
198     }
199 
200     if (stateChanged)
201     {
202         CheckAndHandleChanges(false);
203     }
204 }
205 
HandleTimeout(void)206 void TimeSync::HandleTimeout(void)
207 {
208     CheckAndHandleChanges(false);
209 }
210 
HandleTimeout(Timer & aTimer)211 void TimeSync::HandleTimeout(Timer &aTimer)
212 {
213     aTimer.Get<TimeSync>().HandleTimeout();
214 }
215 
CheckAndHandleChanges(bool aTimeUpdated)216 void TimeSync::CheckAndHandleChanges(bool aTimeUpdated)
217 {
218     otNetworkTimeStatus networkTimeStatus       = OT_NETWORK_TIME_SYNCHRONIZED;
219     const uint32_t      resyncNeededThresholdMs = 2 * Time::SecToMsec(mTimeSyncPeriod);
220     const uint32_t      timeSyncLastSyncMs      = TimerMilli::GetNow() - mLastTimeSyncReceived;
221 
222     mTimer.Stop();
223 
224     switch (Get<Mle::MleRouter>().GetRole())
225     {
226     case Mle::kRoleDisabled:
227     case Mle::kRoleDetached:
228         networkTimeStatus = OT_NETWORK_TIME_UNSYNCHRONIZED;
229         otLogInfoCore("Time sync status UNSYNCHRONIZED as role:DISABLED/DETACHED");
230         break;
231 
232     case Mle::kRoleChild:
233     case Mle::kRoleRouter:
234         if (mLastTimeSyncReceived.GetValue() == 0)
235         {
236             // Haven't yet received any time sync
237             networkTimeStatus = OT_NETWORK_TIME_UNSYNCHRONIZED;
238             otLogInfoCore("Time sync status UNSYNCHRONIZED as mLastTimeSyncReceived:0");
239         }
240         else if (timeSyncLastSyncMs > resyncNeededThresholdMs)
241         {
242             // The device hasn’t received time sync for more than two periods time.
243             networkTimeStatus = OT_NETWORK_TIME_RESYNC_NEEDED;
244             otLogInfoCore("Time sync status RESYNC_NEEDED as timeSyncLastSyncMs:%u > resyncNeededThresholdMs:%u",
245                           timeSyncLastSyncMs, resyncNeededThresholdMs);
246         }
247         else
248         {
249             // Schedule a check 1 millisecond after two periods of time
250             OT_ASSERT(resyncNeededThresholdMs >= timeSyncLastSyncMs);
251             mTimer.Start(resyncNeededThresholdMs - timeSyncLastSyncMs + 1);
252             otLogInfoCore("Time sync status SYNCHRONIZED");
253         }
254         break;
255 
256     case Mle::kRoleLeader:
257         otLogInfoCore("Time sync status SYNCHRONIZED as role:LEADER");
258         break;
259     }
260 
261     if (networkTimeStatus != mCurrentStatus || aTimeUpdated)
262     {
263         mCurrentStatus = networkTimeStatus;
264 
265         NotifyTimeSyncCallback();
266     }
267 }
268 
269 } // namespace ot
270 
271 #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
272