blob: ce963f217c4ad48c940d66c96e901dc41a8fa0d4 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* 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 implements the ChipExchangeManager class.
*
*/
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <stddef.h>
#include <core/CHIPCore.h>
#include <core/CHIPEncoding.h>
#include <message/CHIPBinding.h>
#include <message/CHIPExchangeMgr.h>
#include <message/CHIPSecurityMgr.h>
#include <protocols/CHIPProtocols.h>
#include <protocols/common/CommonProtocol.h>
#include <protocols/security/CHIPSecurity.h>
#include <support/CHIPFaultInjection.h>
#include <support/CodeUtils.h>
#include <support/RandUtils.h>
#include <support/logging/CHIPLogging.h>
#include <system/SystemStats.h>
#include <system/SystemTimer.h>
namespace chip {
using namespace chip;
using namespace chip::Protocols;
using namespace chip::Encoding;
/**
* Constructor for the ChipExchangeManager class.
* It sets the state to kState_NotInitialized.
*
* @note
* The class must be initialized via ChipExchangeManager::Init()
* prior to use.
*
*/
ChipExchangeManager::ChipExchangeManager()
{
State = kState_NotInitialized;
}
/**
* Initialize the ChipExchangeManager object. Within the lifetime
* of this instance, this method is invoked once after object
* construction until a call to Shutdown is made to terminate the
* instance.
*
* @param[in] msgLayer A pointer to the ChipMessageLayer object.
*
* @retval #CHIP_ERROR_INCORRECT_STATE If the state is not equal to
* kState_NotInitialized.
* @retval #CHIP_NO_ERROR On success.
*
*/
CHIP_ERROR ChipExchangeManager::Init(ChipMessageLayer * msgLayer)
{
if (State != kState_NotInitialized)
return CHIP_ERROR_INCORRECT_STATE;
MessageLayer = msgLayer;
FabricState = msgLayer->FabricState;
NextExchangeId = GetRandU16();
memset(ContextPool, 0, sizeof(ContextPool));
mContextsInUse = 0;
InitBindingPool();
memset(UMHandlerPool, 0, sizeof(UMHandlerPool));
OnExchangeContextChanged = nullptr;
msgLayer->ExchangeMgr = this;
msgLayer->OnMessageReceived = HandleMessageReceived;
msgLayer->OnAcceptError = HandleAcceptError;
mRMPTimerInterval = CHIP_CONFIG_RMP_TIMER_DEFAULT_PERIOD; // CRMP Timer tick period
memset(RetransTable, 0, sizeof(RetransTable));
mRMPTimeStampBase = System::Timer::GetCurrentEpoch();
mRMPCurrentTimerExpiry = 0;
State = kState_Initialized;
return CHIP_NO_ERROR;
}
/**
* Shutdown the ChipExchangeManager. This terminates this instance
* of the object and releases all held resources.
*
* @note
* The application should only call this function after ensuring that
* there are no active ExchangeContext objects. Furthermore, it is the
* onus of the application to de-allocate the ChipExchangeManager
* object after calling ChipExchangeManager::Shutdown().
*
* @return #CHIP_NO_ERROR unconditionally.
*
*/
CHIP_ERROR ChipExchangeManager::Shutdown()
{
if (MessageLayer != nullptr)
{
if (MessageLayer->ExchangeMgr == this)
{
MessageLayer->ExchangeMgr = nullptr;
MessageLayer->OnMessageReceived = nullptr;
MessageLayer->OnAcceptError = nullptr;
}
RMPStopTimer();
// Clear the retransmit table
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
ClearRetransmitTable(RetransTable[i]);
}
MessageLayer = nullptr;
}
OnExchangeContextChanged = nullptr;
FabricState = nullptr;
State = kState_NotInitialized;
return CHIP_NO_ERROR;
}
/**
* Creates a new ExchangeContext with a given peer CHIP node specified by the peer node identifier.
*
* @param[in] peerNodeId The node identifier of the peer with which the ExchangeContext is being set up.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @return A pointer to the created ExchangeContext object On success. Otherwise NULL if no object
* can be allocated or is available.
*
*/
ExchangeContext * ChipExchangeManager::NewContext(const uint64_t & peerNodeId, void * appState)
{
return NewContext(peerNodeId, FabricState->SelectNodeAddress(peerNodeId), CHIP_PORT, INET_NULL_INTERFACEID, appState);
}
/**
* Creates a new ExchangeContext with a given peer CHIP node specified by the peer node identifier
* and peer IP address.
*
* @param[in] peerNodeId The node identifier of the peer with which the ExchangeContext is being set up.
*
* @param[in] peerAddr The IP address of the peer node.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @return A pointer to the created ExchangeContext object On success. Otherwise, NULL if no object
* can be allocated or is available.
*
*/
ExchangeContext * ChipExchangeManager::NewContext(const uint64_t & peerNodeId, const IPAddress & peerAddr, void * appState)
{
return NewContext(peerNodeId, peerAddr, CHIP_PORT, INET_NULL_INTERFACEID, appState);
}
/**
* Creates a new ExchangeContext with a given peer CHIP node specified by the peer node identifier, peer IP address,
* and destination port on a specified interface.
*
* @param[in] peerNodeId The node identifier of the peer with which the ExchangeContext is being set up.
*
* @param[in] peerAddr The IP address of the peer node.
*
* @param[in] peerPort The port of the peer node.
*
* @param[in] sendIntfId The interface to use for sending CHIP messages on this exchange.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @return A pointer to the created ExchangeContext object On success. Otherwise, NULL if no object
* can be allocated or is available.
*
*/
ExchangeContext * ChipExchangeManager::NewContext(const uint64_t & peerNodeId, const IPAddress & peerAddr, uint16_t peerPort,
InterfaceId sendIntfId, void * appState)
{
ExchangeContext * ec = AllocContext();
if (ec != nullptr)
{
ec->ExchangeId = NextExchangeId++;
ec->PeerNodeId = peerNodeId;
ec->PeerAddr = peerAddr;
ec->PeerPort = (peerPort != 0) ? peerPort : CHIP_PORT;
ec->PeerIntf = sendIntfId;
ec->AppState = appState;
ec->SetInitiator(true);
// Initialize RMP variables
ec->mMsgProtocolVersion = 0;
// No need to set RMP timer, this will be done when we add to retrans table
ec->mRMPNextAckTime = 0;
ec->SetAckPending(false);
ec->SetMsgRcvdFromPeer(false);
ec->mRMPConfig = gDefaultRMPConfig;
ec->mRMPThrottleTimeout = 0;
// Internal and for Debug Only; When set, Exchange Layer does not send Ack.
ec->SetDropAck(false);
// Initialize the App callbacks to NULL
ec->OnThrottleRcvd = nullptr;
ec->OnDDRcvd = nullptr;
ec->OnAckRcvd = nullptr;
ec->OnSendError = nullptr;
#if CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
ec->SetUseEphemeralUDPPort(MessageLayer->EphemeralUDPPortEnabled());
#endif // CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
ChipLogProgress(ExchangeManager, "ec id: %d, AppState: 0x%x", EXCHANGE_CONTEXT_ID(ec - ContextPool), ec->AppState);
}
return ec;
}
/**
* Creates a new ExchangeContext with a given peer CHIP node over a specified ChipConnection.
*
* @param[in] con A pointer to the ChipConnection object representing the TCP connection
* with the peer.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @return A pointer to the created ExchangeContext object On success. Otherwise, NULL if no object
* can be allocated or is available.
*
*/
ExchangeContext * ChipExchangeManager::NewContext(ChipConnection * con, void * appState)
{
ExchangeContext * ec = NewContext(con->PeerNodeId, con->PeerAddr, con->PeerPort, INET_NULL_INTERFACEID, appState);
if (ec != nullptr)
{
ec->Con = con;
ec->KeyId = con->DefaultKeyId;
ec->EncryptionType = con->DefaultEncryptionType;
}
return ec;
}
/**
* Find the ExchangeContext from a pool matching a given set of parameters.
*
* @param[in] peerNodeId The node identifier of the peer with which the ExchangeContext has been set up.
*
* @param[in] con A pointer to the ChipConnection object representing the TCP connection
* with the peer.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @param[in] isInitiator Boolean indicator of whether the local node is the initiator of the exchange.
*
* @return A pointer to the ExchangeContext object matching the provided parameters On success, NULL on no match.
*
*/
ExchangeContext * ChipExchangeManager::FindContext(uint64_t peerNodeId, ChipConnection * con, void * appState, bool isInitiator)
{
ExchangeContext * ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
if (ec->ExchangeMgr != nullptr && ec->PeerNodeId == peerNodeId && ec->Con == con && ec->AppState == appState &&
ec->IsInitiator() == isInitiator)
return ec;
return nullptr;
}
/**
* Register an unsolicited message handler for a given profile identifier. This handler would be
* invoked for all messages of the given profile.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, ExchangeContext::MessageReceiveFunct handler,
void * appState)
{
return RegisterUMH(profileId, (int16_t) -1, nullptr, false, handler, appState);
}
/**
* Register an unsolicited message handler for a given profile identifier. This handler would be invoked for all messages of the
* given profile.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] allowDups Boolean indicator of whether duplicate messages are allowed for a given profile.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, ExchangeContext::MessageReceiveFunct handler,
bool allowDups, void * appState)
{
return RegisterUMH(profileId, (int16_t) -1, nullptr, allowDups, handler, appState);
}
/**
* Register an unsolicited message handler for a given profile identifier and message type.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType,
ExchangeContext::MessageReceiveFunct handler, void * appState)
{
return RegisterUMH(profileId, (int16_t) msgType, nullptr, false, handler, appState);
}
/**
* Register an unsolicited message handler for a given profile identifier and message type.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] allowDups Boolean indicator of whether duplicate messages are allowed for a given
* profile identifier and message type.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType,
ExchangeContext::MessageReceiveFunct handler, bool allowDups,
void * appState)
{
return RegisterUMH(profileId, (int16_t) msgType, nullptr, allowDups, handler, appState);
}
/**
* Register an unsolicited message handler for a given profile identifier, message type on a specified CHIP
* connection.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @param[in] con A pointer to the ChipConnection object representing the TCP connection
* with the peer.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType, ChipConnection * con,
ExchangeContext::MessageReceiveFunct handler, void * appState)
{
return RegisterUMH(profileId, (int16_t) msgType, con, false, handler, appState);
}
/**
* Register an unsolicited message handler for a given profile identifier, message type on a specified CHIP
* connection.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @param[in] con A pointer to the ChipConnection object representing the TCP connection
* with the peer.
*
* @param[in] handler The unsolicited message handler.
*
* @param[in] allowDups Boolean indicator of whether duplicate messages are allowed for a given
* profile identifier, message type on a specified CHIP connection.
*
* @param[in] appState A pointer to a higher layer object that holds context state.
*
* @retval #CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS If the unsolicited message handler pool
* is full and a new one cannot be allocated.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType, ChipConnection * con,
ExchangeContext::MessageReceiveFunct handler, bool allowDups,
void * appState)
{
return RegisterUMH(profileId, (int16_t) msgType, con, allowDups, handler, appState);
}
/**
* Unregister an unsolicited message handler for a given profile identifier.
*
* @param[in] profileId The profile identifier of the received message.
*
* @retval #CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER If the matching unsolicited message handler
* is not found.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::UnregisterUnsolicitedMessageHandler(uint32_t profileId)
{
return UnregisterUMH(profileId, (int16_t) -1, nullptr);
}
/**
* Unregister an unsolicited message handler for a given profile identifier and message type.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @retval #CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER If the matching unsolicited message handler
* is not found.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::UnregisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType)
{
return UnregisterUMH(profileId, (int16_t) msgType, nullptr);
}
/**
* Unregister an unsolicited message handler for a given profile identifier, message type, and CHIP connection.
*
* @param[in] profileId The profile identifier of the received message.
*
* @param[in] msgType The message type of the corresponding profile.
*
* @param[in] con A pointer to the ChipConnection object representing the TCP connection
* with the peer.
*
* @retval #CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER If the matching unsolicited message handler
* is not found.
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR ChipExchangeManager::UnregisterUnsolicitedMessageHandler(uint32_t profileId, uint8_t msgType, ChipConnection * con)
{
return UnregisterUMH(profileId, (int16_t) msgType, con);
}
void ChipExchangeManager::HandleAcceptError(ChipMessageLayer * msgLayer, CHIP_ERROR err)
{
ChipLogError(ExchangeManager, "Accept FAILED, err = %s", ErrorStr(err));
}
void ChipExchangeManager::HandleConnectionReceived(ChipConnection * con)
{
// Hook the OnMessageReceived callback for new inbound connections.
con->OnMessageReceived = HandleMessageReceived;
}
void ChipExchangeManager::HandleConnectionClosed(ChipConnection * con, CHIP_ERROR conErr)
{
for (int i = 0; i < CHIP_CONFIG_MAX_BINDINGS; i++)
{
BindingPool[i].OnConnectionClosed(con, conErr);
}
ExchangeContext * ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
if (ec->ExchangeMgr != nullptr && ec->Con == con)
{
ec->HandleConnectionClosed(conErr);
}
UnsolicitedMessageHandler * umh = (UnsolicitedMessageHandler *) UMHandlerPool;
for (int i = 0; i < CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS; i++, umh++)
if (umh->Handler != nullptr && umh->Con == con)
{
SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumUMHandlers);
umh->Handler = nullptr;
}
}
/**
* Expire the timers started by ExchangeContext instances.
* This function is not meant to be used in production code.
*
* @return Number of timers found running.
*/
#if CHIP_CONFIG_TEST
size_t ChipExchangeManager::ExpireExchangeTimers()
{
size_t retval = 0;
ExchangeContext * ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr)
{
if (ec->ResponseTimeout)
{
ec->CancelResponseTimer();
ec->ResponseTimeout = 1;
ec->StartResponseTimer();
retval++;
}
}
}
return retval;
}
#endif
ExchangeContext * ChipExchangeManager::AllocContext()
{
ExchangeContext * ec = (ExchangeContext *) ContextPool;
CHIP_FAULT_INJECT(FaultInjection::kFault_AllocExchangeContext, return nullptr);
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
if (ec->ExchangeMgr == nullptr)
{
*ec = ExchangeContext();
ec->ExchangeMgr = this;
ec->mRefCount = 1;
mContextsInUse++;
MessageLayer->SignalMessageLayerActivityChanged();
#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING)
ChipLogProgress(ExchangeManager, "ec++ id: %d, inUse: %d, addr: 0x%x", EXCHANGE_CONTEXT_ID(ec - ContextPool),
mContextsInUse, ec);
#endif
SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumContexts);
return ec;
}
ChipLogError(ExchangeManager, "Alloc ctxt FAILED");
return nullptr;
}
void ChipExchangeManager::RMPProcessDDMessage(uint32_t PauseTimeMillis, uint64_t DelayedNodeId)
{
// Expire any virtual ticks that have expired so all wakeup sources reflect the current time
RMPExpireTicks();
// Go through the retrans table entries for that node and adjust the timer.
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
// Exchcontext is the sentinel object to ascertain validity of the element
if (RetransTable[i].exchContext)
{
// Adjust the retrans timer value if Delayed Node identifier matches Peer in ExchangeContext
if (DelayedNodeId == RetransTable[i].exchContext->PeerNodeId)
{
// Paustime is specified in milliseconds; Update retrans values
RetransTable[i].nextRetransTime += (PauseTimeMillis / mRMPTimerInterval);
// Call the application callback
if (RetransTable[i].exchContext->OnDDRcvd)
{
RetransTable[i].exchContext->OnDDRcvd(RetransTable[i].exchContext, PauseTimeMillis);
}
else
{
ChipLogError(ExchangeManager, "No App Handler for Delayed Delivery for ExchangeContext with Id %04" PRIX16,
RetransTable[i].exchContext->ExchangeId);
}
} // DelayedNodeId == PeerNodeId
} // exchContext
} // for loop in table entry
// Schedule next physical wakeup
RMPStartTimer();
}
static void DefaultOnMessageReceived(ExchangeContext * ec, const IPPacketInfo * pktInfo, const ChipMessageInfo * msgInfo,
uint32_t profileId, uint8_t msgType, PacketBuffer * payload)
{
ChipLogError(ExchangeManager, "Dropping unexpected message %08" PRIX32 ":%d %04" PRIX16 " MsgId:%08" PRIX32, profileId, msgType,
ec->ExchangeId, msgInfo->MessageId);
PacketBuffer::Free(payload);
}
void ChipExchangeManager::DispatchMessage(ChipMessageInfo * msgInfo, PacketBuffer * msgBuf)
{
ChipExchangeHeader exchangeHeader;
UnsolicitedMessageHandler * umh = nullptr;
UnsolicitedMessageHandler * matchingUMH = nullptr;
ExchangeContext * ec = nullptr;
ChipConnection * msgCon = nullptr;
const uint8_t * p = nullptr;
uint32_t PauseTimeMillis = 0;
uint64_t DelayedNodeId = 0;
bool dupMsg;
bool msgNeedsAck;
bool sendAckAndCloseExchange;
#if CHIP_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
bool isMsgCounterSyncResp;
bool peerGroupMsgIdNotSynchronized;
#endif
CHIP_ERROR err = CHIP_NO_ERROR;
// Decode the exchange header.
err = DecodeHeader(&exchangeHeader, msgInfo, msgBuf);
SuccessOrExit(err);
// Check if the version is supported
if (msgInfo->MessageVersion != kChipMessageVersion_V1)
{
ExitNow(err = CHIP_ERROR_UNSUPPORTED_MESSAGE_VERSION);
}
// Notify CHIP Security Manager that encrypted message has been received.
if (msgInfo->EncryptionType != kChipEncryptionType_None)
{
MessageLayer->SecurityMgr->OnEncryptedMsgRcvd(msgInfo->KeyId, msgInfo->SourceNodeId, msgInfo->EncryptionType);
}
msgCon = msgInfo->InCon;
ChipLogRetain(ExchangeManager, "Msg %s %08" PRIX32 ":%d %d %016" PRIX64 " %04" PRIX16 " %04" PRIX16 " %ld MsgId:%08" PRIX32,
"rcvd", exchangeHeader.ProfileId, exchangeHeader.MessageType, (int) msgBuf->DataLength(), msgInfo->SourceNodeId,
msgCon->LogId(), exchangeHeader.ExchangeId, (long) err, msgInfo->MessageId);
#if CHIP_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
isMsgCounterSyncResp = exchangeHeader.ProfileId == chip::Protocols::kChipProtocol_Security &&
exchangeHeader.MessageType == chip::Protocols::Security::kMsgType_MsgCounterSyncResp;
peerGroupMsgIdNotSynchronized = (msgInfo->Flags & kChipMessageFlag_PeerGroupMsgIdNotSynchronized) != 0;
// If received message is a MsgCounterSyncResp process it first.
if (isMsgCounterSyncResp)
{
MessageLayer->SecurityMgr->HandleMsgCounterSyncRespMsg(msgInfo, msgBuf);
msgBuf = nullptr;
}
// If message counter synchronization was requested.
if ((msgInfo->Flags & kChipMessageFlag_MsgCounterSyncReq) != 0)
{
MessageLayer->SecurityMgr->SendMsgCounterSyncResp(msgInfo, msgInfo->InPacketInfo);
// Retransmit all pending messages that were encrypted with application group key.
RetransPendingAppGroupMsgs(msgInfo->SourceNodeId);
}
// Otherwise, if received message is not MsgCounterSyncResp and peer's message counter synchronization is needed.
else if (!isMsgCounterSyncResp && peerGroupMsgIdNotSynchronized)
{
MessageLayer->SecurityMgr->SendSolitaryMsgCounterSyncReq(msgInfo, msgInfo->InPacketInfo);
}
// Exit now without error if received MsgCounterSyncResp message.
if (isMsgCounterSyncResp)
{
ExitNow();
}
#endif // CHIP_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
// Received Delayed Delivery Message: Extend time for pending retrans objects
if (exchangeHeader.ProfileId == chip::Protocols::kChipProtocol_Common &&
exchangeHeader.MessageType == chip::Protocols::Common::kMsgType_RMP_Delayed_Delivery)
{
// Process Delayed Delivery message if it is not a duplicate.
if ((msgInfo->Flags & kChipMessageFlag_DuplicateMessage) == 0)
{
p = msgBuf->Start();
PauseTimeMillis = LittleEndian::Read32(p);
DelayedNodeId = LittleEndian::Read64(p);
RMPProcessDDMessage(PauseTimeMillis, DelayedNodeId);
}
// Return after processing Delayed Delivery message
ExitNow(err = CHIP_NO_ERROR);
} // If delayed delivery Msg
// Search for an existing exchange that the message applies to. If a match is found...
ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr && ec->MatchExchange(msgCon, msgInfo, &exchangeHeader))
{
// Found a matching exchange. Set flag for correct subsequent RMP
// retransmission timeout selection.
if (!ec->HasRcvdMsgFromPeer())
{
ec->SetMsgRcvdFromPeer(true);
}
// Matched ExchangeContext; send to message handler.
ec->HandleMessage(msgInfo, &exchangeHeader, msgBuf);
msgBuf = nullptr;
ExitNow(err = CHIP_NO_ERROR);
}
}
// Is message a duplicate that needs ack.
msgNeedsAck = exchangeHeader.Flags & kChipExchangeFlag_NeedsAck;
dupMsg = (msgInfo->Flags & kChipMessageFlag_DuplicateMessage);
// Search for an unsolicited message handler if it marked as being sent by an initiator. Since we didn't
// find an existing exchange that matches the message, it must be an unsolicited message. However all
// unsolicited messages must be marked as being from an initiator.
if (exchangeHeader.Flags & kChipExchangeFlag_Initiator)
{
// Search for an unsolicited message handler that can handle the message. Prefer handlers that can explicitly
// handle the message type over handlers that handle all messages for a profile.
umh = (UnsolicitedMessageHandler *) UMHandlerPool;
matchingUMH = nullptr;
for (int i = 0; i < CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS; i++, umh++)
if (umh->Handler != nullptr && umh->ProfileId == exchangeHeader.ProfileId &&
(umh->Con == nullptr || umh->Con == msgCon) &&
(!(msgInfo->Flags & kChipMessageFlag_DuplicateMessage) || umh->AllowDuplicateMsgs))
{
if (umh->MessageType == exchangeHeader.MessageType)
{
matchingUMH = umh;
break;
}
if (umh->MessageType == -1)
matchingUMH = umh;
}
}
// Discard the message if it isn't marked as being sent by an initiator and the message is not a duplicate
// that needs to send ack to the peer.
else
{
if (!msgNeedsAck)
ExitNow(err = CHIP_ERROR_UNSOLICITED_MSG_NO_ORIGINATOR);
}
// If no existing exchange that the message applies to was found we need to create
// a new exchange context (EC) in the following cases:
//
// (Dup.) Msg | UMH is | Allow | Need Peer | Action
// Needs Ack | Found | Dup. | MsgIdSync |
// ----------------------------------------------------------------------------------------------------------
// Y | Y | Y | - | Create EC, ec->HandleMessage() sends Dup ack and App callback.
// Y | Y | N | N | Create EC; ec->HandleMessage() sends Dup ack; Close EC.
// Y | N | - | N | Create EC, ec->HandleMessage() sends Dup ack; Close EC.
// N | Y | - | - | Create EC, ec->HandleMessage() sends ack (if needed) and App callback.
// N | N | - | - | Do nothing.
// Create new exchange to send ack for a duplicate message and then close this exchange.
sendAckAndCloseExchange = msgNeedsAck && (matchingUMH == nullptr || (dupMsg && !matchingUMH->AllowDuplicateMsgs));
#if CHIP_CONFIG_USE_APP_GROUP_KEYS_FOR_MSG_ENC
// Don't create new EC only to send an ack if Peer's message counter synchronization is required.
if (peerGroupMsgIdNotSynchronized)
sendAckAndCloseExchange = false;
#endif
// If we found a handler or we need to open a new exchange to send ack for a duplicate message.
if (matchingUMH != nullptr || sendAckAndCloseExchange)
{
ExchangeContext::MessageReceiveFunct umhandler = nullptr;
ec = AllocContext();
VerifyOrExit(ec != nullptr, err = CHIP_ERROR_NO_MEMORY);
ec->Con = msgCon;
ec->ExchangeId = exchangeHeader.ExchangeId;
ec->PeerNodeId = msgInfo->SourceNodeId;
if (msgInfo->InPacketInfo != nullptr)
{
ec->PeerAddr = msgInfo->InPacketInfo->SrcAddress;
ec->PeerPort = msgInfo->InPacketInfo->SrcPort;
// If the message was received over UDP, and the peer's address is an
// IPv6 link-local, capture the interface to be used when sending packets
// back to the peer.
//
// Specifying an outbound interface when sending UDP packets has a subtle
// effect on routing and source address selection. Thus it is only done when
// required by the type of destination address.
//
if (ec->Con == nullptr && ec->PeerAddr.IsIPv6LinkLocal())
{
ec->PeerIntf = msgInfo->InPacketInfo->Interface;
}
}
ec->EncryptionType = msgInfo->EncryptionType;
ec->KeyId = msgInfo->KeyId;
// No need to set RMP timer, this will be done when we add to retrans table
ec->mRMPNextAckTime = 0;
ec->SetAckPending(false);
ec->SetMsgRcvdFromPeer(true);
ec->mRMPConfig = gDefaultRMPConfig;
ec->mRMPThrottleTimeout = 0;
// Internal and for Debug Only; When set, Exchange Layer does not send Ack.
ec->SetDropAck(false);
// Set the ExchangeContext version from the Message header version
ec->mMsgProtocolVersion = msgInfo->MessageVersion;
// If UMH was found and the exchange is created not just for sending ack.
if (!sendAckAndCloseExchange)
{
umhandler = matchingUMH->Handler;
ec->SetInitiator(false);
ec->AppState = matchingUMH->AppState;
ec->OnMessageReceived = DefaultOnMessageReceived;
ec->AllowDuplicateMsgs = matchingUMH->AllowDuplicateMsgs;
ChipLogProgress(ExchangeManager, "ec id: %d, AppState: 0x%x", EXCHANGE_CONTEXT_ID(ec - ContextPool), ec->AppState);
}
// If the exchange is created only to send ack.
else
{
// If rcvd msg is from initiator then this exchange is created as not Initiator (argument to SetInitiator() is false).
// If rcvd msg is not from initiator then this exchange is created as Initiator (argument to SetInitiator() is true).
ec->SetInitiator((exchangeHeader.Flags & kChipExchangeFlag_Initiator) == 0);
}
// If support for ephemeral UDP ports is enabled, arrange to send outbound messages on this exchange from the
// local ephemeral UDP port IF the inbound message that initiated the exchange was sent TO the local ephemeral port.
#if CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
ec->SetUseEphemeralUDPPort(GetFlag(msgInfo->Flags, kChipMessageFlag_ViaEphemeralUDPPort));
#endif // CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
// Add a reservation for the message encryption key. This will ensure the key is not removed until the exchange is freed.
MessageLayer->SecurityMgr->ReserveKey(ec->PeerNodeId, ec->KeyId);
// Arrange to automatically release the encryption key when the exchange is freed.
ec->SetAutoReleaseKey(true);
ec->HandleMessage(msgInfo, &exchangeHeader, msgBuf, umhandler);
msgBuf = nullptr;
// Close exchange if it was created only to send ack for a duplicate message.
if (sendAckAndCloseExchange)
ec->Close();
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "DispatchMessage failed, err = %d", err);
}
if (msgBuf != nullptr)
{
PacketBuffer::Free(msgBuf);
}
return;
}
CHIP_ERROR ChipExchangeManager::RegisterUMH(uint32_t profileId, int16_t msgType, ChipConnection * con, bool allowDups,
ExchangeContext::MessageReceiveFunct handler, void * appState)
{
UnsolicitedMessageHandler * umh = (UnsolicitedMessageHandler *) UMHandlerPool;
UnsolicitedMessageHandler * selected = nullptr;
for (int i = 0; i < CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS; i++, umh++)
{
if (umh->Handler == nullptr)
{
if (selected == nullptr)
selected = umh;
}
else if (umh->ProfileId == profileId && umh->MessageType == msgType && umh->Con == con)
{
umh->Handler = handler;
umh->AppState = appState;
return CHIP_NO_ERROR;
}
}
if (selected == nullptr)
return CHIP_ERROR_TOO_MANY_UNSOLICITED_MESSAGE_HANDLERS;
selected->Handler = handler;
selected->AppState = appState;
selected->ProfileId = profileId;
selected->Con = con;
selected->MessageType = msgType;
selected->AllowDuplicateMsgs = allowDups;
SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumUMHandlers);
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipExchangeManager::UnregisterUMH(uint32_t profileId, int16_t msgType, ChipConnection * con)
{
UnsolicitedMessageHandler * umh = (UnsolicitedMessageHandler *) UMHandlerPool;
for (int i = 0; i < CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS; i++, umh++)
{
if (umh->Handler != nullptr && umh->ProfileId == profileId && umh->MessageType == msgType && umh->Con == con)
{
umh->Handler = nullptr;
SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumUMHandlers);
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER;
}
void ChipExchangeManager::HandleMessageReceived(ChipMessageLayer * msgLayer, ChipMessageInfo * msgInfo, PacketBuffer * msgBuf)
{
msgLayer->ExchangeMgr->DispatchMessage(msgInfo, msgBuf);
}
void ChipExchangeManager::HandleMessageReceived(ChipConnection * con, ChipMessageInfo * msgInfo, PacketBuffer * msgBuf)
{
con->MessageLayer->ExchangeMgr->DispatchMessage(msgInfo, msgBuf);
}
CHIP_ERROR ChipExchangeManager::PrependHeader(ChipExchangeHeader * exchangeHeader, PacketBuffer * buf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint16_t headLen = 8; // Constant part: Version/Flags + Msg Type + Exch Id + Profile Id
uint8_t * p = nullptr;
// Make sure the buffer has a reserved size big enough to hold the full CHIP header.
if (!buf->EnsureReservedSize(CHIP_HEADER_RESERVE_SIZE))
ExitNow(err = CHIP_ERROR_BUFFER_TOO_SMALL);
// Verify the right application version is selected.
if (exchangeHeader->Version != kChipExchangeVersion_V1)
ExitNow(err = CHIP_ERROR_UNSUPPORTED_EXCHANGE_VERSION);
// Compute the Header Len
if (exchangeHeader->Flags & kChipExchangeFlag_AckId)
{
headLen += 4;
}
p = buf->Start();
// Move the buffer start pointer back by the size of the app header.
p -= headLen;
// Adjust the buffer so that the start points to the start of the encoded message.
buf->SetStart(p);
// Encode the CHIP application header
Write8(p, ((exchangeHeader->Version << 4) | (exchangeHeader->Flags & 0xF)));
Write8(p, exchangeHeader->MessageType);
LittleEndian::Write16(p, exchangeHeader->ExchangeId);
LittleEndian::Write32(p, exchangeHeader->ProfileId);
if (exchangeHeader->Flags & kChipExchangeFlag_AckId)
{
LittleEndian::Write32(p, exchangeHeader->AckMsgId);
}
CHIP_FAULT_INJECT_MAX_ARG(
FaultInjection::kFault_FuzzExchangeHeaderTx,
// The FuzzExchangeHeader function takes as argument an index (0 to n-1) into a
// (logical) array of fuzzing cases, because every field of the header can be fuzzed in 3
// different ways. Therefore, the max index that can be used for the
// message being sent depends on the number of fields in the header.
// There are 4 fields, unless the AckMsgId field is present as
// well, for a total of 5.
((exchangeHeader->Flags & kChipExchangeFlag_AckId ? CHIP_FAULT_INJECTION_EXCH_HEADER_NUM_FIELDS
: CHIP_FAULT_INJECTION_EXCH_HEADER_NUM_FIELDS_RMP) *
CHIP_FAULT_INJECTION_NUM_FUZZ_VALUES) -
1,
int32_t arg = 0;
if (numFaultArgs > 0) { arg = faultArgs[0]; },
// Code executed without the Manager's lock:
FaultInjection::FuzzExchangeHeader(buf->Start(), arg););
exit:
return err;
}
CHIP_ERROR ChipExchangeManager::DecodeHeader(ChipExchangeHeader * exchangeHeader, ChipMessageInfo * msgInfo, PacketBuffer * buf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t * p = nullptr;
uint8_t versionFlags;
uint16_t msgLen = buf->DataLength();
uint8_t * msgEnd = buf->Start() + msgLen;
if (buf->DataLength() < 8)
ExitNow(err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
p = buf->Start();
versionFlags = Read8(p);
exchangeHeader->Version = versionFlags >> 4;
exchangeHeader->Flags = versionFlags & 0xF;
if (exchangeHeader->Version != kChipExchangeVersion_V1)
ExitNow(err = CHIP_ERROR_UNSUPPORTED_EXCHANGE_VERSION);
exchangeHeader->MessageType = Read8(p);
exchangeHeader->ExchangeId = LittleEndian::Read16(p);
exchangeHeader->ProfileId = LittleEndian::Read32(p);
if ((exchangeHeader->Flags & kChipExchangeFlag_AckId))
{
if ((p + 4) > msgEnd)
ExitNow(err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
exchangeHeader->AckMsgId = LittleEndian::Read32(p);
}
buf->SetStart(p);
SetFlag(msgInfo->Flags, kChipMessageFlag_FromInitiator, GetFlag(exchangeHeader->Flags, kChipExchangeFlag_Initiator));
exit:
return err;
}
/**
* Allow unsolicited messages to be received on the specified connection. This
* method sets the message reception handler on the given CHIP connection.
*
* @param[in] con A pointer to the CHIP connection object.
*
*/
void ChipExchangeManager::AllowUnsolicitedMessages(ChipConnection * con)
{
// Hook the OnMessageReceived callback.
con->OnMessageReceived = HandleMessageReceived;
}
/**
* Invoked when a message encryption key has been rejected by a peer (via a KeyError), or a key has
* otherwise become invalid (e.g. by ending a session).
*
* @param[in] peerNodeId The ID of the peer node with which the key is associated.
* @param[in] keyId The ID of the key that has failed.
* @param[in] keyErr A CHIP_ERROR representing the reason the key is no longer valid.
*
*/
void ChipExchangeManager::NotifyKeyFailed(uint64_t peerNodeId, uint16_t keyId, CHIP_ERROR keyErr)
{
ExchangeContext * ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr && ec->KeyId == keyId && ec->PeerNodeId == peerNodeId)
{
// Ensure the exchange context stays around until we're done with it.
ec->AddRef();
// Fail entries matching ec.
FailRetransmitTableEntries(ec, keyErr);
// Application callback function in key error case.
if (ec->OnKeyError)
ec->OnKeyError(ec, keyErr);
// Release reference to the exchange context.
ec->Release();
}
}
for (int i = 0; i < CHIP_CONFIG_MAX_BINDINGS; i++)
{
BindingPool[i].OnKeyFailed(peerNodeId, keyId, keyErr);
}
}
/**
* Invoked when the security manager becomes available for initiating new secure sessions.
*/
void ChipExchangeManager::NotifySecurityManagerAvailable()
{
// Notify each binding that the security manager is now available.
//
// Note that this algorithm is unfair to bindings that are positioned later in the pool.
// In practice, however, this is unlikely to cause any problems.
for (int i = 0; i < CHIP_CONFIG_MAX_BINDINGS; i++)
{
BindingPool[i].OnSecurityManagerAvailable();
}
}
/**
* Clear MsgCounterSyncReq flag for all pending messages to that peer.
*
* @param[in] peerNodeId Node ID of the destination node.
*
*/
void ChipExchangeManager::ClearMsgCounterSyncReq(uint64_t peerNodeId)
{
RetransTableEntry * re = (RetransTableEntry *) RetransTable;
// Find all retransmit entries (re) matching peerNodeId and using application group key.
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++, re++)
{
if (re->exchContext != nullptr && re->exchContext->PeerNodeId == peerNodeId &&
ChipKeyId::IsAppGroupKey(re->exchContext->KeyId))
{
// Clear MsgCounterSyncReq flag.
uint16_t headerField = LittleEndian::Get16(re->msgBuf->Start());
headerField &= ~kChipMessageFlag_MsgCounterSyncReq;
LittleEndian::Put16(re->msgBuf->Start(), headerField);
}
}
}
/**
* Retransmit all pending messages that were encrypted with application
* group key and were addressed to the specified node.
*
* @param[in] peerNodeId Node ID of the destination node.
*
*/
void ChipExchangeManager::RetransPendingAppGroupMsgs(uint64_t peerNodeId)
{
RetransTableEntry * re = (RetransTableEntry *) RetransTable;
// Find all retransmit entries (re) matching peerNodeId and using application group key.
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++, re++)
{
if (re->exchContext != nullptr && re->exchContext->PeerNodeId == peerNodeId &&
ChipKeyId::IsAppGroupKey(re->exchContext->KeyId))
{
// Decrement counter to discount the first sent message, which
// was ignored by receiver due to un-synchronized message counter.
re->sendCount--;
// Retramsmit message.
SendFromRetransTable(re);
}
}
}
/**
* Return a tick counter value given a time difference and a tick interval.
* The difference in time is not expected to exceed (2^32 - 1) within the
* scope of two timestamp comparisons in RMP and, thus, it makes sense to cast
* the time delta to uint32_t. This also avoids invocation of 64 bit divisions
* in constrained platforms that do not support them.
*
* @param[in] newTime Timestamp value of in milliseconds.
* @param[in] oldTime Timestamp value of in milliseconds.
* @param[in] tickInterval Timer tick interval in milliseconds.
*
* @return Tick count for the time delta.
*/
uint32_t ChipExchangeManager::GetTickCounterFromTimeDelta(uint64_t newTime, uint64_t oldTime)
{
// Note on math: we have a utility function that will compute U64 var / U32
// compile-time const => U32. At the moment, we are leaving
// mRMPTimerInterval as a member variable in ChipExchangeManager, however,
// given its current usage, it could be replaced by a compile time const.
// Should we make that change, I would recommend making the timeDelta a u64,
// and replacing the plain 32-bit division below with the utility function.
// Note that the 32bit timeDelta overflows at around 46 days; pursuing the
// above code strategy would extend that overflow by a factor if 200 given
// the default mRMPPTimerInterval. If and when such change is made, please
// update the comment around line 1426.
uint32_t timeDelta = static_cast<uint32_t>(newTime - oldTime);
return (timeDelta / mRMPTimerInterval);
}
#if defined(RMP_TICKLESS_DEBUG)
void ChipExchangeManager::TicklessDebugDumpRetransTable(const char * log)
{
ChipLogProgress(ExchangeManager, log);
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
if (RetransTable[i].exchContext)
{
ChipLogProgress(ExchangeManager, "EC:%04" PRIX16 " MsgId:%08" PRIX32 " NextRetransTimeCtr:%04" PRIX16,
RetransTable[i].exchContext, RetransTable[i].msgId, RetransTable[i].nextRetransTime);
}
}
}
#else
void ChipExchangeManager::TicklessDebugDumpRetransTable(const char * log)
{
return;
}
#endif // RMP_TICKLESS_DEBUG
/**
* Iterate through active exchange contexts and retrans table entries.
* If an action needs to be triggered by RMP time facilities, execute
* that action.
*
*/
void ChipExchangeManager::RMPExecuteActions()
{
ExchangeContext * ec = nullptr;
// Process Ack Tables for all ExchangeContexts
ec = (ExchangeContext *) ContextPool;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExecuteActions");
#endif
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr && ec->IsAckPending())
{
if (0 == ec->mRMPNextAckTime)
{
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExecuteActions sending ACK");
#endif
// Send the Ack in a Common::Null message
ec->SendCommonNullMessage();
ec->SetAckPending(false);
}
}
}
TicklessDebugDumpRetransTable("RMPExecuteActions Dumping RetransTable entries before processing");
// Retransmit / cancel anything in the retrans table whose retrans timeout
// has expired
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
ec = RetransTable[i].exchContext;
if (ec)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (0 == RetransTable[i].nextRetransTime)
{
uint8_t sendCount = RetransTable[i].sendCount;
void * msgCtxt = RetransTable[i].msgCtxt;
if (sendCount > ec->mRMPConfig.mMaxRetrans)
{
err = CHIP_ERROR_MESSAGE_NOT_ACKNOWLEDGED;
ChipLogError(ExchangeManager,
"Failed to Send CHIP MsgId:%08" PRIX32 " sendCount: %" PRIu8 " max retries: %" PRIu8,
RetransTable[i].msgId, sendCount, ec->mRMPConfig.mMaxRetrans);
// Remove from Table
ClearRetransmitTable(RetransTable[i]);
}
if (err == CHIP_NO_ERROR)
{
// Resend from Table (if the operation fails, the entry is cleared)
err = SendFromRetransTable(&(RetransTable[i]));
}
if (err == CHIP_NO_ERROR)
{
// If the retransmission was successful, update the passive timer
RetransTable[i].nextRetransTime = ec->GetCurrentRetransmitTimeout() / mRMPTimerInterval;
#if defined(DEBUG)
ChipLogProgress(ExchangeManager, "Retransmit MsgId:%08" PRIX32 " Send Cnt %d", RetransTable[i].msgId,
RetransTable[i].sendCount);
#endif
}
if (err != CHIP_NO_ERROR)
{
if (ec->OnSendError)
{
ec->OnSendError(ec, err, msgCtxt);
}
}
} // nextRetransTime = 0
}
}
TicklessDebugDumpRetransTable("RMPExecuteActions Dumping RetransTable entries after processing");
}
/**
* Calculate number of virtual RMP ticks that have expired since we last
* called this function. Iterate through active exchange contexts and
* retrans table entries, subtracting expired virtual ticks to synchronize
* wakeup times with the current system time. Do not perform any actions
* beyond updating tick counts, actions will be performed by the physical
* RMP timer tick expiry.
*
*/
void ChipExchangeManager::RMPExpireTicks()
{
uint64_t now = 0;
ExchangeContext * ec = nullptr;
uint32_t deltaTicks;
// Process Ack Tables for all ExchangeContexts
ec = (ExchangeContext *) ContextPool;
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 RMP timer)
deltaTicks = GetTickCounterFromTimeDelta(now, mRMPTimeStampBase);
// Note on math involving deltaTicks: in the code below, deltaTicks, a
// 32-bit value, is being subtracted from 16-bit expiration times. In each
// case, we compare the expiration time prior to subtraction to guard
// against underflow.
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExpireTicks at %" PRIu64 ", %" PRIu64 ", %u", now, mRMPTimeStampBase, deltaTicks);
#endif
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr && ec->IsAckPending())
{
// Decrement counter of Ack timestamp by the elapsed timer ticks
if (ec->mRMPNextAckTime >= deltaTicks)
{
ec->mRMPNextAckTime -= deltaTicks;
}
else
{
ec->mRMPNextAckTime = 0;
}
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExpireTicks set mRMPNextAckTime to %u", ec->mRMPNextAckTime);
#endif
}
}
// Process Throttle Time
// Check Throttle timeout stored in EC to set/unset Throttle flag
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
ec = RetransTable[i].exchContext;
if (ec)
{
// Process Retransmit Table
// Decrement Throttle timeout by elapsed timeticks
if (ec->mRMPThrottleTimeout >= deltaTicks)
{
ec->mRMPThrottleTimeout -= deltaTicks;
}
else
{
ec->mRMPThrottleTimeout = 0;
}
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExpireTicks set mRMPThrottleTimeout to %u", RetransTable[i].nextRetransTime);
#endif
// Decrement Retransmit timeout by elapsed timeticks
if (RetransTable[i].nextRetransTime >= deltaTicks)
{
RetransTable[i].nextRetransTime -= deltaTicks;
}
else
{
RetransTable[i].nextRetransTime = 0;
}
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExpireTicks set nextRetransTime to %u", RetransTable[i].nextRetransTime);
#endif
} // ec entry is allocated
}
// Re-Adjust the base time stamp to the most recent tick boundary
// Note on math: we cast deltaTicks to a 64bit value to ensure that that we
// produce a full 64 bit product. At the moment this is a bit of a moot
// conversion: right now, the math in GetTickCounterFromTimeDelta ensures
// that the deltaTicks * mRMPTimerTick fits in 32bits. However, I'm
// leaving the math in this form, because I'm leaving the door open to
// refactoring the division in GetTickCounterFromTimeDelta to use our
// specialized utility function that computes U64 var/ U32 compile-time
// const ==> U32
mRMPTimeStampBase += static_cast<uint64_t>(deltaTicks) * mRMPTimerInterval;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPExpireTicks mRMPTimeStampBase to %" PRIu64, mRMPTimeStampBase);
#endif
}
/**
* Handle physical wakeup of system due to RMP wakeup.
*
*/
void ChipExchangeManager::RMPTimeout(System::Layer * aSystemLayer, void * aAppState, System::Error aError)
{
ChipExchangeManager * exchangeMgr = reinterpret_cast<ChipExchangeManager *>(aAppState);
VerifyOrDie((aSystemLayer != nullptr) && (exchangeMgr != nullptr));
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPTimeout\n");
#endif
// Make sure all tick counts are sync'd to the current time
exchangeMgr->RMPExpireTicks();
// Execute any actions that are due this tick
exchangeMgr->RMPExecuteActions();
// Calculate next physical wakeup
exchangeMgr->RMPStartTimer();
}
/**
* Add a CHIP message into the retransmission table to be subsequently resent if a corresponding acknowledgment
* is not received within the retransmission timeout.
*
* @param[in] ec A pointer to the ExchangeContext object.
*
* @param[in] msgBuf A pointer to the message buffer holding the CHIP message to be retransmitted.
*
* @param[in] messageId The message identifier of the stored CHIP message.
*
* @param[in] msgCtxt A pointer to some application specific context object pertaining to this message.
*
* @param[out] rEntry A pointer to a pointer of a retransmission table entry added into the table.
*
* @retval #CHIP_ERROR_RETRANS_TABLE_FULL If there is no empty slot left in the table for addition.
* @retval #CHIP_NO_ERROR On success.
*
*/
CHIP_ERROR ChipExchangeManager::AddToRetransTable(ExchangeContext * ec, PacketBuffer * msgBuf, uint32_t messageId, void * msgCtxt,
RetransTableEntry ** rEntry)
{
bool added = false;
CHIP_ERROR err = CHIP_NO_ERROR;
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
// Check the exchContext pointer for finding an empty slot in Table
if (!RetransTable[i].exchContext)
{
// Expire any virtual ticks that have expired so all wakeup sources reflect the current time
RMPExpireTicks();
RetransTable[i].exchContext = ec;
RetransTable[i].msgId = messageId;
RetransTable[i].msgBuf = msgBuf;
RetransTable[i].sendCount = 0;
RetransTable[i].nextRetransTime = GetTickCounterFromTimeDelta(
ec->GetCurrentRetransmitTimeout() + System::Timer::GetCurrentEpoch(), mRMPTimeStampBase);
RetransTable[i].msgCtxt = msgCtxt;
*rEntry = &RetransTable[i];
// Increment the reference count
ec->AddRef();
added = true;
// Check if the timer needs to be started and start it.
RMPStartTimer();
break;
}
}
if (!added)
{
ChipLogError(ExchangeManager, "RetransTable Already Full");
err = CHIP_ERROR_RETRANS_TABLE_FULL;
}
return err;
}
/**
* Send the specified entry from the retransmission table.
*
* @param[in] entry A pointer to a retransmission table entry object that needs to be sent.
*
* @return #CHIP_NO_ERROR On success, else corresponding CHIP_ERROR returned from SendMessage.
*
*/
CHIP_ERROR ChipExchangeManager::SendFromRetransTable(RetransTableEntry * entry)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint16_t msgSendFlags = 0;
uint8_t * p = nullptr;
uint32_t len = 0;
ExchangeContext * ec = entry->exchContext;
// To trigger a call to OnSendError, set the number of transmissions so
// that the next call to RMPExecuteActions will abort this entry,
// restart the timer immediately, and ExitNow.
CHIP_FAULT_INJECT(FaultInjection::kFault_RMPSendError, entry->sendCount = (ec->mRMPConfig.mMaxRetrans + 1);
entry->nextRetransTime = 0; RMPStartTimer(); ExitNow());
if (ec)
{
SetFlag(msgSendFlags, kChipMessageFlag_RetainBuffer);
#if CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
SetFlag(msgSendFlags, kChipMessageFlag_ViaEphemeralUDPPort, ec->UseEphemeralUDPPort());
#endif // CHIP_CONFIG_ENABLE_EPHEMERAL_UDP_PORT
// Locally store the start and length;
p = entry->msgBuf->Start();
len = entry->msgBuf->DataLength();
// Send the message through
err = MessageLayer->SendMessage(ec->PeerAddr, ec->PeerPort, ec->PeerIntf, entry->msgBuf, msgSendFlags);
// Reset the msgBuf start pointer and data length after sending
entry->msgBuf->SetStart(p);
entry->msgBuf->SetDataLength(len);
// Update the counters
entry->sendCount++;
}
else
{
ChipLogError(ExchangeManager, "Table entry invalid");
}
VerifyOrExit(err != CHIP_NO_ERROR, err = CHIP_NO_ERROR);
// Any error generated during initial sending is evaluated for criticality which would
// qualify it to be reportable back to the caller. If it is non-critical then
// err is set to CHIP_NO_ERROR.
if (ChipMessageLayer::IsSendErrorNonCritical(err))
{
ChipLogError(ExchangeManager, "Non-crit err %ld sending CHIP MsgId:%08" PRIX32 " from retrans table", long(err),
entry->msgId);
err = CHIP_NO_ERROR;
}
else
{
// Remove from table
ChipLogError(ExchangeManager, "Crit-err %ld when sending CHIP MsgId:%08" PRIX32 ", send tries: %d", long(err), entry->msgId,
entry->sendCount);
ClearRetransmitTable(*entry);
}
exit:
return err;
}
/**
* Clear entries matching a specified ExchangeContext.
*
* @param[in] ec A pointer to the ExchangeContext object.
*
*/
void ChipExchangeManager::ClearRetransmitTable(ExchangeContext * ec)
{
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
if (RetransTable[i].exchContext == ec)
{
// Clear the retransmit table entry.
ClearRetransmitTable(RetransTable[i]);
}
}
}
/**
* Clear an entry in the retransmission table.
*
* @param[in] rEntry A reference to the RetransTableEntry object.
*
*/
void ChipExchangeManager::ClearRetransmitTable(RetransTableEntry & rEntry)
{
if (rEntry.exchContext)
{
// Expire any virtual ticks that have expired so all wakeup sources reflect the current time
RMPExpireTicks();
rEntry.exchContext->Release();
rEntry.exchContext = nullptr;
if (rEntry.msgBuf)
{
PacketBuffer::Free(rEntry.msgBuf);
rEntry.msgBuf = nullptr;
}
// Clear all other fields
memset(&rEntry, 0, sizeof(rEntry));
// Schedule next physical wakeup
RMPStartTimer();
}
}
/**
* Fail entries matching a specified ExchangeContext.
*
* @param[in] ec A pointer to the ExchangeContext object.
*
* @param[in] err The error for failing table entries.
*
*/
void ChipExchangeManager::FailRetransmitTableEntries(ExchangeContext * ec, CHIP_ERROR err)
{
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
if (RetransTable[i].exchContext == ec)
{
void * msgCtxt = RetransTable[i].msgCtxt;
// Remove the entry from the retransmission table.
ClearRetransmitTable(RetransTable[i]);
// Application callback OnSendError.
if (ec->OnSendError)
ec->OnSendError(ec, err, msgCtxt);
}
}
}
/**
* Iterate through active exchange contexts and retrans table entries.
* Determine how many RMP ticks we need to sleep before we need to physically
* wake the CPU to perform an action. Set a timer to go off when we
* next need to wake the system.
*
*/
void ChipExchangeManager::RMPStartTimer()
{
CHIP_ERROR res = CHIP_NO_ERROR;
uint32_t nextWakeTime = UINT32_MAX;
bool foundWake = false;
ExchangeContext * ec = nullptr;
// When do we need to next wake up to send an ACK?
ec = (ExchangeContext *) ContextPool;
for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++)
{
if (ec->ExchangeMgr != nullptr && ec->IsAckPending() && ec->mRMPNextAckTime < nextWakeTime)
{
nextWakeTime = ec->mRMPNextAckTime;
foundWake = true;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPStartTimer next ACK time %u", nextWakeTime);
#endif
}
}
for (int i = 0; i < CHIP_CONFIG_RMP_RETRANS_TABLE_SIZE; i++)
{
ec = RetransTable[i].exchContext;
if (ec)
{
// When do we need to next wake up for throttle retransmission?
if (ec->mRMPThrottleTimeout != 0 && ec->mRMPThrottleTimeout < nextWakeTime)
{
nextWakeTime = ec->mRMPThrottleTimeout;
foundWake = true;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPStartTimer throttle timeout %u", nextWakeTime);
#endif
}
// When do we need to next wake up for RMP retransmit?
if (RetransTable[i].nextRetransTime < nextWakeTime)
{
nextWakeTime = RetransTable[i].nextRetransTime;
foundWake = true;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPStartTimer RetransTime %u", nextWakeTime);
#endif
}
}
}
if (foundWake)
{
// Set timer for next tick boundary - subtract the elapsed time from the current tick
System::Timer::Epoch currentTime = System::Timer::GetCurrentEpoch();
int32_t timerArmValue = nextWakeTime * mRMPTimerInterval - (currentTime - mRMPTimeStampBase);
System::Timer::Epoch timerExpiryEpoch = currentTime + timerArmValue;
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPStartTimer wake in %d ms (%" PRIu64 " %u %" PRIu64 " %" PRIu64 ")", timerArmValue,
timerExpiryEpoch, nextWakeTime, currentTime, mRMPTimeStampBase);
#endif
if (timerExpiryEpoch != mRMPCurrentTimerExpiry)
{
// If the tick boundary has expired in the past (delayed processing of event due to other system activity),
// expire the timer immediately
if (timerArmValue < 0)
{
timerArmValue = 0;
}
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "RMPStartTimer set timer for %d %" PRIu64, timerArmValue, timerExpiryEpoch);
#endif
RMPStopTimer();
res = MessageLayer->SystemLayer->StartTimer((uint32_t) timerArmValue, RMPTimeout, this);
VerifyOrDieWithMsg(res == CHIP_NO_ERROR, ExchangeManager, "Cannot start RMPTimeout\n");
mRMPCurrentTimerExpiry = timerExpiryEpoch;
#if defined(RMP_TICKLESS_DEBUG)
}
else
{
ChipLogProgress(ExchangeManager, "RMPStartTimer timer already set for %" PRIu64, timerExpiryEpoch);
#endif
}
}
else
{
#if defined(RMP_TICKLESS_DEBUG)
ChipLogProgress(ExchangeManager, "Not setting RMP timeout at %" PRIu64, System::Timer::GetCurrentEpoch());
#endif
RMPStopTimer();
}
TicklessDebugDumpRetransTable("RMPStartTimer Dumping RetransTable entries after setting wakeup times");
}
void ChipExchangeManager::RMPStopTimer()
{
MessageLayer->SystemLayer->CancelTimer(RMPTimeout, this);
}
/**
* Initialize the shared pool of Bindings.
*
*/
void ChipExchangeManager::InitBindingPool()
{
memset(BindingPool, 0, sizeof(BindingPool));
for (size_t i = 0; i < CHIP_CONFIG_MAX_BINDINGS; ++i)
{
BindingPool[i].mState = Binding::kState_NotAllocated;
BindingPool[i].mExchangeManager = this;
}
mBindingsInUse = 0;
}
/**
* Allocate a new Binding
*
* @return A pointer to the newly allocated Binding, or NULL if the pool has been exhausted
*
*/
Binding * ChipExchangeManager::AllocBinding()
{
Binding * pResult = nullptr;
CHIP_FAULT_INJECT(FaultInjection::kFault_AllocBinding, return nullptr);
for (size_t i = 0; i < CHIP_CONFIG_MAX_BINDINGS; ++i)
{
if (Binding::kState_NotAllocated == BindingPool[i].mState)
{
pResult = &BindingPool[i];
++mBindingsInUse;
SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumBindings);
break;
}
}
return pResult;
}
/**
* Deallocate the binding object so it could be reused later
*
* @param[in] binding A pointer to the binding object to be deallocated. The object
* must be previously allocated from this #ChipExchangeManager.
*
*/
void ChipExchangeManager::FreeBinding(Binding * binding)
{
binding->mState = Binding::kState_NotAllocated;
--mBindingsInUse;
SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumBindings);
}
/**
* Allocate a new Binding with the arguments supplied
*
* @param[in] eventCallback A function pointer to be used for event callback
* @param[in] appState A pointer to some context which would be carried in event callback later
*
* @return A pointer to the newly allocated Binding, or NULL if the pool has been exhausted
*
*/
Binding * ChipExchangeManager::NewBinding(Binding::EventCallback eventCallback, void * appState)
{
Binding * pResult = AllocBinding();
if (nullptr != pResult)
{
pResult->Init(appState, eventCallback);
}
return pResult;
}
/**
* Get an ID suitable for identifying a binding in log messages.
*
* @param[in] binding A pointer to a Binding object
*
* @return An unsigned integer identifying the binding
*
*/
uint16_t ChipExchangeManager::GetBindingLogId(const Binding * const binding) const
{
// note that the result of pointer subtraction should be ptrdiff_t
return static_cast<uint16_t>(binding - BindingPool);
}
} // namespace chip