From 7981ba67da1f742e2b0578a4fcf1827267ff03f4 Mon Sep 17 00:00:00 2001 From: yunhanw-google Date: Mon, 12 Jul 2021 07:20:09 -0700 Subject: [PATCH] Add IM timed request builder/parser (#8264) --- src/app/BUILD.gn | 1 + src/app/MessageDef/TimedRequest.cpp | 103 ++++++++++++++++++++++++++++ src/app/MessageDef/TimedRequest.h | 82 ++++++++++++++++++++++ src/app/tests/TestMessageDef.cpp | 50 ++++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 src/app/MessageDef/TimedRequest.cpp create mode 100644 src/app/MessageDef/TimedRequest.h diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 949492eea54c73..de3f97c0546fb2 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -89,6 +89,7 @@ static_library("app") { "MessageDef/StatusElement.h", "MessageDef/SubscribeRequest.cpp", "MessageDef/SubscribeResponse.cpp", + "MessageDef/TimedRequest.cpp", "MessageDef/WriteRequest.cpp", "MessageDef/WriteResponse.cpp", "ReadClient.cpp", diff --git a/src/app/MessageDef/TimedRequest.cpp b/src/app/MessageDef/TimedRequest.cpp new file mode 100644 index 00000000000000..62e020e0c0038b --- /dev/null +++ b/src/app/MessageDef/TimedRequest.cpp @@ -0,0 +1,103 @@ +/** + * + * 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 "TimedRequest.h" +#include "MessageDefHelper.h" + +namespace chip { +namespace app { +CHIP_ERROR TimedRequest::Parser::Init(const chip::TLV::TLVReader & aReader) +{ + // make a copy of the reader here + mReader.Init(aReader); + VerifyOrReturnLogError(chip::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 TimedRequest::Parser::CheckSchemaValidity() const +{ + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t TagPresenceMask = 0; + chip::TLV::TLVReader reader; + PRETTY_PRINT("TimedRequest ="); + PRETTY_PRINT("{"); + + // make a copy of the reader + reader.Init(mReader); + + while (CHIP_NO_ERROR == (err = reader.Next())) + { + VerifyOrReturnLogError(chip::TLV::IsContextTag(reader.GetTag()), CHIP_ERROR_INVALID_TLV_TAG); + switch (chip::TLV::TagNumFromTag(reader.GetTag())) + { + case kCsTag_TimeoutMs: + VerifyOrReturnLogError(!(TagPresenceMask & (1 << kCsTag_TimeoutMs)), CHIP_ERROR_INVALID_TLV_TAG); + TagPresenceMask |= (1 << kCsTag_TimeoutMs); + VerifyOrReturnLogError(chip::TLV::kTLVType_UnsignedInteger == reader.GetType(), CHIP_ERROR_WRONG_TLV_TYPE); +#if CHIP_DETAIL_LOGGING + { + uint16_t timeout; + ReturnLogErrorOnFailure(reader.Get(timeout)); + PRETTY_PRINT("\tTimeoutMs = 0x%" PRIx8 ",", timeout); + } +#endif // CHIP_DETAIL_LOGGING + break; + default: + ReturnLogErrorOnFailure(CHIP_ERROR_INVALID_TLV_TAG); + } + } + PRETTY_PRINT("}"); + PRETTY_PRINT(""); + + if (CHIP_END_OF_TLV == err) + { + const uint16_t RequiredFields = (1 << kCsTag_TimeoutMs); + + if ((TagPresenceMask & RequiredFields) == RequiredFields) + { + err = CHIP_NO_ERROR; + } + } + ReturnLogErrorOnFailure(err); + return reader.ExitContainer(mOuterContainerType); +} +#endif // CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + +CHIP_ERROR TimedRequest::Parser::GetTimeoutMs(uint16_t * const apTimeoutMs) const +{ + return GetUnsignedInteger(kCsTag_TimeoutMs, apTimeoutMs); +} + +CHIP_ERROR TimedRequest::Builder::Init(chip::TLV::TLVWriter * const apWriter) +{ + return InitAnonymousStructure(apWriter); +} + +TimedRequest::Builder & TimedRequest::Builder::TimeoutMs(const uint16_t aTimeoutMs) +{ + // skip if error has already been set + if (mError == CHIP_NO_ERROR) + { + mError = mpWriter->Put(chip::TLV::ContextTag(kCsTag_TimeoutMs), aTimeoutMs); + ChipLogFunctError(mError); + } + EndOfContainer(); + return *this; +} +} // namespace app +} // namespace chip diff --git a/src/app/MessageDef/TimedRequest.h b/src/app/MessageDef/TimedRequest.h new file mode 100644 index 00000000000000..301ebe60204951 --- /dev/null +++ b/src/app/MessageDef/TimedRequest.h @@ -0,0 +1,82 @@ +/** + * + * 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 + +namespace chip { +namespace app { +namespace TimedRequest { +enum +{ + kCsTag_TimeoutMs = 0, +}; + +class Parser : public chip::app::Parser +{ +public: + /** + * @param [in] aReader A pointer to a TLVReader, which should point to the beginning of this response + */ + CHIP_ERROR Init(const chip::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 Timeout value. 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 GetTimeoutMs(uint16_t * const apTimeoutMs) const; +}; + +class Builder : public chip::app::Builder +{ +public: + CHIP_ERROR Init(chip::TLV::TLVWriter * const apWriter); + + /** + * @brief Timeout value, sent by a client to a server, if there is a preceding successful Timed Request action, + * the following action SHALL be received before the end of the Timeout interval. + */ + TimedRequest::Builder & TimeoutMs(const uint16_t aTimeoutMs); +}; +} // namespace TimedRequest +} // namespace app +} // namespace chip diff --git a/src/app/tests/TestMessageDef.cpp b/src/app/tests/TestMessageDef.cpp index e5b6c4c3ccad7d..44ec35aa41a7f9 100644 --- a/src/app/tests/TestMessageDef.cpp +++ b/src/app/tests/TestMessageDef.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -1007,6 +1008,35 @@ void ParseSubscribeResponse(nlTestSuite * apSuite, chip::TLV::TLVReader & aReade NL_TEST_ASSERT(apSuite, finalSyncIntervalSeconds == 1 && err == CHIP_NO_ERROR); } +void BuildTimedRequest(nlTestSuite * apSuite, chip::TLV::TLVWriter & aWriter) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TimedRequest::Builder timedRequestBuilder; + + err = timedRequestBuilder.Init(&aWriter); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + timedRequestBuilder.TimeoutMs(1); + NL_TEST_ASSERT(apSuite, timedRequestBuilder.GetError() == CHIP_NO_ERROR); +} + +void ParseTimedRequest(nlTestSuite * apSuite, chip::TLV::TLVReader & aReader) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + TimedRequest::Parser timedRequestarser; + uint16_t timeout = 0; + + err = timedRequestarser.Init(aReader); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK + err = timedRequestarser.CheckSchemaValidity(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); +#endif + err = timedRequestarser.GetTimeoutMs(&timeout); + NL_TEST_ASSERT(apSuite, timeout == 1 && err == CHIP_NO_ERROR); +} + void AttributePathTest(nlTestSuite * apSuite, void * apContext) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -1511,6 +1541,25 @@ void SubscribeResponseTest(nlTestSuite * apSuite, void * apContext) ParseSubscribeResponse(apSuite, reader); } +void TimedRequestTest(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)); + BuildTimedRequest(apSuite, writer); + chip::System::PacketBufferHandle buf; + err = writer.Finalize(&buf); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + DebugPrettyPrint(buf); + + reader.Init(std::move(buf)); + err = reader.Next(); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + ParseTimedRequest(apSuite, reader); +} + void CheckPointRollbackTest(nlTestSuite * apSuite, void * apContext) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -1594,6 +1643,7 @@ const nlTest sTests[] = NL_TEST_DEF("WriteResponseTest", WriteResponseTest), NL_TEST_DEF("SubscribeRequestTest", SubscribeRequestTest), NL_TEST_DEF("SubscribeResponseTest", SubscribeResponseTest), + NL_TEST_DEF("TimedRequestTest", TimedRequestTest), NL_TEST_DEF("CheckPointRollbackTest", CheckPointRollbackTest), NL_TEST_SENTINEL() };