/* * * Copyright (c) 2024 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 #include #include #include #include #include namespace { using namespace chip; using namespace chip::app; using namespace chip::app::DataModel; using StartUpEventType = chip::app::Clusters::BasicInformation::Events::StartUp::Type; using AccessControlEntryChangedType = chip::app::Clusters::AccessControl::Events::AccessControlEntryChanged::Type; constexpr uint32_t kFakeSoftwareVersion = 0x1234abcd; /// Keeps the "last event" in-memory to allow tests to validate /// that event writing and encoding worked. class LogOnlyEvents : public EventsGenerator { public: CHIP_ERROR GenerateEvent(EventLoggingDelegate * eventContentWriter, const EventOptions & options, EventNumber & generatedEventNumber) override { TLV::TLVWriter writer; TLV::TLVType outerType; writer.Init(mLastEventEncodeBuffer); ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); ReturnErrorOnFailure(eventContentWriter->WriteEvent(writer)); ReturnErrorOnFailure(writer.EndContainer(outerType)); ReturnErrorOnFailure(writer.Finalize()); mLastEncodedSpan = ByteSpan(mLastEventEncodeBuffer, writer.GetLengthWritten()); mLastOptions = options; generatedEventNumber = ++mCurrentEventNumber; return CHIP_NO_ERROR; } EventNumber CurrentEventNumber() const { return mCurrentEventNumber; } const EventOptions & LastOptions() const { return mLastOptions; } ByteSpan LastWrittenEvent() const { return mLastEncodedSpan; } // This relies on the default encoding of events which uses // DataModel::Encode on a EventDataIB::Tag::kData template CHIP_ERROR DecodeLastEvent(T & dest) { // attempt to decode the last encoded event TLV::TLVReader reader; TLV::TLVType outerType; reader.Init(LastWrittenEvent()); ReturnErrorOnFailure(reader.Next()); ReturnErrorOnFailure(reader.EnterContainer(outerType)); ReturnErrorOnFailure(reader.Next()); // MUST be positioned on the first element ReturnErrorOnFailure(DataModel::Decode(reader, dest)); ReturnErrorOnFailure(reader.ExitContainer(outerType)); return CHIP_NO_ERROR; } private: EventNumber mCurrentEventNumber = 0; EventOptions mLastOptions; uint8_t mLastEventEncodeBuffer[128]; ByteSpan mLastEncodedSpan; }; } // namespace TEST(TestInteractionModelEventEmitting, TestBasicType) { LogOnlyEvents logOnlyEvents; EventsGenerator * events = &logOnlyEvents; StartUpEventType event{ kFakeSoftwareVersion }; std::optional n1 = events->GenerateEvent(event, 0 /* EndpointId */); ASSERT_EQ(n1, logOnlyEvents.CurrentEventNumber()); ASSERT_EQ(logOnlyEvents.LastOptions().mPath, ConcreteEventPath(0 /* endpointId */, StartUpEventType::GetClusterId(), StartUpEventType::GetEventId())); chip::app::Clusters::BasicInformation::Events::StartUp::DecodableType decoded_event; CHIP_ERROR err = logOnlyEvents.DecodeLastEvent(decoded_event); if (err != CHIP_NO_ERROR) { ChipLogError(EventLogging, "Decoding failed: %" CHIP_ERROR_FORMAT, err.Format()); } ASSERT_EQ(err, CHIP_NO_ERROR); ASSERT_EQ(decoded_event.softwareVersion, kFakeSoftwareVersion); std::optional n2 = events->GenerateEvent(event, /* endpointId = */ 1); ASSERT_EQ(n2, logOnlyEvents.CurrentEventNumber()); ASSERT_EQ(logOnlyEvents.LastOptions().mPath, ConcreteEventPath(1 /* endpointId */, StartUpEventType::GetClusterId(), StartUpEventType::GetEventId())); } TEST(TestInteractionModelEventEmitting, TestFabricScoped) { constexpr NodeId kTestNodeId = 0x12ab; constexpr uint16_t kTestPasscode = 12345; constexpr FabricIndex kTestFabricIndex = kMinValidFabricIndex + 10; static_assert(kTestFabricIndex != kUndefinedFabricIndex); LogOnlyEvents logOnlyEvents; EventsGenerator * events = &logOnlyEvents; AccessControlEntryChangedType event; event.adminNodeID = chip::app::DataModel::MakeNullable(kTestNodeId); event.adminPasscodeID = chip::app::DataModel::MakeNullable(kTestPasscode); std::optional n1 = events->GenerateEvent(event, 0 /* EndpointId */); // encoding without a fabric ID MUST fail for fabric events ASSERT_FALSE(n1.has_value()); event.fabricIndex = kTestFabricIndex; n1 = events->GenerateEvent(event, /* endpointId = */ 0); ASSERT_EQ(n1, logOnlyEvents.CurrentEventNumber()); ASSERT_EQ(logOnlyEvents.LastOptions().mPath, ConcreteEventPath(0 /* endpointId */, AccessControlEntryChangedType::GetClusterId(), AccessControlEntryChangedType::GetEventId())); chip::app::Clusters::AccessControl::Events::AccessControlEntryChanged::DecodableType decoded_event; CHIP_ERROR err = logOnlyEvents.DecodeLastEvent(decoded_event); if (err != CHIP_NO_ERROR) { ChipLogError(EventLogging, "Decoding failed: %" CHIP_ERROR_FORMAT, err.Format()); } ASSERT_EQ(err, CHIP_NO_ERROR); ASSERT_EQ(decoded_event.adminNodeID.ValueOr(0), kTestNodeId); ASSERT_EQ(decoded_event.adminPasscodeID.ValueOr(0), kTestPasscode); ASSERT_EQ(decoded_event.fabricIndex, kTestFabricIndex); }