diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 87e6e6bd33e75c..34fb9a0bc16173 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -87,6 +87,7 @@ static_library("app") { "MessageDef/ReportData.h", "MessageDef/StatusElement.cpp", "MessageDef/StatusElement.h", + "MessageDef/StatusResponse.cpp", "MessageDef/SubscribeRequest.cpp", "MessageDef/SubscribeResponse.cpp", "MessageDef/TimedRequest.cpp", diff --git a/src/app/MessageDef/StatusResponse.cpp b/src/app/MessageDef/StatusResponse.cpp new file mode 100644 index 00000000000000..faedef75ad5a18 --- /dev/null +++ b/src/app/MessageDef/StatusResponse.cpp @@ -0,0 +1,101 @@ +/** + * + * Copyright (c) 2021 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. + */ + +#include "StatusResponse.h" +#include "MessageDefHelper.h" + +namespace chip { +namespace app { +CHIP_ERROR StatusResponse::Parser::Init(const TLV::TLVReader & aReader) +{ + // make a copy of the reader here + mReader.Init(aReader); + VerifyOrReturnLogError(TLV::kTLVType_Structure == mReader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE); + ReturnLogErrorOnFailure(mReader.EnterContainer(mOuterContainerType)); + return CHIP_NO_ERROR; +} + +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK +CHIP_ERROR StatusResponse::Parser::CheckSchemaValidity() const +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool statusTagPresence = false; + TLV::TLVReader reader; + PRETTY_PRINT("StatusResponse ="); + PRETTY_PRINT("{"); + + // make a copy of the reader + reader.Init(mReader); + + while (CHIP_NO_ERROR == (err = reader.Next())) + { + VerifyOrReturnLogError(TLV::IsContextTag(reader.GetTag()), CHIP_ERROR_INVALID_TLV_TAG); + switch (TLV::TagNumFromTag(reader.GetTag())) + { + case kCsTag_Status: + VerifyOrReturnLogError(!statusTagPresence, CHIP_ERROR_INVALID_TLV_TAG); + statusTagPresence = true; + VerifyOrReturnLogError(TLV::kTLVType_UnsignedInteger == reader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE); +#if CHIP_DETAIL_LOGGING + { + uint16_t status; + ReturnLogErrorOnFailure(reader.Get(status)); + PRETTY_PRINT("\tStatus = 0x%" PRIx16 ",", status); + } +#endif // CHIP_DETAIL_LOGGING + break; + default: + ReturnLogErrorOnFailure(CHIP_ERROR_INVALID_TLV_TAG); + } + } + PRETTY_PRINT("}"); + PRETTY_PRINT(""); + + if (CHIP_END_OF_TLV == err && statusTagPresence) + { + err = CHIP_NO_ERROR; + } + ReturnLogErrorOnFailure(err); + return reader.ExitContainer(mOuterContainerType); +} +#endif // CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + +CHIP_ERROR StatusResponse::Parser::GetStatus(Protocols::InteractionModel::ProtocolCode & aStatus) const +{ + uint16_t status = 0; + CHIP_ERROR err = GetUnsignedInteger(kCsTag_Status, &status); + aStatus = static_cast(status); + return err; +} + +CHIP_ERROR StatusResponse::Builder::Init(TLV::TLVWriter * const apWriter) +{ + return InitAnonymousStructure(apWriter); +} + +StatusResponse::Builder & StatusResponse::Builder::Status(const Protocols::InteractionModel::ProtocolCode aStatus) +{ + // skip if error has already been set + if (mError == CHIP_NO_ERROR) + { + mError = mpWriter->Put(TLV::ContextTag(kCsTag_Status), to_underlying(aStatus)); + } + EndOfContainer(); + return *this; +} + +} // namespace app +} // namespace chip diff --git a/src/app/MessageDef/StatusResponse.h b/src/app/MessageDef/StatusResponse.h new file mode 100644 index 00000000000000..5a7e004aeb0e0d --- /dev/null +++ b/src/app/MessageDef/StatusResponse.h @@ -0,0 +1,78 @@ +/** + * + * Copyright (c) 2021 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. + */ + +#pragma once +#include "Builder.h" +#include "Parser.h" +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace StatusResponse { +enum +{ + kCsTag_Status = 0, +}; + +class Parser : public app::Parser +{ +public: + /** + * @param [in] aReader A pointer to a TLVReader, which should point to the beginning of this response + */ + CHIP_ERROR Init(const TLV::TLVReader & aReader); +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + /** + * @brief Roughly verify the message is correctly formed + * 1) all mandatory tags are present + * 2) all elements have expected data type + * 3) any tag can only appear once + * 4) At the top level of the structure, unknown tags are ignored for forward compatibility + * @note The main use of this function is to print out what we're + * receiving during protocol development and debugging. + * The encoding rule has changed in IM encoding spec so this + * check is only "roughly" conformant now. + * + * @return #CHIP_NO_ERROR on success + */ + CHIP_ERROR CheckSchemaValidity() const; +#endif + + /** + * @brief Get Status. Next() must be called before accessing them. + * + * @return #CHIP_NO_ERROR on success + * #CHIP_END_OF_TLV if there is no such element + */ + CHIP_ERROR GetStatus(Protocols::InteractionModel::ProtocolCode & aStatus) const; +}; + +class Builder : public app::Builder +{ +public: + CHIP_ERROR Init(TLV::TLVWriter * const apWriter); + StatusResponse::Builder & Status(const Protocols::InteractionModel::ProtocolCode aStatus); +}; +} // namespace StatusResponse +} // namespace app +} // namespace chip diff --git a/src/app/ReadClient.cpp b/src/app/ReadClient.cpp index b9dea66a60334f..61f3e32328b165 100644 --- a/src/app/ReadClient.cpp +++ b/src/app/ReadClient.cpp @@ -25,7 +25,6 @@ #include #include #include -#include namespace chip { namespace app { @@ -189,25 +188,25 @@ CHIP_ERROR ReadClient::SendReadRequest(ReadPrepareParams & aReadPrepareParams) return err; } -CHIP_ERROR ReadClient::SendStatusReport(CHIP_ERROR aError) +CHIP_ERROR ReadClient::SendStatusResponse(CHIP_ERROR aError) { - Protocols::SecureChannel::GeneralStatusCode generalCode = Protocols::SecureChannel::GeneralStatusCode::kSuccess; - uint32_t protocolId = Protocols::InteractionModel::Id.ToFullyQualifiedSpecForm(); - uint16_t protocolCode = to_underlying(Protocols::InteractionModel::ProtocolCode::Success); - VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); + System::PacketBufferHandle msgBuf = System::PacketBufferHandle::New(kMaxSecureSduLengthBytes); + VerifyOrReturnLogError(!msgBuf.IsNull(), CHIP_ERROR_NO_MEMORY); + + System::PacketBufferTLVWriter writer; + writer.Init(std::move(msgBuf)); + StatusResponse::Builder response; + ReturnLogErrorOnFailure(response.Init(&writer)); + Protocols::InteractionModel::ProtocolCode statusCode = Protocols::InteractionModel::ProtocolCode::Success; if (aError != CHIP_NO_ERROR) { - generalCode = Protocols::SecureChannel::GeneralStatusCode::kFailure; - protocolCode = to_underlying(Protocols::InteractionModel::ProtocolCode::InvalidSubscription); + statusCode = Protocols::InteractionModel::ProtocolCode::InvalidSubscription; } - - Protocols::SecureChannel::StatusReport report(generalCode, protocolId, protocolCode); - - Encoding::LittleEndian::PacketBufferWriter buf(System::PacketBufferHandle::New(kMaxSecureSduLengthBytes)); - report.WriteToBuffer(buf); - System::PacketBufferHandle msgBuf = buf.Finalize(); - VerifyOrReturnLogError(!msgBuf.IsNull(), CHIP_ERROR_NO_MEMORY); + response.Status(statusCode); + ReturnLogErrorOnFailure(response.GetError()); + ReturnLogErrorOnFailure(writer.Finalize(&msgBuf)); + VerifyOrReturnLogError(mpExchangeCtx != nullptr, CHIP_ERROR_INCORRECT_STATE); if (IsSubscriptionType()) { @@ -221,7 +220,7 @@ CHIP_ERROR ReadClient::SendStatusReport(CHIP_ERROR aError) } } ReturnLogErrorOnFailure( - mpExchangeCtx->SendMessage(Protocols::SecureChannel::MsgType::StatusReport, std::move(msgBuf), + mpExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::StatusResponse, std::move(msgBuf), Messaging::SendFlags(IsAwaitingSubscribeResponse() ? Messaging::SendMessageFlags::kExpectResponse : Messaging::SendMessageFlags::kNone))); return CHIP_NO_ERROR; @@ -439,7 +438,7 @@ CHIP_ERROR ReadClient::ProcessReportData(System::PacketBufferHandle && aPayload) mpDelegate->ReportProcessed(this); } exit: - SendStatusReport(err); + SendStatusResponse(err); if (!mInitialReport) { mpExchangeCtx = nullptr; diff --git a/src/app/ReadClient.h b/src/app/ReadClient.h index 67d8c9123a9c20..7ed44b7c8d2a9a 100644 --- a/src/app/ReadClient.h +++ b/src/app/ReadClient.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -95,7 +96,7 @@ class ReadClient : public Messaging::ExchangeDelegate Messaging::ExchangeContext * GetExchangeContext() const { return mpExchangeCtx; } bool IsReadType() { return mInteractionType == InteractionType::Read; } bool IsSubscriptionType() const { return mInteractionType == InteractionType::Subscribe; }; - CHIP_ERROR SendStatusReport(CHIP_ERROR aError); + CHIP_ERROR SendStatusResponse(CHIP_ERROR aError); private: friend class TestReadInteraction; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 4cb1cbcd64f573..e8c94a0c8292dd 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -25,12 +25,12 @@ #include #include #include +#include #include #include #include #include #include -#include namespace chip { namespace app { @@ -120,17 +120,28 @@ CHIP_ERROR ReadHandler::OnReadInitialRequest(System::PacketBufferHandle && aPayl return err; } -CHIP_ERROR ReadHandler::OnStatusReport(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload) +CHIP_ERROR ReadHandler::OnStatusResponse(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload) { CHIP_ERROR err = CHIP_NO_ERROR; - Protocols::SecureChannel::StatusReport statusReport; - err = statusReport.Parse(std::move(aPayload)); + Protocols::InteractionModel::ProtocolCode statusCode; + StatusResponse::Parser response; + System::PacketBufferTLVReader reader; + reader.Init(std::move(aPayload)); + reader.Next(); + err = response.Init(reader); + SuccessOrExit(err); + +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + err = response.CheckSchemaValidity(); SuccessOrExit(err); - ChipLogProgress(DataManagement, "in state %s, receive status report, protocol id is %" PRIu32 ", protocol code is %" PRIu16, - GetStateStr(), statusReport.GetProtocolId(), statusReport.GetProtocolCode()); - VerifyOrExit((statusReport.GetProtocolId() == Protocols::InteractionModel::Id.ToFullyQualifiedSpecForm()) && - (statusReport.GetProtocolCode() == to_underlying(Protocols::InteractionModel::ProtocolCode::Success)), - err = CHIP_ERROR_INVALID_ARGUMENT); +#endif + + err = response.GetStatus(statusCode); + SuccessOrExit(err); + + ChipLogProgress(DataManagement, "In state %s, receive status response, status code is %" PRIu16, GetStateStr(), + to_underlying(statusCode)); + VerifyOrExit((statusCode == Protocols::InteractionModel::ProtocolCode::Success), err = CHIP_ERROR_INVALID_ARGUMENT); switch (mState) { case HandlerState::AwaitingReportResponse: @@ -201,9 +212,9 @@ CHIP_ERROR ReadHandler::OnMessageReceived(Messaging::ExchangeContext * apExchang { CHIP_ERROR err = CHIP_NO_ERROR; - if (aPayloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport)) + if (aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse)) { - err = OnStatusReport(apExchangeContext, std::move(aPayload)); + err = OnStatusResponse(apExchangeContext, std::move(aPayload)); } else { @@ -477,7 +488,7 @@ CHIP_ERROR ReadHandler::SendSubscribeResponse() SubscribeResponse::Builder response; ReturnLogErrorOnFailure(response.Init(&writer)); response.SubscriptionId(mSubscriptionId) - .MinIntervalFloorSeconds(mMaxIntervalCeilingSeconds) + .MinIntervalFloorSeconds(mMinIntervalFloorSeconds) .MaxIntervalCeilingSeconds(mMaxIntervalCeilingSeconds) .EndOfSubscribeResponse(); ReturnLogErrorOnFailure(response.GetError()); diff --git a/src/app/ReadHandler.h b/src/app/ReadHandler.h index 0bd8b23377493c..68254a40047c5b 100644 --- a/src/app/ReadHandler.h +++ b/src/app/ReadHandler.h @@ -151,7 +151,7 @@ class ReadHandler : public Messaging::ExchangeDelegate CHIP_ERROR ProcessReadRequest(System::PacketBufferHandle && aPayload); CHIP_ERROR ProcessAttributePathList(AttributePathList::Parser & aAttributePathListParser); CHIP_ERROR ProcessEventPathList(EventPathList::Parser & aEventPathListParser); - CHIP_ERROR OnStatusReport(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload); + CHIP_ERROR OnStatusResponse(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload); CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload) override; void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override; diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index aafac491c33878..0f9580b9805848 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -33,6 +33,7 @@ chip_test_suite("tests") { "TestMessageDef.cpp", "TestReadInteraction.cpp", "TestReportingEngine.cpp", + "TestStatusResponse.cpp", "TestWriteInteraction.cpp", ] diff --git a/src/app/tests/TestStatusResponse.cpp b/src/app/tests/TestStatusResponse.cpp new file mode 100644 index 00000000000000..719fd9dadb8e7d --- /dev/null +++ b/src/app/tests/TestStatusResponse.cpp @@ -0,0 +1,157 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file implements a test for CHIP Interaction Model Message Def + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using namespace chip::app; +constexpr chip::Protocols::InteractionModel::ProtocolCode statusValue = chip::Protocols::InteractionModel::ProtocolCode::Success; +constexpr chip::Protocols::InteractionModel::ProtocolCode invalidStatusValue = + chip::Protocols::InteractionModel::ProtocolCode::Failure; + +void BuildStatusResponse(nlTestSuite * apSuite, chip::TLV::TLVWriter & aWriter) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + StatusResponse::Builder statusResponse; + + err = statusResponse.Init(&aWriter); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + statusResponse.Status(statusValue); + NL_TEST_ASSERT(apSuite, statusResponse.GetError() == CHIP_NO_ERROR); +} + +void ParseStatusResponse(nlTestSuite * apSuite, chip::TLV::TLVReader & aReader, bool aTestPositiveCase) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + StatusResponse::Parser statusResponse; + chip::Protocols::InteractionModel::ProtocolCode status; + + err = statusResponse.Init(aReader); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + err = statusResponse.CheckSchemaValidity(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +#endif + + err = statusResponse.GetStatus(status); + if (aTestPositiveCase) + { + NL_TEST_ASSERT(apSuite, status == statusValue && err == CHIP_NO_ERROR); + } + else + { + NL_TEST_ASSERT(apSuite, status != invalidStatusValue && err == CHIP_NO_ERROR); + } +} + +void StatusResponsePositiveTest(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::System::PacketBufferTLVWriter writer; + chip::System::PacketBufferTLVReader reader; + writer.Init(chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSize)); + BuildStatusResponse(apSuite, writer); + chip::System::PacketBufferHandle buf; + err = writer.Finalize(&buf); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + reader.Init(std::move(buf)); + err = reader.Next(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ParseStatusResponse(apSuite, reader, true /*aTestPositiveCase*/); +} + +void StatusResponseNegativeTest(nlTestSuite * apSuite, void * apContext) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::System::PacketBufferTLVWriter writer; + chip::System::PacketBufferTLVReader reader; + writer.Init(chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSize)); + BuildStatusResponse(apSuite, writer); + chip::System::PacketBufferHandle buf; + err = writer.Finalize(&buf); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + reader.Init(std::move(buf)); + err = reader.Next(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ParseStatusResponse(apSuite, reader, false /*aTestPositiveCase*/); +} + +// clang-format off +const nlTest sTests[] = + { + NL_TEST_DEF("StatusResponsePositiveTest", StatusResponsePositiveTest), + NL_TEST_DEF("StatusResponseNegativeTest", StatusResponseNegativeTest), + NL_TEST_SENTINEL() + }; +// clang-format on +} // namespace + +/** + * Set up the test suite. + */ +static int TestSetup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + if (error != CHIP_NO_ERROR) + return FAILURE; + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +static int TestTeardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +int TestStatusResponse() +{ + // clang-format off + nlTestSuite theSuite = + { + "StatusResponse", + &sTests[0], + TestSetup, + TestTeardown, + }; + // clang-format on + + nlTestRunner(&theSuite, nullptr); + + return (nlTestRunnerStats(&theSuite)); +} + +CHIP_REGISTER_TEST_SUITE(TestStatusResponse) diff --git a/src/protocols/interaction_model/Constants.h b/src/protocols/interaction_model/Constants.h index 0adc286ce1a87e..7bafa651e0e53c 100644 --- a/src/protocols/interaction_model/Constants.h +++ b/src/protocols/interaction_model/Constants.h @@ -53,6 +53,7 @@ constexpr uint16_t kVersion = 0; */ enum class MsgType : uint8_t { + StatusResponse = 0x01, ReadRequest = 0x02, SubscribeRequest = 0x03, SubscribeResponse = 0x04,