blob: 29fc998b95ae64ade6493e5ef46b2136b291dbd6 [file] [log] [blame]
/*
* Copyright (c) 2020-2021 Project CHIP Authors
* 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 implements the CHIP reliable message protocol.
*
*/
#include <inttypes.h>
#include <messaging/ReliableMessageMgr.h>
#include <messaging/ErrorCategory.h>
#include <messaging/ExchangeMessageDispatch.h>
#include <messaging/Flags.h>
#include <messaging/ReliableMessageContext.h>
#include <support/BitFlags.h>
#include <support/CHIPFaultInjection.h>
#include <support/CodeUtils.h>
#include <support/logging/CHIPLogging.h>
namespace chip {
namespace Messaging {
ReliableMessageMgr::RetransTableEntry::RetransTableEntry() : rc(nullptr), nextRetransTimeTick(0), sendCount(0) {}
ReliableMessageMgr::ReliableMessageMgr(BitMapObjectPool<ExchangeContext, CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS> & contextPool) :
mContextPool(contextPool), mSystemLayer(nullptr), mSessionMgr(nullptr), mCurrentTimerExpiry(0),
mTimerIntervalShift(CHIP_CONFIG_RMP_TIMER_DEFAULT_PERIOD_SHIFT)
{}
ReliableMessageMgr::~ReliableMessageMgr() {}
void ReliableMessageMgr::Init(chip::System::Layer * systemLayer, SecureSessionMgr * sessionMgr)
{
mSystemLayer = systemLayer;
mSessionMgr = sessionMgr;
mTimeStampBase = System::Timer::GetCurrentEpoch();
mCurrentTimerExpiry = 0;
}
void ReliableMessageMgr::Shutdown()
{
StopTimer();
mSystemLayer = nullptr;
mSessionMgr = nullptr;
// Clear the retransmit table
for (RetransTableEntry & rEntry : mRetransTable)
{
ClearRetransTable(rEntry);
}
}
uint64_t ReliableMessageMgr::GetTickCounterFromTimePeriod(uint64_t period)
{
return (period >> mTimerIntervalShift);
}
uint64_t ReliableMessageMgr::GetTickCounterFromTimeDelta(uint64_t newTime)
{
return GetTickCounterFromTimePeriod(newTime - mTimeStampBase);
}
#if defined(RMP_TICKLESS_DEBUG)
void ReliableMessageMgr::TicklessDebugDumpRetransTable(const char * log)
{
ChipLogDetail(ExchangeManager, log);
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.rc)
{
ChipLogDetail(ExchangeManager, "EC:%04" PRIX16 " MsgId:%08" PRIX32 " NextRetransTimeCtr:%04" PRIX16, entry.rc,
entry.msgId, entry.nextRetransTimeTick);
}
}
}
#else
void ReliableMessageMgr::TicklessDebugDumpRetransTable(const char * log)
{
return;
}
#endif // RMP_TICKLESS_DEBUG
void ReliableMessageMgr::ExecuteActions()
{
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExecuteActions");
#endif
ExecuteForAllContext([](ReliableMessageContext * rc) {
if (rc->IsAckPending())
{
if (0 == rc->mNextAckTimeTick)
{
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExecuteActions sending ACK");
#endif
// Send the Ack in a SecureChannel::StandaloneAck message
rc->SendStandaloneAckMessage();
rc->SetAckPending(false);
}
}
});
TicklessDebugDumpRetransTable("ReliableMessageMgr::ExecuteActions Dumping mRetransTable entries before processing");
// Retransmit / cancel anything in the retrans table whose retrans timeout
// has expired
for (RetransTableEntry & entry : mRetransTable)
{
ReliableMessageContext * rc = entry.rc;
CHIP_ERROR err = CHIP_NO_ERROR;
if (!rc || entry.nextRetransTimeTick != 0)
continue;
if (entry.retainedBuf.IsNull())
{
// We generally try to prevent entries with a null buffer being in a table, but it could happen
// if the message dispatch (which is supposed to fill in the buffer) fails to do so _and_ returns
// success (so its caller doesn't clear out the bogus table entry).
//
// If that were to happen, we would crash in the code below. Guard against it, just in case.
ClearRetransTable(entry);
continue;
}
uint8_t sendCount = entry.sendCount;
uint32_t msgId = entry.retainedBuf.GetMsgId();
if (sendCount == CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS)
{
err = CHIP_ERROR_MESSAGE_NOT_ACKNOWLEDGED;
ChipLogError(ExchangeManager, "Failed to Send CHIP MsgId:%08" PRIX32 " sendCount: %" PRIu8 " max retries: %d", msgId,
sendCount, CHIP_CONFIG_RMP_DEFAULT_MAX_RETRANS);
// Remove from Table
ClearRetransTable(entry);
}
// Resend from Table (if the operation fails, the entry is cleared)
if (err == CHIP_NO_ERROR)
err = SendFromRetransTable(&entry);
if (err == CHIP_NO_ERROR)
{
// If the retransmission was successful, update the passive timer
entry.nextRetransTimeTick = static_cast<uint16_t>(rc->GetActiveRetransmitTimeoutTick());
#if !defined(NDEBUG)
ChipLogDetail(ExchangeManager, "Retransmit MsgId:%08" PRIX32 " Send Cnt %d", msgId, entry.sendCount);
#endif
}
}
TicklessDebugDumpRetransTable("ReliableMessageMgr::ExecuteActions Dumping mRetransTable entries after processing");
}
static void TickProceed(uint16_t & time, uint64_t ticks)
{
if (time >= ticks)
{
time = static_cast<uint16_t>(time - ticks);
}
else
{
time = 0;
}
}
void ReliableMessageMgr::ExpireTicks()
{
uint64_t now = System::Timer::GetCurrentEpoch();
// Number of full ticks elapsed since last timer processing. We always round down
// to the previous tick. If we are between tick boundaries, the extra time since the
// last virtual tick is not accounted for here (it will be accounted for when resetting
// the ReliableMessageProtocol timer)
uint64_t deltaTicks = GetTickCounterFromTimeDelta(now);
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExpireTicks at %" PRIu64 ", %" PRIu64 ", %u", now, mTimeStampBase,
deltaTicks);
#endif
ExecuteForAllContext([deltaTicks](ReliableMessageContext * rc) {
if (rc->IsAckPending())
{
// Decrement counter of Ack timestamp by the elapsed timer ticks
TickProceed(rc->mNextAckTimeTick, deltaTicks);
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExpireTicks set mNextAckTimeTick to %u", rc->mNextAckTimeTick);
#endif
}
});
for (RetransTableEntry & entry : mRetransTable)
{
ReliableMessageContext * rc = entry.rc;
if (rc)
{
// Decrement Retransmit timeout by elapsed timeticks
TickProceed(entry.nextRetransTimeTick, deltaTicks);
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExpireTicks set nextRetransTimeTick to %u",
entry.nextRetransTimeTick);
#endif
} // rc entry is allocated
}
// Re-Adjust the base time stamp to the most recent tick boundary
mTimeStampBase += (deltaTicks << mTimerIntervalShift);
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::ExpireTicks mTimeStampBase to %" PRIu64, mTimeStampBase);
#endif
}
void ReliableMessageMgr::Timeout(System::Layer * aSystemLayer, void * aAppState, System::Error aError)
{
ReliableMessageMgr * manager = reinterpret_cast<ReliableMessageMgr *>(aAppState);
VerifyOrDie((aSystemLayer != nullptr) && (manager != nullptr));
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::Timeout\n");
#endif
// Make sure all tick counts are sync'd to the current time
manager->ExpireTicks();
// Execute any actions that are due this tick
manager->ExecuteActions();
// Calculate next physical wakeup
manager->StartTimer();
}
CHIP_ERROR ReliableMessageMgr::AddToRetransTable(ReliableMessageContext * rc, RetransTableEntry ** rEntry)
{
bool added = false;
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrDie(rc != nullptr && !rc->IsOccupied());
for (RetransTableEntry & entry : mRetransTable)
{
// Check the exchContext pointer for finding an empty slot in Table
if (!entry.rc)
{
// Expire any virtual ticks that have expired so all wakeup sources reflect the current time
ExpireTicks();
entry.rc = rc;
entry.sendCount = 0;
entry.retainedBuf = EncryptedPacketBufferHandle();
*rEntry = &entry;
// Increment the reference count
rc->RetainContext();
rc->SetOccupied(true);
added = true;
break;
}
}
if (!added)
{
ChipLogError(ExchangeManager, "mRetransTable Already Full");
err = CHIP_ERROR_RETRANS_TABLE_FULL;
}
return err;
}
void ReliableMessageMgr::StartRetransmision(RetransTableEntry * entry)
{
VerifyOrReturn(entry != nullptr && entry->rc != nullptr,
ChipLogError(ExchangeManager, "StartRetransmission was called for invalid entry"));
entry->nextRetransTimeTick = static_cast<uint16_t>(entry->rc->GetInitialRetransmitTimeoutTick() +
GetTickCounterFromTimeDelta(System::Timer::GetCurrentEpoch()));
// Check if the timer needs to be started and start it.
StartTimer();
}
void ReliableMessageMgr::PauseRetransmision(ReliableMessageContext * rc, uint32_t PauseTimeMillis)
{
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.rc == rc)
{
entry.nextRetransTimeTick = static_cast<uint16_t>(entry.nextRetransTimeTick + (PauseTimeMillis >> mTimerIntervalShift));
break;
}
}
}
void ReliableMessageMgr::ResumeRetransmision(ReliableMessageContext * rc)
{
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.rc == rc)
{
entry.nextRetransTimeTick = 0;
break;
}
}
}
bool ReliableMessageMgr::CheckAndRemRetransTable(ReliableMessageContext * rc, uint32_t ackMsgId)
{
for (RetransTableEntry & entry : mRetransTable)
{
if ((entry.rc == rc) && entry.retainedBuf.GetMsgId() == ackMsgId)
{
// Clear the entry from the retransmision table.
ClearRetransTable(entry);
#if !defined(NDEBUG)
ChipLogDetail(ExchangeManager, "Rxd Ack; Removing MsgId:%08" PRIX32 " from Retrans Table", ackMsgId);
#endif
return true;
}
}
return false;
}
CHIP_ERROR ReliableMessageMgr::SendFromRetransTable(RetransTableEntry * entry)
{
CHIP_ERROR err = CHIP_NO_ERROR;
ReliableMessageContext * rc = entry->rc;
uint32_t msgId = 0; // Not actually used unless we reach the
// line that initializes it properly.
VerifyOrReturnError(rc != nullptr, err);
// Now that we know this is a valid entry, grab the message id from the
// retained buffer. We need to do that now, because we're about to hand it
// over to someone else, and on failure it will no longer be available.
msgId = entry->retainedBuf.GetMsgId();
const ExchangeMessageDispatch * dispatcher = rc->GetExchangeContext()->GetMessageDispatch();
VerifyOrExit(dispatcher != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
err = dispatcher->SendPreparedMessage(rc->GetExchangeContext()->GetSecureSession(), entry->retainedBuf);
SuccessOrExit(err);
// Update the counters
entry->sendCount++;
exit:
if (err != CHIP_NO_ERROR)
{
// Remove from table
ChipLogError(ExchangeManager, "Crit-err %ld when sending CHIP MsgId:%08" PRIX32 ", send tries: %d", long(err), msgId,
entry->sendCount);
ClearRetransTable(*entry);
}
return err;
}
void ReliableMessageMgr::ClearRetransTable(ReliableMessageContext * rc)
{
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.rc == rc)
{
// Clear the retransmit table entry.
ClearRetransTable(entry);
}
}
}
void ReliableMessageMgr::ClearRetransTable(RetransTableEntry & rEntry)
{
if (rEntry.rc)
{
VerifyOrDie(rEntry.rc->IsOccupied() == true);
// Expire any virtual ticks that have expired so all wakeup sources reflect the current time
ExpireTicks();
rEntry.rc->ReleaseContext();
rEntry.rc->SetOccupied(false);
rEntry.rc = nullptr;
// Clear all other fields
rEntry = RetransTableEntry();
// Schedule next physical wakeup, unless shutting down
if (mSystemLayer)
StartTimer();
}
}
void ReliableMessageMgr::FailRetransTableEntries(ReliableMessageContext * rc, CHIP_ERROR err)
{
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.rc == rc)
{
// Remove the entry from the retransmission table.
ClearRetransTable(entry);
}
}
}
void ReliableMessageMgr::StartTimer()
{
CHIP_ERROR res = CHIP_NO_ERROR;
uint64_t nextWakeTimeTick = UINT64_MAX;
bool foundWake = false;
// When do we need to next wake up to send an ACK?
ExecuteForAllContext([&nextWakeTimeTick, &foundWake](ReliableMessageContext * rc) {
if (rc->IsAckPending() && rc->mNextAckTimeTick < nextWakeTimeTick)
{
nextWakeTimeTick = rc->mNextAckTimeTick;
foundWake = true;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::StartTimer next ACK time %u", nextWakeTimeTick);
#endif
}
});
for (RetransTableEntry & entry : mRetransTable)
{
ReliableMessageContext * rc = entry.rc;
if (rc)
{
// When do we need to next wake up for ReliableMessageProtocol retransmit?
if (entry.nextRetransTimeTick < nextWakeTimeTick)
{
nextWakeTimeTick = entry.nextRetransTimeTick;
foundWake = true;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::StartTimer RetransTime %u", nextWakeTimeTick);
#endif
}
}
}
if (foundWake)
{
// Set timer for next tick boundary - subtract the elapsed time from the current tick
System::Timer::Epoch timerExpiryEpoch = (nextWakeTimeTick << mTimerIntervalShift) + mTimeStampBase;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::StartTimer wake at %" PRIu64 " ms (%" PRIu64 " %" PRIu64 ")",
timerExpiryEpoch, nextWakeTimeTick, mTimeStampBase);
#endif
if (timerExpiryEpoch != mCurrentTimerExpiry)
{
// If the tick boundary has expired in the past (delayed processing of event due to other system activity),
// expire the timer immediately
uint64_t now = System::Timer::GetCurrentEpoch();
uint64_t timerArmValue = (timerExpiryEpoch > now) ? timerExpiryEpoch - now : 0;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::StartTimer set timer for %" PRIu64, timerArmValue);
#endif
StopTimer();
res = mSystemLayer->StartTimer((uint32_t) timerArmValue, Timeout, this);
VerifyOrDieWithMsg(res == CHIP_NO_ERROR, ExchangeManager, "Cannot start ReliableMessageMgr::Timeout\n");
mCurrentTimerExpiry = timerExpiryEpoch;
#if defined(RMP_TICKLESS_DEBUG)
}
else
{
ChipLogDetail(ExchangeManager, "ReliableMessageMgr::StartTimer timer already set for %" PRIu64, timerExpiryEpoch);
#endif
}
}
else
{
#if defined(RMP_TICKLESS_DEBUG)
ChipLogDetail(ExchangeManager, "Not setting ReliableMessageProtocol timeout at %" PRIu64, System::Timer::GetCurrentEpoch());
#endif
StopTimer();
}
TicklessDebugDumpRetransTable("ReliableMessageMgr::StartTimer Dumping mRetransTable entries after setting wakeup times");
}
void ReliableMessageMgr::StopTimer()
{
mSystemLayer->CancelTimer(Timeout, this);
}
#if CHIP_CONFIG_TEST
int ReliableMessageMgr::TestGetCountRetransTable()
{
int count = 0;
for (RetransTableEntry & entry : mRetransTable)
{
ReliableMessageContext * rc = entry.rc;
if (rc)
count++;
}
return count;
}
#endif // CHIP_CONFIG_TEST
} // namespace Messaging
} // namespace chip