Timer: Modify timer implementation to store fire time (#1904)

This commit changes the `Timer` class to store the fire time instead
of start time and duration. This simplifies the `Timer` implementation
and reduces the size of a `Timer` instance. The `test_timer` unit test
is also updated to add new test cases to verify correct timer behavior
during 32-bit timer wrap.

This commit also simplifies the key rotation implementation in
`KeyManager` class. Since the key rotation time and guard time are
provided in hours unit and can span multiple days, the code tracks
number of hours since last key rotation in `mHoursSinceKeyRotation`
(using a one hour interval timer). This is then used to decide when/if
to change the key sequence.
This commit is contained in:
Abtin Keshavarzian
2017-06-16 09:28:52 -07:00
committed by Jonathan Hui
parent 8b64f524b5
commit f067d68f38
11 changed files with 170 additions and 139 deletions
+1 -1
View File
@@ -405,7 +405,7 @@ Message *Coap::CopyAndEnqueueMessage(const Message &aMessage, uint16_t aCopyLeng
if (mRetransmissionTimer.IsRunning())
{
// If timer is already running, check if it should be restarted with earlier fire time.
alarmFireTime = mRetransmissionTimer.Gett0() + mRetransmissionTimer.Getdt();
alarmFireTime = mRetransmissionTimer.GetFireTime();
if (aCoapMetadata.IsEarlier(alarmFireTime))
{
+13 -44
View File
@@ -69,7 +69,7 @@ void TimerScheduler::Add(Timer &aTimer)
for (cur = mHead; cur; cur = cur->mNext)
{
if (TimerCompare(aTimer, *cur))
if (IsStrictlyBefore(aTimer.mFireTime, cur->mFireTime))
{
if (prev)
{
@@ -126,18 +126,14 @@ exit:
void TimerScheduler::SetAlarm(void)
{
uint32_t now = otPlatAlarmGetNow();
uint32_t elapsed;
uint32_t remaining;
if (mHead == NULL)
{
otPlatAlarmStop(GetIp6()->GetInstance());
}
else
{
elapsed = now - mHead->mT0;
remaining = (mHead->mDt > elapsed) ? mHead->mDt - elapsed : 0;
uint32_t now = otPlatAlarmGetNow();
uint32_t remaining = IsStrictlyBefore(now, mHead->mFireTime) ? (mHead->mFireTime - now) : 0;
otPlatAlarmStartAt(GetIp6()->GetInstance(), now, remaining);
}
@@ -146,21 +142,17 @@ void TimerScheduler::SetAlarm(void)
extern "C" void otPlatAlarmFired(otInstance *aInstance)
{
otLogFuncEntry();
aInstance->mIp6.mTimerScheduler.FireTimers();
aInstance->mIp6.mTimerScheduler.ProcessTimers();
otLogFuncExit();
}
void TimerScheduler::FireTimers(void)
void TimerScheduler::ProcessTimers(void)
{
uint32_t now = otPlatAlarmGetNow();
uint32_t elapsed;
Timer *timer = mHead;
if (timer)
{
elapsed = now - timer->mT0;
if (elapsed >= timer->mDt)
if (!IsStrictlyBefore(otPlatAlarmGetNow(), timer->mFireTime))
{
Remove(*timer);
timer->Fired();
@@ -181,39 +173,16 @@ Ip6::Ip6 *TimerScheduler::GetIp6(void)
return Ip6::Ip6FromTimerScheduler(this);
}
bool TimerScheduler::TimerCompare(const Timer &aTimerA, const Timer &aTimerB)
bool TimerScheduler::IsStrictlyBefore(uint32_t aTimeA, uint32_t aTimeB)
{
uint32_t now = otPlatAlarmGetNow();
uint32_t elapsedA = now - aTimerA.mT0;
uint32_t elapsedB = now - aTimerB.mT0;
bool retval = false;
uint32_t diff = aTimeA - aTimeB;
if (aTimerA.mDt >= elapsedA && aTimerB.mDt >= elapsedB)
{
uint32_t remainingA = aTimerA.mDt - elapsedA;
uint32_t remainingB = aTimerB.mDt - elapsedB;
// Three cases:
// 1) aTimeA is before aTimeB => Difference is negative (last bit of difference is set) => Returning true.
// 2) aTimeA is same as aTimeB => Difference is zero (last bit of difference is clear) => Returning false.
// 3) aTimeA is after aTimeB => Difference is positive (last bit of difference is clear) => Returning false.
if (remainingA < remainingB)
{
retval = true;
}
}
else if (aTimerA.mDt < elapsedA && aTimerB.mDt >= elapsedB)
{
retval = true;
}
else if (aTimerA.mDt < elapsedA && aTimerB.mDt < elapsedB)
{
uint32_t expiredByA = elapsedA - aTimerA.mDt;
uint32_t expiredByB = elapsedB - aTimerB.mDt;
if (expiredByB < expiredByA)
{
retval = true;
}
}
return retval;
return ((diff & (1UL << 31)) != 0);
}
} // namespace ot
+39 -32
View File
@@ -40,6 +40,7 @@
#include <openthread/types.h>
#include <openthread/platform/alarm.h>
#include "common/debug.hpp"
#include "common/tasklet.hpp"
namespace ot {
@@ -90,12 +91,17 @@ public:
void Remove(Timer &aTimer);
/**
* This method processes all running timers.
*
* @param[in] aContext A pointer to arbitrary context information.
* This method processes the running timers.
*
*/
void FireTimers(void);
void ProcessTimers(void);
private:
/**
* This method sets the platform alarm based on timer at front of the list.
*
*/
void SetAlarm(void);
/**
* This method returns the pointer to the parent Ip6 structure.
@@ -105,20 +111,20 @@ public:
*/
Ip6::Ip6 *GetIp6(void);
private:
void SetAlarm(void);
/**
* This method compares two timers and returns a value to indicate
* which timer will fire earlier.
* This static method compares two times and indicates if the first time is strictly before (earlier) than the
* second time.
*
* @param[in] aTimerA The first timer for comparison.
* @param[in] aTimerB The second timer for comparison.
* This method requires that the difference between the two given times to be smaller than kMaxDt.
*
* @param[in] aTimerA The first time for comparison.
* @param[in] aTimerB The second time for comparison.
*
* @returns TRUE if aTimeA is before aTimeB.
* @returns FALSE if aTimeA is same time or after aTimeB.
*
* @returns true if aTimerA will fire before aTimerB.
* @returns false if aTimerA will fire at the same time or after aTimerB.
*/
static bool TimerCompare(const Timer &aTimerA, const Timer &aTimerB);
static bool IsStrictlyBefore(uint32_t aTimeA, uint32_t aTimeB);
Timer *mHead;
};
@@ -132,6 +138,12 @@ class Timer
friend class TimerScheduler;
public:
enum
{
kMaxDt = (1UL << 31) - 1, //< Maximum permitted value for parameter `aDt` in `Start` and `StartAt` method.
};
/**
* This function pointer is called when the timer expires.
*
@@ -151,32 +163,24 @@ public:
mScheduler(aScheduler),
mHandler(aHandler),
mContext(aContext),
mT0(0),
mDt(0),
mFireTime(0),
mNext(this) {
}
/**
* This method returns the start time in milliseconds for the timer.
* This method returns the fire time of the timer.
*
* @returns The start time in milliseconds.
* @returns The fire time in milliseconds.
*
*/
uint32_t Gett0(void) const { return mT0; }
/**
* This method returns the delta time in milliseconds for the timer.
*
* @returns The delta time.
*
*/
uint32_t Getdt(void) const { return mDt; }
uint32_t GetFireTime(void) const { return mFireTime; }
/**
* This method indicates whether or not the timer instance is running.
*
* @retval TRUE If the timer is running.
* @retval FALSE If the timer is not running.
*
*/
bool IsRunning(void) const { return (mNext != this); }
@@ -184,16 +188,20 @@ public:
* This method schedules the timer to fire a @p dt milliseconds from now.
*
* @param[in] aDt The expire time in milliseconds from now.
* (aDt must be smaller than or equal to kMaxDt).
*
*/
void Start(uint32_t aDt) { StartAt(GetNow(), aDt); }
/**
* This method schedules the timer to fire at @p dt milliseconds from @p t0.
* This method schedules the timer to fire at @p aDt milliseconds from @p aT0.
*
* @param[in] aT0 The start time in milliseconds.
* @param[in] aDt The expire time in milliseconds from @p t0.
* @param[in] aDt The expire time in milliseconds from @p aT0.
* (aDt must be smaller than or equal to kMaxDt).
*
*/
void StartAt(uint32_t aT0, uint32_t aDt) { mT0 = aT0; mDt = aDt; mScheduler.Add(*this); }
void StartAt(uint32_t aT0, uint32_t aDt) { assert(aDt <= kMaxDt); mFireTime = aT0 + aDt; mScheduler.Add(*this); }
/**
* This method stops the timer.
@@ -247,8 +255,7 @@ private:
TimerScheduler &mScheduler;
Handler mHandler;
void *mContext;
uint32_t mT0;
uint32_t mDt;
uint32_t mFireTime;
Timer *mNext;
};
+1 -1
View File
@@ -169,7 +169,7 @@ Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetad
if (mRetransmissionTimer.IsRunning())
{
// If timer is already running, check if it should be restarted with earlier fire time.
nextTransmissionTime = mRetransmissionTimer.Gett0() + mRetransmissionTimer.Getdt();
nextTransmissionTime = mRetransmissionTimer.GetFireTime();
if (aQueryMetadata.IsEarlier(nextTransmissionTime))
{
+1 -1
View File
@@ -192,7 +192,7 @@ void Mpl::AddBufferedMessage(Message &aMessage, uint16_t aSeedId, uint8_t aSeque
if (mRetransmissionTimer.IsRunning())
{
// If timer is already running, check if it should be restarted with earlier fire time.
nextTransmissionTime = mRetransmissionTimer.Gett0() + mRetransmissionTimer.Getdt();
nextTransmissionTime = mRetransmissionTimer.GetFireTime();
if (messageMetadata.IsEarlier(nextTransmissionTime))
{
+4 -2
View File
@@ -51,6 +51,7 @@ namespace ot {
DataPollManager::DataPollManager(MeshForwarder &aMeshForwarder):
mMeshForwarder(aMeshForwarder),
mTimer(aMeshForwarder.GetNetif().GetIp6().mTimerScheduler, &DataPollManager::HandlePollTimer, this),
mTimerStartTime(0),
mExternalPollPeriod(0),
mPollPeriod(0),
mEnabled(false),
@@ -327,11 +328,12 @@ void DataPollManager::ScheduleNextPoll(PollPeriodSelector aPollPeriodSelector)
if (mTimer.IsRunning())
{
mTimer.StartAt(mTimer.Gett0(), mPollPeriod);
mTimer.StartAt(mTimerStartTime, mPollPeriod);
}
else
{
mTimer.Start(mPollPeriod);
mTimerStartTime = Timer::GetNow();
mTimer.StartAt(mTimerStartTime, mPollPeriod);
}
}
+1
View File
@@ -222,6 +222,7 @@ private:
MeshForwarder &mMeshForwarder;
Timer mTimer;
uint32_t mTimerStartTime;
uint32_t mExternalPollPeriod;
uint32_t mPollPeriod;
+25 -23
View File
@@ -56,6 +56,7 @@ KeyManager::KeyManager(ThreadNetif &aThreadNetif):
mMleFrameCounter(0),
mStoredMacFrameCounter(0),
mStoredMleFrameCounter(0),
mHoursSinceKeyRotation(0),
mKeyRotationTime(kDefaultKeyRotationTime),
mKeySwitchGuardTime(kDefaultKeySwitchGuardTime),
mKeySwitchGuardEnabled(false),
@@ -68,7 +69,7 @@ KeyManager::KeyManager(ThreadNetif &aThreadNetif):
void KeyManager::Start(void)
{
mKeySwitchGuardEnabled = false;
mKeyRotationTimer.Start(Timer::HoursToMsec(mKeyRotationTime));
StartKeyRotationTimer();
}
void KeyManager::Stop(void)
@@ -170,25 +171,7 @@ void KeyManager::SetCurrentKeySequence(uint32_t aKeySequence)
mKeyRotationTimer.IsRunning() &&
mKeySwitchGuardEnabled)
{
uint32_t now = Timer::GetNow();
uint32_t guardStartTimestamp = mKeyRotationTimer.Gett0();
uint32_t guardEndTimestamp = guardStartTimestamp + Timer::HoursToMsec(mKeySwitchGuardTime);
// Check for timer overflow
if (guardEndTimestamp < mKeyRotationTimer.Gett0())
{
if ((now > guardStartTimestamp) || (now < guardEndTimestamp))
{
ExitNow();
}
}
else
{
if ((now > guardStartTimestamp) && (now < guardEndTimestamp))
{
ExitNow();
}
}
VerifyOrExit(mHoursSinceKeyRotation < mKeySwitchGuardTime);
}
mKeySequence = aKeySequence;
@@ -200,7 +183,7 @@ void KeyManager::SetCurrentKeySequence(uint32_t aKeySequence)
if (mKeyRotationTimer.IsRunning())
{
mKeySwitchGuardEnabled = true;
mKeyRotationTimer.Start(Timer::HoursToMsec(mKeyRotationTime));
StartKeyRotationTimer();
}
mNetif.SetStateChangedFlags(OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER);
@@ -252,7 +235,6 @@ otError KeyManager::SetKeyRotation(uint32_t aKeyRotation)
otError result = OT_ERROR_NONE;
VerifyOrExit(aKeyRotation >= static_cast<uint32_t>(kMinKeyRotationTime), result = OT_ERROR_INVALID_ARGS);
VerifyOrExit(aKeyRotation <= static_cast<uint32_t>(kMaxKeyRotationTime), result = OT_ERROR_INVALID_ARGS);
mKeyRotationTime = aKeyRotation;
@@ -260,6 +242,12 @@ exit:
return result;
}
void KeyManager::StartKeyRotationTimer(void)
{
mHoursSinceKeyRotation = 0;
mKeyRotationTimer.Start(kOneHourIntervalInMsec);
}
void KeyManager::HandleKeyRotationTimer(void *aContext)
{
static_cast<KeyManager *>(aContext)->HandleKeyRotationTimer();
@@ -267,7 +255,21 @@ void KeyManager::HandleKeyRotationTimer(void *aContext)
void KeyManager::HandleKeyRotationTimer(void)
{
SetCurrentKeySequence(mKeySequence + 1);
mHoursSinceKeyRotation++;
// Order of operations below is important. We should restart the timer (from
// last fire time for one hour interval) before potentially calling
// `SetCurrentKeySequence()`. `SetCurrentKeySequence()` uses the fact that
// timer is running to decide to check for the guard time and to reset the
// rotation timer (and the `mHoursSinceKeyRotation`) if it updates the key
// sequence.
mKeyRotationTimer.StartAt(mKeyRotationTimer.GetFireTime(), kOneHourIntervalInMsec);
if (mHoursSinceKeyRotation >= mKeyRotationTime)
{
SetCurrentKeySequence(mKeySequence + 1);
}
}
} // namespace ot
+4 -2
View File
@@ -272,7 +272,7 @@ public:
* This method sets the KeyRotation time.
*
* The KeyRotation time is the time interval after witch security key will be automatically rotated.
* It's value shall be in range [kMinKeyRotationTime, kMaxKeyRotationTime].
* Its value shall be larger than or equal to kMinKeyRotationTime.
*
* @param[in] aKeyRotation The KeyRotation value in hours.
*
@@ -328,14 +328,15 @@ private:
enum
{
kMinKeyRotationTime = 1,
kMaxKeyRotationTime = 0xffffffff / 3600u / 1000u,
kDefaultKeyRotationTime = 672,
kDefaultKeySwitchGuardTime = 624,
kMacKeyOffset = 16,
kOneHourIntervalInMsec = 3600u * 1000u,
};
otError ComputeKey(uint32_t aKeySequence, uint8_t *aKey);
void StartKeyRotationTimer(void);
static void HandleKeyRotationTimer(void *aContext);
void HandleKeyRotationTimer(void);
@@ -353,6 +354,7 @@ private:
uint32_t mStoredMacFrameCounter;
uint32_t mStoredMleFrameCounter;
uint32_t mHoursSinceKeyRotation;
uint32_t mKeyRotationTime;
uint32_t mKeySwitchGuardTime;
bool mKeySwitchGuardEnabled;
+1 -1
View File
@@ -1975,7 +1975,7 @@ otError Mle::AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestinat
if (mDelayedResponseTimer.IsRunning())
{
// If timer is already running, check if it should be restarted with earlier fire time.
alarmFireTime = mDelayedResponseTimer.Gett0() + mDelayedResponseTimer.Getdt();
alarmFireTime = mDelayedResponseTimer.GetFireTime();
if (delayedResponse.IsEarlier(alarmFireTime))
{
+80 -32
View File
@@ -103,6 +103,8 @@ int TestOneTimer(void)
InitTestTimer();
InitCounters();
printf("TestOneTimer() ");
sNow = kTimeT0;
timer.Start(kTimerInterval);
@@ -130,12 +132,12 @@ int TestOneTimer(void)
sNow = 0 - (kTimerInterval - 2);
timer.Start(kTimerInterval);
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart] == 1, "TestOneTimer: Start CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop] == 0, "TestOneTimer: Stop CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler] == 0, "TestOneTimer: Handler CallCount Failed.\n");
VerifyOrQuit(sPlatT0 == 0 - (kTimerInterval - 2) && sPlatDt == 10, "TestOneTimer: Start params Failed.\n");
VerifyOrQuit(timer.IsRunning(), "TestOneTimer: Timer running Failed.\n");
VerifyOrQuit(sTimerOn, "TestOneTimer: Platform Timer State Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart] == 1, "TestOneTimer: Start CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop] == 0, "TestOneTimer: Stop CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler] == 0, "TestOneTimer: Handler CallCount Failed.\n");
VerifyOrQuit(sPlatT0 == 0 - (kTimerInterval - 2) && sPlatDt == 10, "TestOneTimer: Start params Failed.\n");
VerifyOrQuit(timer.IsRunning(), "TestOneTimer: Timer running Failed.\n");
VerifyOrQuit(sTimerOn, "TestOneTimer: Platform Timer State Failed.\n");
sNow += kTimerInterval;
@@ -205,13 +207,19 @@ int TestOneTimer(void)
VerifyOrQuit(timer.IsRunning() == false, "TestOneTimer: Timer running Failed.\n");
VerifyOrQuit(sTimerOn == false, "TestOneTimer: Platform Timer State Failed.\n");
printf(" --> PASSED\n");
return 0;
}
/**
* Test the TimerScheduler's behavior of ten timers started and fired.
*
* `aTimeShift` is added to the t0 and trigger times for all timers. It can be used to check the ten timer behavior
* at different start time (e.g., around a 32-bit wrap).
*/
int TestTenTimers(void)
static void TenTimers(uint32_t aTimeShift)
{
const uint32_t kNumTimers = 10;
const uint32_t kNumTriggers = 7;
@@ -232,27 +240,27 @@ int TestTenTimers(void)
{
20,
100,
(0 - kTimeT0[2]),
(ot::Timer::kMaxDt - kTimeT0[2]),
100000,
1000000,
10,
(1000 - kTimeT0[6]),
ot::Timer::kMaxDt,
200,
200,
200
};
// Expected timer fire order
// timer # Trigger time
// 5 1014
// 0 1020
// 1 1100
// 7 1206
// 8 1207
// 9 1208
// 3 101002
// 4 1001003
// 2 0 <timer wrapped>
// 6 1000 <timer wrapped>
// timer # Trigger time
// 5 1014
// 0 1020
// 1 1100
// 7 1206
// 8 1207
// 9 1208
// 3 101002
// 4 1001003
// 2 kMaxDt
// 6 kMaxDt + 1005
const uint32_t kTriggerTimes[kNumTriggers] =
{
1014,
@@ -260,9 +268,8 @@ int TestTenTimers(void)
1100,
1207,
101004,
/* timer wrap here */
2,
1000
ot::Timer::kMaxDt,
ot::Timer::kMaxDt + kTimeT0[6]
};
// Expected timers fired by each kTriggerTimes[] value
// Trigger # Timers Fired
@@ -341,9 +348,23 @@ int TestTenTimers(void)
ot::Timer timer7(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[7]);
ot::Timer timer8(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[8]);
ot::Timer timer9(aInstance.mIp6.mTimerScheduler, TestTimerHandler, &timerContextHandleCounter[9]);
ot::Timer *timers[kNumTimers] = {&timer0, &timer1, &timer2, &timer3, &timer4, &timer5, &timer6, &timer7, &timer8, &timer9};
ot::Timer *timers[kNumTimers] =
{
&timer0,
&timer1,
&timer2,
&timer3,
&timer4,
&timer5,
&timer6,
&timer7,
&timer8,
&timer9
};
size_t i;
printf("TestTenTimer() with aTimeShift=%-10u ", aTimeShift);
// Start the Ten timers.
InitTestTimer();
@@ -351,17 +372,18 @@ int TestTenTimers(void)
for (i = 0; i < kNumTimers ; i++)
{
sNow = kTimeT0[i];
sNow = kTimeT0[i] + aTimeShift;
timers[i]->Start(kTimerInterval[i]);
}
// given the order in which timers are started, the TimerScheduler should call otPlatAlarmStartAt 2 times.
// one for timer[0] and one for timer[5] which will supercede timer[0].
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart] == 2, "TestTenTimer: Start CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop] == 0, "TestTenTimer: Stop CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler] == 0, "TestTenTimer: Handler CallCount Failed.\n");
VerifyOrQuit(sPlatT0 == kTimeT0[5] && sPlatDt == kTimerInterval[5], "TestTenTimer: Start params Failed.\n");
VerifyOrQuit(sTimerOn, "TestTenTimer: Platform Timer State Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStart] == 2, "TestTenTimer: Start CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexAlarmStop] == 0, "TestTenTimer: Stop CallCount Failed.\n");
VerifyOrQuit(sCallCount[kCallCountIndexTimerHandler] == 0, "TestTenTimer: Handler CallCount Failed.\n");
VerifyOrQuit(sPlatT0 == kTimeT0[5] + aTimeShift, "TestTenTimer: Start params Failed.\n");
VerifyOrQuit(sPlatDt == kTimerInterval[5], "TestTenTimer: Start params Failed.\n");
VerifyOrQuit(sTimerOn, "TestTenTimer: Platform Timer State Failed.\n");
for (i = 0 ; i < kNumTimers ; i++)
{
@@ -372,7 +394,7 @@ int TestTenTimers(void)
for (size_t trigger = 0 ; trigger < kNumTriggers ; trigger++)
{
sNow = kTriggerTimes[trigger];
sNow = kTriggerTimes[trigger] + aTimeShift;
do
{
@@ -397,7 +419,10 @@ int TestTenTimers(void)
for (i = 0 ; i < kNumTimers ; i++)
{
VerifyOrQuit(timers[i]->IsRunning() == kTimerStateAfterTrigger[trigger][i], "TestTenTimer: Timer running Failed.\n");
VerifyOrQuit(
timers[i]->IsRunning() == kTimerStateAfterTrigger[trigger][i],
"TestTenTimer: Timer running Failed.\n"
);
}
}
@@ -406,6 +431,29 @@ int TestTenTimers(void)
VerifyOrQuit(timerContextHandleCounter[i] == 1, "TestTenTimer: Timer context counter Failed.\n");
}
printf("--> PASSED\n");
}
int TestTenTimers(void)
{
// Time shift to change the start/fire time of ten timers.
const uint32_t kTimeShift[] =
{
0,
100000U,
0U - 1U,
0U - 1100U,
ot::Timer::kMaxDt,
ot::Timer::kMaxDt + 1020U,
};
size_t i;
for (i = 0; i < sizeof(kTimeShift) / sizeof(kTimeShift[0]); i++)
{
TenTimers(kTimeShift[i]);
}
return 0;
}