diff --git a/BUILD.gn b/BUILD.gn index d750260c332c72..4db3599de3a3fc 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -49,6 +49,7 @@ if (current_toolchain != "${dir_pw_toolchain}/dummy:dummy") { "${chip_root}/src/lib/shell", "${chip_root}/src/lib/support", "${chip_root}/src/lwip:all", + "${chip_root}/src/messaging", "${chip_root}/src/setup_payload", "${chip_root}/src/system", "${chip_root}/src/transport", diff --git a/examples/common/chip-app-server/Server.cpp b/examples/common/chip-app-server/Server.cpp index d348a01323582a..a651db2b29f249 100644 --- a/examples/common/chip-app-server/Server.cpp +++ b/examples/common/chip-app-server/Server.cpp @@ -48,8 +48,8 @@ namespace { class ServerCallback : public SecureSessionMgrDelegate { public: - void OnMessageReceived(const PacketHeader & header, Transport::PeerConnectionState * state, System::PacketBuffer * buffer, - SecureSessionMgrBase * mgr) override + void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, Transport::PeerConnectionState * state, + System::PacketBuffer * buffer, SecureSessionMgrBase * mgr) override { const size_t data_len = buffer->DataLength(); char src_addr[PeerAddress::kMaxToStringSize]; diff --git a/examples/wifi-echo/server/esp32/main/EchoServer.cpp b/examples/wifi-echo/server/esp32/main/EchoServer.cpp index b470a569234caa..1529f2b52b997a 100644 --- a/examples/wifi-echo/server/esp32/main/EchoServer.cpp +++ b/examples/wifi-echo/server/esp32/main/EchoServer.cpp @@ -123,8 +123,8 @@ static size_t odc(const uint8_t * bytes, size_t bytes_len, char * out, size_t ou class EchoServerCallback : public SecureSessionMgrDelegate { public: - void OnMessageReceived(const PacketHeader & header, Transport::PeerConnectionState * state, System::PacketBuffer * buffer, - SecureSessionMgrBase * mgr) override + void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, Transport::PeerConnectionState * state, + System::PacketBuffer * buffer, SecureSessionMgrBase * mgr) override { CHIP_ERROR err; const size_t data_len = buffer->DataLength(); diff --git a/src/BUILD.gn b/src/BUILD.gn index 19fdf21986dc98..86495962ffe022 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -40,6 +40,7 @@ if (chip_build_tests) { deps = [ "${chip_root}/src/crypto/tests", "${chip_root}/src/inet/tests", + "${chip_root}/src/messaging/tests", "${chip_root}/src/system/tests", "${chip_root}/src/transport/raw/tests", "${chip_root}/src/transport/retransmit/tests", diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 0ffb67d6575157..86203bb9badadf 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -611,8 +611,9 @@ void ChipDeviceController::ClearRequestState() void ChipDeviceController::OnNewConnection(Transport::PeerConnectionState * state, SecureSessionMgrBase * mgr) {} -void ChipDeviceController::OnMessageReceived(const PacketHeader & header, Transport::PeerConnectionState * state, - System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr) +void ChipDeviceController::OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, + Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf, + SecureSessionMgrBase * mgr) { if (header.GetSourceNodeId().HasValue()) { diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 17a630b0405640..dfb3a55f3e3432 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -216,8 +216,8 @@ class DLL_EXPORT ChipDeviceController : public SecureSessionMgrDelegate, */ CHIP_ERROR ServiceEventSignal(); - void OnMessageReceived(const PacketHeader & header, Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf, - SecureSessionMgrBase * mgr) override; + void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, Transport::PeerConnectionState * state, + System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr) override; void OnNewConnection(Transport::PeerConnectionState * state, SecureSessionMgrBase * mgr) override; diff --git a/src/lib/BUILD.gn b/src/lib/BUILD.gn index e550d20a358a4b..ab025e505cd12f 100644 --- a/src/lib/BUILD.gn +++ b/src/lib/BUILD.gn @@ -27,6 +27,7 @@ static_library("lib") { "${chip_root}/src/inet", "${chip_root}/src/lib/core", "${chip_root}/src/lib/support", + "${chip_root}/src/messaging", "${chip_root}/src/platform", "${chip_root}/src/setup_payload", "${chip_root}/src/system", diff --git a/src/messaging/BUILD.gn b/src/messaging/BUILD.gn new file mode 100644 index 00000000000000..a0e20f97a41459 --- /dev/null +++ b/src/messaging/BUILD.gn @@ -0,0 +1,37 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# 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. + +import("//build_overrides/chip.gni") + +static_library("messaging") { + output_name = "libMessagingLayer" + + sources = [ + "ExchangeContext.cpp", + "ExchangeContext.h", + "ExchangeMgr.cpp", + "ExchangeMgr.h", + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + "${chip_root}/src/crypto", + "${chip_root}/src/inet", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + "${chip_root}/src/transport", + "${chip_root}/src/transport/raw", + ] +} diff --git a/src/messaging/ExchangeContext.cpp b/src/messaging/ExchangeContext.cpp new file mode 100644 index 00000000000000..e46b7ff0869ded --- /dev/null +++ b/src/messaging/ExchangeContext.cpp @@ -0,0 +1,372 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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 ExchangeContext class. + * + */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip::Encoding; +using namespace chip::Inet; +using namespace chip::System; + +namespace chip { + +static void DefaultOnMessageReceived(ExchangeContext * ec, const PacketHeader & packetHeader, uint32_t protocolId, uint8_t msgType, + PacketBuffer * payload) +{ + ChipLogError(ExchangeManager, "Dropping unexpected message %08" PRIX32 ":%d %04" PRIX16 " MsgId:%08" PRIX32, protocolId, + msgType, ec->GetExchangeId(), packetHeader.GetMessageId()); + + PacketBuffer::Free(payload); +} + +bool ExchangeContext::IsInitiator() const +{ + return mFlags.Has(ExFlagValues::kFlagInitiator); +} + +bool ExchangeContext::IsResponseExpected() const +{ + return mFlags.Has(ExFlagValues::kFlagResponseExpected); +} + +void ExchangeContext::SetInitiator(bool inInitiator) +{ + mFlags.Set(ExFlagValues::kFlagInitiator, inInitiator); +} + +void ExchangeContext::SetResponseExpected(bool inResponseExpected) +{ + mFlags.Set(ExFlagValues::kFlagResponseExpected, inResponseExpected); +} + +CHIP_ERROR ExchangeContext::SendMessage(uint16_t protocolId, uint8_t msgType, PacketBuffer * msgBuf, uint16_t sendFlags, + void * msgCtxt) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + PayloadHeader payloadHeader; + + // Don't let method get called on a freed object. + VerifyOrDie(mExchangeMgr != nullptr && mRefCount != 0); + + // we hold the exchange context here in case the entity that + // originally generated it tries to close it as a result of + // an error arising below. at the end, we have to close it. + AddRef(); + + // Set the exchange ID for this header. + payloadHeader.SetExchangeID(mExchangeId); + + // Set the protocol ID for this header. + payloadHeader.SetProtocolID(protocolId); + + // Set the message type for this header. + payloadHeader.SetMessageType(msgType); + + // If a response message is expected... + if ((sendFlags & kSendFlag_ExpectResponse) != 0) + { + // Only one 'response expected' message can be outstanding at a time. + VerifyOrExit(!IsResponseExpected(), err = CHIP_ERROR_INCORRECT_STATE); + + SetResponseExpected(true); + + // Arm the response timer if a timeout has been specified. + if (mResponseTimeout > 0) + { + err = StartResponseTimer(); + SuccessOrExit(err); + } + } + + payloadHeader.SetInitiator(IsInitiator()); + + err = mExchangeMgr->GetSessionMgr()->SendMessage(payloadHeader, mPeerNodeId, msgBuf); + msgBuf = nullptr; + SuccessOrExit(err); + +exit: + if (err != CHIP_NO_ERROR && IsResponseExpected()) + { + CancelResponseTimer(); + SetResponseExpected(false); + } + if (msgBuf != nullptr && (sendFlags & kSendFlag_RetainBuffer) == 0) + { + PacketBuffer::Free(msgBuf); + } + + // Release the reference to the exchange context acquired above. Under normal circumstances + // this will merely decrement the reference count, without actually freeing the exchange context. + // However if one of the function calls in this method resulted in a callback to the protocol, + // the protocol may have released its reference, resulting in the exchange context actually + // being freed here. + Release(); + + return err; +} + +/** + * Increment the reference counter for the exchange context by one. + * + */ +void ExchangeContext::AddRef() +{ + mRefCount++; +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec id: %d [%04" PRIX16 "], refCount++: %d", (this - mExchangeMgr->ContextPool + 1), + mExchangeId, mRefCount); +#endif +} + +void ExchangeContext::DoClose(bool clearRetransTable) +{ + // Clear protocol callbacks + mDelegate = nullptr; + + // Cancel the response timer. + CancelResponseTimer(); +} + +/** + * Gracefully close an exchange context. This call decrements the + * reference count and releases the exchange when the reference + * count goes to zero. + * + */ +void ExchangeContext::Close() +{ + VerifyOrDie(mExchangeMgr != nullptr && mRefCount != 0); + +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec id: %d [%04" PRIX16 "], %s", (this - mExchangeMgr->ContextPool + 1), mExchangeId, + __func__); +#endif + + DoClose(false); + Release(); +} +/** + * Abort the Exchange context immediately and release all + * references to it. + * + */ +void ExchangeContext::Abort() +{ + VerifyOrDie(mExchangeMgr != nullptr && mRefCount != 0); + +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec id: %d [%04" PRIX16 "], %s", (this - mExchangeMgr->ContextPool + 1), mExchangeId, + __func__); +#endif + + DoClose(true); + Release(); +} + +/** + * Release reference to this exchange context. If count is down + * to one then close the context, reset all protocol callbacks, + * and stop all timers. + * + */ +void ExchangeContext::Release() +{ + VerifyOrDie(mExchangeMgr != nullptr && mRefCount != 0); + + if (mRefCount == 1) + { + // Ideally, in this scenario, the retransmit table should + // be clear of any outstanding messages for this context and + // the boolean parameter passed to DoClose() should not matter. + ExchangeManager * em = mExchangeMgr; +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + uint16_t tmpid = mExchangeId; +#endif + + DoClose(false); + mRefCount = 0; + mExchangeMgr = nullptr; + + em->DecrementContextsInUse(); + +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec-- id: %d [%04" PRIX16 "], inUse: %d, addr: 0x%x", (this - em->ContextPool + 1), tmpid, + em->GetContextsInUse(), this); +#endif + SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumContexts); + } + else + { + mRefCount--; +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec id: %d [%04" PRIX16 "], refCount--: %d", (this - mExchangeMgr->ContextPool + 1), + mExchangeId, mRefCount); +#endif + } +} + +bool ExchangeContext::MatchExchange(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader) +{ + // A given message is part of a particular exchange if... + return + + // The exchange identifier of the message matches the exchange identifier of the context. + (mExchangeId == payloadHeader.GetExchangeID()) + + // AND The message was received from the peer node associated with the exchange, or the peer node identifier is 'any'. + && ((mPeerNodeId == kAnyNodeId) || (mPeerNodeId == packetHeader.GetSourceNodeId().Value())) + + // AND The message was sent by an initiator and the exchange context is a responder (IsInitiator==false) + // OR The message was sent by a responder and the exchange context is an initiator (IsInitiator==true) (for the broadcast + // case, the initiator is ill defined) + + && (payloadHeader.IsInitiator() != IsInitiator()); +} + +CHIP_ERROR ExchangeContext::StartResponseTimer() +{ + System::Layer * lSystemLayer = mExchangeMgr->GetSessionMgr()->SystemLayer(); + if (lSystemLayer == nullptr) + { + // this is an assertion error, which shall never happen + return CHIP_ERROR_INTERNAL; + } + + return lSystemLayer->StartTimer(mResponseTimeout, HandleResponseTimeout, this); +} + +void ExchangeContext::CancelResponseTimer() +{ + System::Layer * lSystemLayer = mExchangeMgr->GetSessionMgr()->SystemLayer(); + if (lSystemLayer == nullptr) + { + // this is an assertion error, which shall never happen + return; + } + + lSystemLayer->CancelTimer(HandleResponseTimeout, this); +} + +void ExchangeContext::HandleResponseTimeout(System::Layer * aSystemLayer, void * aAppState, System::Error aError) +{ + ExchangeContext * ec = reinterpret_cast(aAppState); + + if (ec == nullptr) + return; + + // NOTE: we don't set mResponseExpected to false here because the response could still arrive. If the user + // wants to never receive the response, they must close the exchange context. + + ExchangeContextDelegate * delegate = ec->GetDelegate(); + + // Call the user's timeout handler. + if (delegate != nullptr) + delegate->OnResponseTimeout(ec); +} + +CHIP_ERROR ExchangeContext::HandleMessage(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, + PacketBuffer * msgBuf) +{ + return HandleMessage(packetHeader, payloadHeader, msgBuf, nullptr); +} + +CHIP_ERROR ExchangeContext::HandleMessage(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, + PacketBuffer * msgBuf, ExchangeContext::MessageReceiveFunct umhandler) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t protocolId = 0; + uint8_t messageType = 0; + + // We hold a reference to the ExchangeContext here to + // guard against Close() calls(decrementing the reference + // count) by the protocol before the CHIP Exchange + // layer has completed its work on the ExchangeContext. + AddRef(); + + protocolId = payloadHeader.GetProtocolID(); + messageType = payloadHeader.GetMessageType(); + + // Return and not pass this to protocol if Common::Null Msg Type + if ((protocolId == chip::Protocols::kChipProtocol_Common) && (messageType == chip::Protocols::Common::kMsgType_Null)) + { + ExitNow(err = CHIP_NO_ERROR); + } + else + { + // Since we got the response, cancel the response timer. + CancelResponseTimer(); + + // If the context was expecting a response to a previously sent message, this message + // is implicitly that response. + SetResponseExpected(false); + + // Deliver the message to the app via its callback. + if (umhandler) + { + umhandler(this, packetHeader, protocolId, messageType, msgBuf); + msgBuf = nullptr; + } + else if (mDelegate != nullptr) + { + mDelegate->OnMessageReceived(this, packetHeader, protocolId, messageType, msgBuf); + msgBuf = nullptr; + } + else + { + DefaultOnMessageReceived(this, packetHeader, protocolId, messageType, msgBuf); + } + } + +exit: + + // Release the reference to the ExchangeContext that was held at the beginning of this function. + // This call should also do the needful of closing the ExchangeContext if the protocol has + // already made a prior call to Close(). + Release(); + + if (msgBuf != nullptr) + { + PacketBuffer::Free(msgBuf); + } + + return err; +} + +} // namespace chip diff --git a/src/messaging/ExchangeContext.h b/src/messaging/ExchangeContext.h index 47c7f82adbea5b..1d5f82af48e019 100644 --- a/src/messaging/ExchangeContext.h +++ b/src/messaging/ExchangeContext.h @@ -220,8 +220,7 @@ class DLL_EXPORT ExchangeContext typedef uint32_t Timeout; // Type used to express the timeout in this ExchangeContext, in milliseconds - Timeout mResponseTimeout; // Maximum time to wait for response (in milliseconds); 0 disables response timeout. - System::PacketBuffer * mMsg; // If we are re-transmitting, then this is the pointer to the message being retransmitted + Timeout mResponseTimeout; // Maximum time to wait for response (in milliseconds); 0 disables response timeout. ExchangeContextDelegate * mDelegate = nullptr; ExchangeManager * mExchangeMgr; void * mAppState; // Pointer to application-specific state object. @@ -250,7 +249,6 @@ class DLL_EXPORT ExchangeContext void SetExchangeMgr(ExchangeManager * exMgr) { mExchangeMgr = exMgr; } void SetAppState(void * state) { mAppState = state; } - CHIP_ERROR ResendMessage(); CHIP_ERROR StartResponseTimer(); void CancelResponseTimer(); static void HandleResponseTimeout(System::Layer * aSystemLayer, void * aAppState, System::Error aError); diff --git a/src/messaging/ExchangeMgr.cpp b/src/messaging/ExchangeMgr.cpp new file mode 100644 index 00000000000000..bd3f453deb2414 --- /dev/null +++ b/src/messaging/ExchangeMgr.cpp @@ -0,0 +1,343 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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 ExchangeManager class. + * + */ + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip::Encoding; +using namespace chip::Inet; +using namespace chip::System; + +namespace chip { + +/** + * Constructor for the ExchangeManager class. + * It sets the state to kState_NotInitialized. + * + * @note + * The class must be initialized via ExchangeManager::Init() + * prior to use. + * + */ +ExchangeManager::ExchangeManager() +{ + mState = State::kState_NotInitialized; +} + +CHIP_ERROR ExchangeManager::Init(SecureSessionMgrBase * sessionMgr) +{ + if (mState != State::kState_NotInitialized) + return CHIP_ERROR_INCORRECT_STATE; + + mSessionMgr = sessionMgr; + + mNextExchangeId = GetRandU16(); + + memset(ContextPool, 0, sizeof(ContextPool)); + mContextsInUse = 0; + + memset(UMHandlerPool, 0, sizeof(UMHandlerPool)); + OnExchangeContextChanged = nullptr; + + sessionMgr->SetDelegate(this); + + mState = State::kState_Initialized; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExchangeManager::Shutdown() +{ + if (mSessionMgr != nullptr) + { + mSessionMgr->SetDelegate(nullptr); + mSessionMgr = nullptr; + } + + OnExchangeContextChanged = nullptr; + + mState = State::kState_NotInitialized; + + return CHIP_NO_ERROR; +} + +ExchangeContext * ExchangeManager::NewContext(const uint64_t & peerNodeId, void * appState) +{ + ExchangeContext * ec = AllocContext(); + + if (ec != nullptr) + { + ec->SetExchangeId(mNextExchangeId++); + ec->SetPeerNodeId(peerNodeId); + ec->SetAppState(appState); + ec->SetInitiator(true); + ChipLogProgress(ExchangeManager, "ec id: %d, AppState: 0x%x", (ec - ContextPool + 1), ec->GetAppState()); + } + + return ec; +} + +ExchangeContext * ExchangeManager::FindContext(uint64_t peerNodeId, void * appState, bool isInitiator) +{ + ExchangeContext * ec = ContextPool; + + for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++) + { + if (ec->GetExchangeMgr() != nullptr && ec->GetPeerNodeId() == peerNodeId && ec->GetAppState() == appState && + ec->IsInitiator() == isInitiator) + return ec; + } + + return nullptr; +} + +CHIP_ERROR ExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t protocolId, ExchangeContext::MessageReceiveFunct handler, + void * appState) +{ + return RegisterUMH(protocolId, kAnyMessageType, handler, appState); +} + +CHIP_ERROR ExchangeManager::RegisterUnsolicitedMessageHandler(uint32_t protocolId, uint8_t msgType, + ExchangeContext::MessageReceiveFunct handler, void * appState) +{ + return RegisterUMH(protocolId, static_cast(msgType), handler, appState); +} + +CHIP_ERROR ExchangeManager::UnregisterUnsolicitedMessageHandler(uint32_t protocolId) +{ + return UnregisterUMH(protocolId, kAnyMessageType); +} + +CHIP_ERROR ExchangeManager::UnregisterUnsolicitedMessageHandler(uint32_t protocolId, uint8_t msgType) +{ + return UnregisterUMH(protocolId, static_cast(msgType)); +} + +void ExchangeManager::OnReceiveError(CHIP_ERROR error, const Transport::PeerAddress & source, SecureSessionMgrBase * msgLayer) +{ + ChipLogError(ExchangeManager, "Accept FAILED, err = %s", ErrorStr(error)); +} + +ExchangeContext * ExchangeManager::AllocContext() +{ + ExchangeContext * ec = ContextPool; + + CHIP_FAULT_INJECT(FaultInjection::kFault_AllocExchangeContext, return nullptr); + + for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++) + { + if (ec->GetExchangeMgr() == nullptr) + { + *ec = ExchangeContext(); + ec->SetExchangeMgr(this); + ec->SetRefCount(1); + mContextsInUse++; + +#if defined(CHIP_EXCHANGE_CONTEXT_DETAIL_LOGGING) + ChipLogProgress(ExchangeManager, "ec++ id: %d, inUse: %d, addr: 0x%x", (ec - ContextPool + 1), mContextsInUse, ec); +#endif + SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumContexts); + + return ec; + } + } + + ChipLogError(ExchangeManager, "Alloc ctxt FAILED"); + return nullptr; +} + +void ExchangeManager::DispatchMessage(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, PacketBuffer * msgBuf) +{ + UnsolicitedMessageHandler * umh = nullptr; + UnsolicitedMessageHandler * matchingUMH = nullptr; + ExchangeContext * ec = nullptr; + CHIP_ERROR err = CHIP_NO_ERROR; + + // Search for an existing exchange that the message applies to. If a match is found... + ec = ContextPool; + for (int i = 0; i < CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS; i++, ec++) + { + if (ec->GetExchangeMgr() != nullptr && ec->MatchExchange(packetHeader, payloadHeader)) + { + // Matched ExchangeContext; send to message handler. + ec->HandleMessage(packetHeader, payloadHeader, msgBuf); + + msgBuf = nullptr; + + ExitNow(err = CHIP_NO_ERROR); + } + } + + // 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 (payloadHeader.IsInitiator()) + { + // 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->ProtocolId == payloadHeader.GetProtocolID()) + { + if (umh->MessageType == payloadHeader.GetMessageType()) + { + matchingUMH = umh; + break; + } + + if (umh->MessageType == kAnyMessageType) + matchingUMH = umh; + } + } + } + // Discard the message if it isn't marked as being sent by an initiator. + else + { + ExitNow(err = CHIP_ERROR_UNSOLICITED_MSG_NO_ORIGINATOR); + } + + // If we found a handler or we need to create a new exchange context (EC). + if (matchingUMH != nullptr) + { + ExchangeContext::MessageReceiveFunct umhandler = nullptr; + + ec = AllocContext(); + VerifyOrExit(ec != nullptr, err = CHIP_ERROR_NO_MEMORY); + + ec->SetExchangeId(payloadHeader.GetExchangeID()); + ec->SetPeerNodeId(packetHeader.GetSourceNodeId().Value()); + ec->SetInitiator(false); + ec->SetAppState(matchingUMH->AppState); + + umhandler = matchingUMH->Handler; + + ChipLogProgress(ExchangeManager, "ec id: %d, AppState: 0x%x", (ec - ContextPool + 1), ec->GetAppState()); + + ec->HandleMessage(packetHeader, payloadHeader, msgBuf, umhandler); + msgBuf = nullptr; + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(ExchangeManager, "DispatchMessage failed, err = %d", err); + } + + if (msgBuf != nullptr) + { + PacketBuffer::Free(msgBuf); + } +} + +CHIP_ERROR ExchangeManager::RegisterUMH(uint32_t protocolId, int16_t msgType, ExchangeContext::MessageReceiveFunct handler, + void * appState) +{ + UnsolicitedMessageHandler * umh = 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->ProtocolId == protocolId && umh->MessageType == msgType) + { + 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->ProtocolId = protocolId; + selected->MessageType = msgType; + + SYSTEM_STATS_INCREMENT(chip::System::Stats::kExchangeMgr_NumUMHandlers); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExchangeManager::UnregisterUMH(uint32_t protocolId, int16_t msgType) +{ + UnsolicitedMessageHandler * umh = UMHandlerPool; + + for (int i = 0; i < CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS; i++, umh++) + { + if (umh->Handler != nullptr && umh->ProtocolId == protocolId && umh->MessageType == msgType) + { + umh->Handler = nullptr; + SYSTEM_STATS_DECREMENT(chip::System::Stats::kExchangeMgr_NumUMHandlers); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_NO_UNSOLICITED_MESSAGE_HANDLER; +} + +void ExchangeManager::OnMessageReceived(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, + Transport::PeerConnectionState * state, System::PacketBuffer * msgBuf, + SecureSessionMgrBase * msgLayer) +{ + DispatchMessage(packetHeader, payloadHeader, msgBuf); +} + +void ExchangeManager::DecrementContextsInUse() +{ + if (mContextsInUse >= 1) + { + mContextsInUse--; + } + else + { + ChipLogError(ExchangeManager, "No context in use, decrement failed"); + } +} + +} // namespace chip diff --git a/src/messaging/tests/BUILD.gn b/src/messaging/tests/BUILD.gn new file mode 100644 index 00000000000000..f8347e8705e0b8 --- /dev/null +++ b/src/messaging/tests/BUILD.gn @@ -0,0 +1,43 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# 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. + +import("//build_overrides/chip.gni") +import("//build_overrides/nlio.gni") +import("//build_overrides/nlunit_test.gni") + +import("${chip_root}/build/chip/chip_test_suite.gni") + +chip_test_suite("tests") { + output_name = "libMessagingLayerTests" + + sources = [ + "TestExchangeMgr.cpp", + "TestMessagingLayer.h", + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + "${chip_root}/src/inet/tests:tests_common", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", + "${chip_root}/src/messaging", + "${chip_root}/src/transport", + "${chip_root}/src/transport/raw/tests:helpers", + "${nlio_root}:nlio", + "${nlunit_test_root}:nlunit-test", + ] + + tests = [ "TestExchangeMgr" ] +} diff --git a/src/messaging/tests/TestExchangeMgr.cpp b/src/messaging/tests/TestExchangeMgr.cpp new file mode 100644 index 00000000000000..e3daf88122c637 --- /dev/null +++ b/src/messaging/tests/TestExchangeMgr.cpp @@ -0,0 +1,242 @@ +/* + * + * Copyright (c) 2020 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 unit tests for the ExchangeManager implementation. + */ + +#include "TestMessagingLayer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace { + +using namespace chip; +using namespace chip::Inet; +using namespace chip::Transport; + +using TestContext = chip::Test::IOContext; + +TestContext sContext; + +constexpr NodeId kSourceNodeId = 123654; +constexpr NodeId kDestinationNodeId = 111222333; + +class LoopbackTransport : public Transport::Base +{ +public: + /// Transports are required to have a constructor that takes exactly one argument + CHIP_ERROR Init(const char * unused) { return CHIP_NO_ERROR; } + + CHIP_ERROR SendMessage(const PacketHeader & header, Header::Flags payloadFlags, const PeerAddress & address, + System::PacketBuffer * msgBuf) override + { + HandleMessageReceived(header, address, msgBuf); + return CHIP_NO_ERROR; + } + + bool CanSendToPeer(const PeerAddress & address) override { return true; } +}; + +static void HanldeAllUnsolicitedMessage(ExchangeContext * ec, const PacketHeader & packetHeader, uint32_t protocolId, + uint8_t msgType, System::PacketBuffer * payload) +{} + +static void HanldeOneUnsolicitedMessage(ExchangeContext * ec, const PacketHeader & packetHeader, uint32_t protocolId, + uint8_t msgType, System::PacketBuffer * payload) +{} + +void CheckSimpleInitTest(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + SecureSessionMgr conn; + CHIP_ERROR err; + + ctx.GetInetLayer().SystemLayer()->Init(nullptr); + + err = conn.Init(kSourceNodeId, ctx.GetInetLayer().SystemLayer(), "LOOPBACK"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeManager exchangeMgr; + err = exchangeMgr.Init(&conn); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); +} + +void CheckNewContextTest(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + SecureSessionMgr conn; + CHIP_ERROR err; + + ctx.GetInetLayer().SystemLayer()->Init(nullptr); + + err = conn.Init(kSourceNodeId, ctx.GetInetLayer().SystemLayer(), "LOOPBACK"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeManager exchangeMgr; + err = exchangeMgr.Init(&conn); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeContext * ec1 = exchangeMgr.NewContext(kSourceNodeId, (void *) 0x1234); + NL_TEST_ASSERT(inSuite, ec1 != nullptr); + NL_TEST_ASSERT(inSuite, ec1->IsInitiator() == true); + NL_TEST_ASSERT(inSuite, ec1->GetExchangeId() != 0); + NL_TEST_ASSERT(inSuite, ec1->GetPeerNodeId() == kSourceNodeId); + NL_TEST_ASSERT(inSuite, ec1->GetAppState() == (void *) 0x1234); + + ExchangeContext * ec2 = exchangeMgr.NewContext(kDestinationNodeId, (void *) 0x2345); + NL_TEST_ASSERT(inSuite, ec2 != nullptr); + NL_TEST_ASSERT(inSuite, ec2->GetExchangeId() > ec1->GetExchangeId()); + NL_TEST_ASSERT(inSuite, ec2->GetPeerNodeId() == kDestinationNodeId); +} + +void CheckFindContextTest(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + SecureSessionMgr conn; + CHIP_ERROR err; + + ctx.GetInetLayer().SystemLayer()->Init(nullptr); + + err = conn.Init(kSourceNodeId, ctx.GetInetLayer().SystemLayer(), "LOOPBACK"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeManager exchangeMgr; + err = exchangeMgr.Init(&conn); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeContext * ec = exchangeMgr.NewContext(kDestinationNodeId, nullptr); + NL_TEST_ASSERT(inSuite, ec != nullptr); + + bool result = exchangeMgr.FindContext(kDestinationNodeId, nullptr, true); + NL_TEST_ASSERT(inSuite, result == true); + + result = exchangeMgr.FindContext(kDestinationNodeId, nullptr, false); + NL_TEST_ASSERT(inSuite, result == false); +} + +void CheckUmhRegistrationTest(nlTestSuite * inSuite, void * inContext) +{ + TestContext & ctx = *reinterpret_cast(inContext); + + SecureSessionMgr conn; + CHIP_ERROR err; + + ctx.GetInetLayer().SystemLayer()->Init(nullptr); + + err = conn.Init(kSourceNodeId, ctx.GetInetLayer().SystemLayer(), "LOOPBACK"); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ExchangeManager exchangeMgr; + err = exchangeMgr.Init(&conn); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = exchangeMgr.RegisterUnsolicitedMessageHandler(0x0001, HanldeAllUnsolicitedMessage, nullptr); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = exchangeMgr.RegisterUnsolicitedMessageHandler(0x0002, 0x0001, HanldeOneUnsolicitedMessage, nullptr); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = exchangeMgr.UnregisterUnsolicitedMessageHandler(0x0001); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = exchangeMgr.UnregisterUnsolicitedMessageHandler(0x0002); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + err = exchangeMgr.UnregisterUnsolicitedMessageHandler(0x0002, 0x0001); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = exchangeMgr.UnregisterUnsolicitedMessageHandler(0x0002, 0x0002); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); +} + +// Test Suite + +/** + * Test Suite that lists all the test functions. + */ +// clang-format off +const nlTest sTests[] = +{ + NL_TEST_DEF("Test ExchangeMgr::Init", CheckSimpleInitTest), + NL_TEST_DEF("Test ExchangeMgr::NewContext", CheckNewContextTest), + NL_TEST_DEF("Test ExchangeMgr::FindContext", CheckFindContextTest), + NL_TEST_DEF("Test ExchangeMgr::CheckUmhRegistrationTest", CheckUmhRegistrationTest), + + NL_TEST_SENTINEL() +}; +// clang-format on + +int Initialize(void * aContext); +int Finalize(void * aContext); + +// clang-format off +nlTestSuite sSuite = +{ + "Test-CHIP-ExchangeManager", + &sTests[0], + Initialize, + Finalize +}; +// clang-format on + +/** + * Initialize the test suite. + */ +int Initialize(void * aContext) +{ + CHIP_ERROR err = reinterpret_cast(aContext)->Init(&sSuite); + return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE; +} + +/** + * Finalize the test suite. + */ +int Finalize(void * aContext) +{ + CHIP_ERROR err = reinterpret_cast(aContext)->Shutdown(); + return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE; +} + +} // namespace + +/** + * Main + */ +int TestExchangeMgr() +{ + // Run test suit against one context + nlTestRunner(&sSuite, &sContext); + + return (nlTestRunnerStats(&sSuite)); +} diff --git a/src/messaging/tests/TestExchangeMgrDriver.cpp b/src/messaging/tests/TestExchangeMgrDriver.cpp new file mode 100644 index 00000000000000..c4699e8d27bb50 --- /dev/null +++ b/src/messaging/tests/TestExchangeMgrDriver.cpp @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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 a standalone/native program executable + * test driver for the CHIP core library CHIP ExchangeManager tests. + * + */ + +#include "TestMessagingLayer.h" + +#include + +int main() +{ + // Generate machine-readable, comma-separated value (CSV) output. + nlTestSetOutputStyle(OUTPUT_CSV); + + return (TestExchangeMgr()); +} diff --git a/src/messaging/tests/TestMessagingLayer.h b/src/messaging/tests/TestMessagingLayer.h new file mode 100644 index 00000000000000..a42fde59e609bb --- /dev/null +++ b/src/messaging/tests/TestMessagingLayer.h @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * + * 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 declares test entry points for CHIP Messaging layer + * layer library unit tests. + * + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +int TestExchangeMgr(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/transport/SecureSessionMgr.cpp b/src/transport/SecureSessionMgr.cpp index 5f170f25c2e5ab..86ce731bd43c03 100644 --- a/src/transport/SecureSessionMgr.cpp +++ b/src/transport/SecureSessionMgr.cpp @@ -89,6 +89,13 @@ CHIP_ERROR SecureSessionMgrBase::InitInternal(NodeId localNodeId, System::Layer } CHIP_ERROR SecureSessionMgrBase::SendMessage(NodeId peerNodeId, System::PacketBuffer * msgBuf) +{ + PayloadHeader payloadHeader; + + return SendMessage(payloadHeader, peerNodeId, msgBuf); +} + +CHIP_ERROR SecureSessionMgrBase::SendMessage(PayloadHeader & payloadHeader, NodeId peerNodeId, System::PacketBuffer * msgBuf) { CHIP_ERROR err = CHIP_NO_ERROR; PeerConnectionState * state = nullptr; @@ -108,7 +115,6 @@ CHIP_ERROR SecureSessionMgrBase::SendMessage(NodeId peerNodeId, System::PacketBu { uint8_t * data = nullptr; PacketHeader packetHeader; - PayloadHeader payloadHeader; MessageAuthenticationCode mac; const uint16_t headerSize = payloadHeader.EncodeSizeBytes(); @@ -297,7 +303,7 @@ void SecureSessionMgrBase::HandleDataReceived(const PacketHeader & packetHeader, if (connection->mCB != nullptr) { - connection->mCB->OnMessageReceived(packetHeader, state, msg, connection); + connection->mCB->OnMessageReceived(packetHeader, payloadHeader, state, msg, connection); msg = nullptr; } } diff --git a/src/transport/SecureSessionMgr.h b/src/transport/SecureSessionMgr.h index d97a3ffe051d02..f88c8c2921f518 100644 --- a/src/transport/SecureSessionMgr.h +++ b/src/transport/SecureSessionMgr.h @@ -59,12 +59,14 @@ class DLL_EXPORT SecureSessionMgrDelegate : public ReferenceCounted & peerAddr, SecurePairingSession * pairing); + /** + * @brief + * Return the System Layer pointer used by current SecureSessionMgr. + */ + System::Layer * SystemLayer() { return mSystemLayer; } + protected: /** * @brief diff --git a/src/transport/raw/MessageHeader.h b/src/transport/raw/MessageHeader.h index fd0f029dfe29a9..1cf74901639cdc 100644 --- a/src/transport/raw/MessageHeader.h +++ b/src/transport/raw/MessageHeader.h @@ -50,6 +50,16 @@ enum class EncryptionType kAESCCMTagLen16 = 2, }; +/** + * @brief + * The CHIP Exchange header flag bits. + */ +enum class ExFlagValues : uint16_t +{ + /// Set when current message is sent by the initiator of an exchange. + kExchangeFlag_Initiator = 0x0001, +}; + enum class FlagValues : uint16_t { /// Header flag specifying that a destination node id is included in the header. @@ -328,6 +338,21 @@ class PayloadHeader return *this; } + /** Set the Initiator flag bit. */ + PayloadHeader & SetInitiator(bool inInitiator) + { + mExchangeFlags.Set(Header::ExFlagValues::kExchangeFlag_Initiator, inInitiator); + return *this; + } + + /** + * Determine whether the initiator of the exchange. + * + * @return Returns 'true' if it is the initiator, else 'false'. + * + */ + bool IsInitiator() const { return mExchangeFlags.Has(Header::ExFlagValues::kExchangeFlag_Initiator); } + /** * A call to `Encode` will require at least this many bytes on the current * object to be successful. @@ -386,6 +411,9 @@ class PayloadHeader /// Protocol identifier uint16_t mProtocolID = 0; + + /// Bit flag indicators for CHIP Exchange header + BitFlags mExchangeFlags; }; /** Handles encoding/decoding of CHIP message headers */ diff --git a/src/transport/tests/TestSecureSessionMgr.cpp b/src/transport/tests/TestSecureSessionMgr.cpp index d5ccd3838c8c13..dace12d5186ae2 100644 --- a/src/transport/tests/TestSecureSessionMgr.cpp +++ b/src/transport/tests/TestSecureSessionMgr.cpp @@ -66,8 +66,8 @@ class LoopbackTransport : public Transport::Base class TestSessMgrCallback : public SecureSessionMgrDelegate { public: - void OnMessageReceived(const PacketHeader & header, PeerConnectionState * state, System::PacketBuffer * msgBuf, - SecureSessionMgrBase * mgr) override + void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, PeerConnectionState * state, + System::PacketBuffer * msgBuf, SecureSessionMgrBase * mgr) override { NL_TEST_ASSERT(mSuite, header.GetSourceNodeId() == Optional::Value(kSourceNodeId)); NL_TEST_ASSERT(mSuite, header.GetDestinationNodeId() == Optional::Value(kDestinationNodeId));