blob: 97d6dd857d9198ec565004f0fdc58fa3aa9ba86a [file] [log] [blame]
/*
*
* 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.
*/
#include <lib/format/protocol_decoder.h>
#include <lib/format/FlatTree.h>
#include <lib/format/FlatTreePosition.h>
namespace chip {
namespace Decoders {
namespace {
using namespace chip::TLV;
using chip::StringBuilderBase;
using chip::TLVMeta::AttributeTag;
using chip::TLVMeta::ClusterTag;
using chip::TLVMeta::CommandTag;
using chip::TLVMeta::ConstantValueTag;
using chip::TLVMeta::EventTag;
using chip::TLVMeta::ItemType;
class ByTag
{
public:
constexpr ByTag(Tag tag) : mTag(tag) {}
bool operator()(const chip::TLVMeta::ItemInfo & item) { return item.tag == mTag; }
private:
const Tag mTag;
};
CHIP_ERROR FormatCurrentValue(TLVReader & reader, chip::StringBuilderBase & out)
{
switch (reader.GetType())
{
case kTLVType_SignedInteger: {
int64_t sVal;
ReturnErrorOnFailure(reader.Get(sVal));
out.AddFormat("%" PRIi64, sVal);
break;
}
case kTLVType_UnsignedInteger: {
uint64_t uVal;
ReturnErrorOnFailure(reader.Get(uVal));
out.AddFormat("%" PRIu64, uVal);
break;
}
case kTLVType_Boolean: {
bool bVal;
ReturnErrorOnFailure(reader.Get(bVal));
out.Add(bVal ? "true" : "false");
break;
}
case kTLVType_FloatingPointNumber: {
double fpVal;
ReturnErrorOnFailure(reader.Get(fpVal));
out.AddFormat("%lf", fpVal);
break;
}
case kTLVType_UTF8String: {
const uint8_t * strbuf = nullptr;
ReturnErrorOnFailure(reader.GetDataPtr(strbuf));
out.AddFormat("\"%-.*s\"", static_cast<int>(reader.GetLength()), strbuf);
break;
}
case kTLVType_ByteString: {
const uint8_t * strbuf = nullptr;
ReturnErrorOnFailure(reader.GetDataPtr(strbuf));
out.Add("hex:");
for (uint32_t i = 0; i < reader.GetLength(); i++)
{
out.AddFormat("%02X", strbuf[i]);
}
break;
}
case kTLVType_Null:
out.Add("NULL");
break;
case kTLVType_NotSpecified:
out.Add("Not Specified");
break;
default:
out.Add("???");
break;
}
return CHIP_NO_ERROR;
}
// Returns a null terminated string containing the current reader value
void PrettyPrintCurrentValue(TLVReader & reader, chip::StringBuilderBase & out, PayloadDecoderBase::DecodePosition & position)
{
CHIP_ERROR err = FormatCurrentValue(reader, out);
if (err != CHIP_NO_ERROR)
{
out.AddFormat("ERR: %" CHIP_ERROR_FORMAT, err.Format());
return;
}
auto data = position.Get();
if (data == nullptr)
{
return;
}
// Report enum values in human readable form
if (data->type == ItemType::kEnum && (reader.GetType() == kTLVType_UnsignedInteger))
{
uint64_t value = 0;
VerifyOrReturn(reader.Get(value) == CHIP_NO_ERROR);
position.Enter(ByTag(ConstantValueTag(value)));
auto enum_data = position.Get();
if (enum_data != nullptr)
{
out.Add(" == ").Add(enum_data->name);
}
position.Exit();
}
if (data->type == ItemType::kBitmap && (reader.GetType() == kTLVType_UnsignedInteger))
{
uint64_t value = 0;
VerifyOrReturn(reader.Get(value) == CHIP_NO_ERROR);
uint64_t bit = 0x01;
bool first = true;
for (unsigned i = 0; i < 64; i++, bit <<= 1)
{
if ((value & bit) == 0)
{
continue;
}
// NOTE: this only can select individual bits;
position.Enter(ByTag(ConstantValueTag(bit)));
auto bitmap_data = position.Get();
if (bitmap_data == nullptr)
{
position.Exit();
continue;
}
// Try to pretty print the value
if (first)
{
out.Add(" == ");
first = false;
}
else
{
out.Add(" | ");
}
out.Add(bitmap_data->name);
value = value & (~bit);
position.Exit();
}
if (!first && value)
{
// Only append if some constants were found.
out.AddFormat(" | 0x%08" PRIX64, value);
}
}
}
} // namespace
PayloadDecoderBase::PayloadDecoderBase(const PayloadDecoderInitParams & params, StringBuilderBase & nameBuilder,
StringBuilderBase & valueBuilder) :
mProtocol(params.GetProtocol()),
mMessageType(params.GetMessageType()), mNameBuilder(nameBuilder), mValueBuilder(valueBuilder),
mPayloadPosition(params.GetProtocolDecodeTree(), params.GetProtocolDecodeTreeSize()),
mIMContentPosition(params.GetClusterDecodeTree(), params.GetClusterDecodeTreeSize())
{}
void PayloadDecoderBase::StartDecoding(const TLVReader & reader)
{
mReader = reader;
mPayloadPosition.ResetToTop();
mIMContentPosition.ResetToTop();
mCurrentNesting = 0;
mClusterId = 0;
mAttributeId = 0;
mEventId = 0;
mCommandId = 0;
mState = State::kStarting;
}
bool PayloadDecoderBase::Next(PayloadEntry & entry)
{
switch (mState)
{
case State::kStarting:
NextFromStarting(entry);
return true;
case State::kValueRead:
NextFromValueRead(entry);
return true;
case State::kContentRead:
NextFromContentRead(entry);
return true;
case State::kDone:
return false;
}
// should never happen
return false;
}
void PayloadDecoderBase::NextFromStarting(PayloadEntry & entry)
{
// Find the right protocol (fake cluster id)
mPayloadPosition.Enter(ByTag(ClusterTag(0xFFFF0000 | mProtocol.ToFullyQualifiedSpecForm())));
mPayloadPosition.Enter(ByTag(AttributeTag(mMessageType)));
auto data = mPayloadPosition.Get();
if (data == nullptr)
{
// do not try to decode unknown data. assume binary
mNameBuilder.Reset().AddFormat("PROTO(0x%" PRIX32 ", 0x%X)", mProtocol.ToFullyQualifiedSpecForm(), mMessageType);
mValueBuilder.Reset().Add("UNKNOWN");
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
mState = State::kDone;
return;
}
// name is known (so valid protocol)
if (mReader.GetTotalLength() == 0)
{
mState = State::kDone;
entry = PayloadEntry::SimpleValue(data->name, "");
return;
}
if (data->type == ItemType::kProtocolBinaryData)
{
mState = State::kDone;
entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
return;
}
CHIP_ERROR err = mReader.Next(kTLVType_Structure, AnonymousTag());
if (err != CHIP_NO_ERROR)
{
mValueBuilder.Reset().AddFormat("ERROR getting Anonymous Structure TLV: %" CHIP_ERROR_FORMAT, err.Format());
mState = State::kDone;
entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
return;
}
EnterContainer(entry);
}
void PayloadDecoderBase::ExitContainer(PayloadEntry & entry)
{
entry = PayloadEntry::NestingExit();
if (mCurrentNesting > 0)
{
if (mState == State::kContentRead)
{
mIMContentPosition.Exit();
if (mIMContentPosition.DescendDepth() <= 1)
{
// Lever for actual content is 2: cluster::attr/cmd/ev
mState = State::kValueRead;
mPayloadPosition.Exit();
}
}
else
{
mPayloadPosition.Exit();
}
mReader.ExitContainer(mNestingEnters[--mCurrentNesting]);
}
if (mCurrentNesting == 0)
{
mState = State::kDone;
}
}
bool PayloadDecoderBase::ReaderEnterContainer(PayloadEntry & entry)
{
if (mCurrentNesting >= kMaxDecodeDepth)
{
mValueBuilder.AddFormat("NESTING DEPTH REACHED");
mReader.GetTag().AppendTo(mNameBuilder.Reset());
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
return false;
}
TLVType containerType;
CHIP_ERROR err = mReader.EnterContainer(containerType);
if (err != CHIP_NO_ERROR)
{
mValueBuilder.AddFormat("ERROR entering container: %" CHIP_ERROR_FORMAT, err.Format());
mReader.GetTag().AppendTo(mNameBuilder.Reset()); // assume enter is not done, so tag is correct
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
mState = State::kDone;
return false;
}
mNestingEnters[mCurrentNesting++] = containerType;
return true;
}
void PayloadDecoderBase::EnterContainer(PayloadEntry & entry)
{
// Tag fetch must be done BEFORE entering container
chip::TLV::Tag tag = mReader.GetTag();
VerifyOrReturn(ReaderEnterContainer(entry));
const chip::TLVMeta::ItemInfo * data = nullptr;
if (mState == State::kContentRead)
{
data = mIMContentPosition.Get();
}
else
{
mState = State::kValueRead;
data = mPayloadPosition.Get();
}
if (data == nullptr)
{
tag.AppendTo(mNameBuilder.Reset());
entry = PayloadEntry::NestingEnter(mNameBuilder.c_str());
}
else
{
entry = PayloadEntry::NestingEnter(data->name);
}
}
void PayloadDecoderBase::NextFromContentRead(PayloadEntry & entry)
{
CHIP_ERROR err = mReader.Next();
if (err == CHIP_END_OF_TLV)
{
ExitContainer(entry);
return;
}
if (err != CHIP_NO_ERROR)
{
mValueBuilder.Reset().AddFormat("ERROR on TLV Next: %" CHIP_ERROR_FORMAT, err.Format());
entry = PayloadEntry::SimpleValue("TLV_ERR", mValueBuilder.c_str());
mState = State::kDone;
return;
}
if (mCurrentNesting > 0 && mNestingEnters[mCurrentNesting - 1] == kTLVType_List)
{
// Spec A5.3: `The members of a list may be encoded with any form of tag, including an anonymous tag.`
// TLVMeta always uses Anonymous
mIMContentPosition.Enter(ByTag(AnonymousTag()));
}
else
{
mIMContentPosition.Enter(ByTag(mReader.GetTag()));
}
auto data = mIMContentPosition.Get();
if (data != nullptr)
{
if (data->type == ItemType::kProtocolBinaryData)
{
mIMContentPosition.Exit();
entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
return;
}
}
if (TLVTypeIsContainer(mReader.GetType()))
{
EnterContainer(entry);
return;
}
PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mIMContentPosition);
mIMContentPosition.Exit();
if (data == nullptr)
{
mReader.GetTag().AppendTo(mNameBuilder.Reset());
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
return;
}
entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
}
void PayloadDecoderBase::MoveToContent(PayloadEntry & entry)
{
if (!mIMContentPosition.HasValidTree())
{
mPayloadPosition.Exit();
return;
}
VerifyOrDie((entry.GetType() == PayloadEntry::IMPayloadType::kAttribute) ||
(entry.GetType() == PayloadEntry::IMPayloadType::kCommand) ||
(entry.GetType() == PayloadEntry::IMPayloadType::kEvent));
mNameBuilder.Reset();
mIMContentPosition.ResetToTop();
mIMContentPosition.Enter(ByTag(ClusterTag(entry.GetClusterId())));
auto data = mIMContentPosition.Get();
if (data != nullptr)
{
mNameBuilder.AddFormat("%s::", data->name);
}
else
{
mNameBuilder.AddFormat("0x%" PRIx32 "::", entry.GetClusterId());
}
uint32_t id = 0;
const char * id_type = "UNKNOWN";
switch (entry.GetType())
{
case PayloadEntry::IMPayloadType::kAttribute:
mIMContentPosition.Enter(ByTag(AttributeTag(entry.GetAttributeId())));
id = entry.GetAttributeId();
id_type = "ATTR";
break;
case PayloadEntry::IMPayloadType::kCommand:
mIMContentPosition.Enter(ByTag(CommandTag(entry.GetCommandId())));
id = entry.GetCommandId();
id_type = "CMD";
break;
case PayloadEntry::IMPayloadType::kEvent:
mIMContentPosition.Enter(ByTag(EventTag(entry.GetEventId())));
id = entry.GetEventId();
id_type = "EV";
break;
default:
// never happens: verified all case above covered.
break;
}
data = mIMContentPosition.Get();
if (data != nullptr)
{
mNameBuilder.AddFormat("%s", data->name);
}
else
{
mNameBuilder.AddFormat("%s(0x%" PRIx32 ")", id_type, id);
}
if (TLVTypeIsContainer(mReader.GetType()))
{
mState = State::kContentRead;
entry = PayloadEntry::NestingEnter(mNameBuilder.c_str());
ReaderEnterContainer(entry);
}
else
{
PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mIMContentPosition);
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
// Can simply exit, only one value to return
mPayloadPosition.Exit();
}
}
void PayloadDecoderBase::NextFromValueRead(PayloadEntry & entry)
{
CHIP_ERROR err = mReader.Next();
if (err == CHIP_END_OF_TLV)
{
ExitContainer(entry);
return;
}
if (err != CHIP_NO_ERROR)
{
mValueBuilder.Reset().AddFormat("ERROR on TLV Next: %" CHIP_ERROR_FORMAT, err.Format());
entry = PayloadEntry::SimpleValue("TLV_ERR", mValueBuilder.c_str());
mState = State::kDone;
return;
}
// Attempt to find information about the current tag
mPayloadPosition.Enter(ByTag(mReader.GetTag()));
auto data = mPayloadPosition.Get();
// handle special types
if (data != nullptr)
{
if (data->type == ItemType::kProtocolBinaryData)
{
mPayloadPosition.Exit();
entry = PayloadEntry::SimpleValue(data->name, "BINARY DATA");
return;
}
if (data->type == ItemType::kProtocolPayloadAttribute)
{
entry = PayloadEntry::AttributePayload(mClusterId, mAttributeId);
MoveToContent(entry);
return;
}
if (data->type == ItemType::kProtocolPayloadCommand)
{
entry = PayloadEntry::CommandPayload(mClusterId, mCommandId);
MoveToContent(entry);
return;
}
if (data->type == ItemType::kProtocolPayloadEvent)
{
entry = PayloadEntry::EventPayload(mClusterId, mEventId);
MoveToContent(entry);
return;
}
}
if (TLVTypeIsContainer(mReader.GetType()))
{
EnterContainer(entry);
return;
}
if (data == nullptr)
{
mReader.GetTag().AppendTo(mNameBuilder.Reset());
PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mPayloadPosition);
entry = PayloadEntry::SimpleValue(mNameBuilder.c_str(), mValueBuilder.c_str());
mPayloadPosition.Exit();
return;
}
// at this point, data is "simple data" or "simple data with meaning"
const chip::TLVMeta::ItemInfo * info = nullptr;
switch (data->type)
{
case ItemType::kProtocolClusterId:
mReader.Get(mClusterId);
mIMContentPosition.ResetToTop();
mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
info = mIMContentPosition.Get();
break;
case ItemType::kProtocolAttributeId:
mReader.Get(mAttributeId);
mIMContentPosition.ResetToTop();
mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
mIMContentPosition.Enter(ByTag(AttributeTag(mAttributeId)));
info = mIMContentPosition.Get();
break;
case ItemType::kProtocolCommandId:
mReader.Get(mCommandId);
mIMContentPosition.ResetToTop();
mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
mIMContentPosition.Enter(ByTag(CommandTag(mCommandId)));
info = mIMContentPosition.Get();
break;
case ItemType::kProtocolEventId:
mReader.Get(mEventId);
mIMContentPosition.ResetToTop();
mIMContentPosition.Enter(ByTag(ClusterTag(mClusterId)));
mIMContentPosition.Enter(ByTag(EventTag(mEventId)));
info = mIMContentPosition.Get();
break;
default:
break;
}
PrettyPrintCurrentValue(mReader, mValueBuilder.Reset(), mPayloadPosition);
if (info != nullptr)
{
mValueBuilder.Add(" == '").Add(info->name).Add("'");
}
mPayloadPosition.Exit();
entry = PayloadEntry::SimpleValue(data->name, mValueBuilder.c_str());
}
} // namespace Decoders
} // namespace chip