/* * * Copyright (c) 2023 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. */ #pragma once #include #include #include #include #include namespace chip { namespace Decoders { /// Represents an individual decoded entry for IM Payloads /// Generally a name + value + metadata tuple, where name and value are never NULL. class PayloadEntry { public: static constexpr uint32_t kInvalidId = 0xFFFFFFFF; enum class IMPayloadType { kNone = 0, // generally should not be used except initial init kValue, // represents a simple value to output kNestingEnter, // Nested struct enter. Has name, empty value kNestingExit, // Nested struct exit (has no name/value) // Content payloads kAttribute, kCommand, kEvent, }; PayloadEntry(const PayloadEntry &) = default; PayloadEntry & operator=(const PayloadEntry &) = default; PayloadEntry() : mType(IMPayloadType::kNone), mName(""), mValue("") {} IMPayloadType GetType() const { return mType; } const char * GetName() const { return mName; } const char * GetValueText() const { return mValue; } /// valid only if payload is an IM Payload uint32_t GetClusterId() const { return mClusterId; }; /// valid only if payload as an Attribute ID uint32_t GetAttributeId() const { VerifyOrReturnValue(mType == IMPayloadType::kAttribute, kInvalidId); return mSubId; } /// valid only if payload as a Command ID uint32_t GetCommandId() const { VerifyOrReturnValue(mType == IMPayloadType::kCommand, kInvalidId); return mSubId; } /// valid only if payload as a Command ID uint32_t GetEventId() const { VerifyOrReturnValue(mType == IMPayloadType::kEvent, kInvalidId); return mSubId; } static PayloadEntry SimpleValue(const char * name, const char * value) { return PayloadEntry(IMPayloadType::kValue, name, value); } static PayloadEntry NestingEnter(const char * name) { return PayloadEntry(IMPayloadType::kNestingEnter, name, ""); } static PayloadEntry NestingExit() { return PayloadEntry(IMPayloadType::kNestingExit, "", ""); } static PayloadEntry AttributePayload(uint32_t cluster_id, uint32_t attribute_id) { PayloadEntry result(IMPayloadType::kAttribute, "ATTR_DATA", ""); result.mClusterId = cluster_id; result.mSubId = attribute_id; return result; } static PayloadEntry CommandPayload(uint32_t cluster_id, uint32_t command_id) { PayloadEntry result(IMPayloadType::kCommand, "COMMAND_DATA", ""); result.mClusterId = cluster_id; result.mSubId = command_id; return result; } static PayloadEntry EventPayload(uint32_t cluster_id, uint32_t event_id) { PayloadEntry result(IMPayloadType::kEvent, "EVENT_DATA", ""); result.mClusterId = cluster_id; result.mSubId = event_id; return result; } private: PayloadEntry(IMPayloadType type, const char * name, const char * value) : mType(type), mName(name), mValue(value) {} IMPayloadType mType = IMPayloadType::kValue; const char * mName = nullptr; const char * mValue = nullptr; uint32_t mClusterId = 0; uint32_t mSubId = 0; // attribute, command or event id }; /// Sets up decoding of some Matter data payload class PayloadDecoderInitParams { public: using DecodeTree = const FlatTree::Node *; PayloadDecoderInitParams() = default; PayloadDecoderInitParams & SetProtocol(Protocols::Id value) { mProtocol = value; return *this; } PayloadDecoderInitParams & SetMessageType(uint8_t value) { mMessageType = value; return *this; } PayloadDecoderInitParams & SetProtocolDecodeTree(DecodeTree tree, size_t s) { mProtocolTree = tree; mProtocolTreeSize = s; return *this; } template PayloadDecoderInitParams & SetProtocolDecodeTree(const std::array, N> & a) { return SetProtocolDecodeTree(a.data(), N); } PayloadDecoderInitParams & SetClusterDecodeTree(DecodeTree tree, size_t s) { mClusterTree = tree; mClusterTreeSize = s; return *this; } template PayloadDecoderInitParams & SetClusterDecodeTree(const std::array, N> & a) { return SetClusterDecodeTree(a.data(), N); } DecodeTree GetProtocolDecodeTree() const { return mProtocolTree; } size_t GetProtocolDecodeTreeSize() const { return mProtocolTreeSize; } DecodeTree GetClusterDecodeTree() const { return mClusterTree; } size_t GetClusterDecodeTreeSize() const { return mClusterTreeSize; } Protocols::Id GetProtocol() const { return mProtocol; } uint8_t GetMessageType() const { return mMessageType; } private: DecodeTree mProtocolTree = nullptr; size_t mProtocolTreeSize = 0; DecodeTree mClusterTree = nullptr; size_t mClusterTreeSize = 0; Protocols::Id mProtocol = Protocols::NotSpecified; uint8_t mMessageType = 0; }; class PayloadDecoderBase { public: static constexpr size_t kMaxDecodeDepth = 16; using DecodePosition = chip::FlatTree::Position; PayloadDecoderBase(const PayloadDecoderInitParams & params, StringBuilderBase & nameBuilder, StringBuilderBase & valueBuilder); /// Initialize decoding from the given reader /// Will create a copy of the reader, however the copy will contain /// pointers in the original reader (so data must stay valid while Next is called) void StartDecoding(const TLV::TLVReader & reader); void StartDecoding(chip::ByteSpan data) { TLV::TLVReader reader; reader.Init(data); StartDecoding(reader); } /// Get the next decoded entry from the underlying TLV /// /// If a cluster decoder is set, then kAttribute/kCommand/kEvent are ALWAYS decoded /// (even if unknown tags), otherwise they will be returned as separate PayloadEntry values. /// /// Returns false when decoding finished. bool Next(PayloadEntry & entry); const TLV::TLVReader & ReadState() const { return mReader; } private: enum class State { kStarting, kValueRead, // reading value for Payload kContentRead, // reading value for IMContent (i.e. decoded attr/cmd/event) kDone, }; /// Move to the given attribute/event/command entry. /// /// [entry] MUST be of type command/attribute/event. /// /// This call either moves to "ContentDecoding mode" if content tree is available /// or leaves entry unchanged if content decoding tree is not available. void MoveToContent(PayloadEntry & entry); void NextFromStarting(PayloadEntry & entry); void NextFromValueRead(PayloadEntry & entry); void NextFromContentRead(PayloadEntry & entry); /// Enter the container in mReader. /// /// May change entry/state in case of errors. /// /// Returns false on error (and entry is changed in that case) bool ReaderEnterContainer(PayloadEntry & entry); /// Returns a "nesting enter" value, making sure that /// nesting depth is sufficient. void EnterContainer(PayloadEntry & entry); /// Returns a "nesting exit" value, making sure that /// nesting depth is sufficient. void ExitContainer(PayloadEntry & entry); const chip::Protocols::Id mProtocol; const uint8_t mMessageType; StringBuilderBase & mNameBuilder; StringBuilderBase & mValueBuilder; State mState = State::kStarting; TLV::TLVReader mReader; DecodePosition mPayloadPosition; DecodePosition mIMContentPosition; TLV::TLVType mNestingEnters[kMaxDecodeDepth]; size_t mCurrentNesting = 0; /// incremental state for parsing of paths uint32_t mClusterId = 0; uint32_t mAttributeId = 0; uint32_t mEventId = 0; uint32_t mCommandId = 0; }; template class PayloadDecoder : public PayloadDecoderBase { public: PayloadDecoder(const PayloadDecoderInitParams & params) : PayloadDecoderBase(std::move(params), mName, mValue) {} private: chip::StringBuilder mName; chip::StringBuilder mValue; }; } // namespace Decoders } // namespace chip