Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ICD] Add OnPeerTypeChange for dynamic ICD #31340

Merged
16 changes: 16 additions & 0 deletions src/app/InteractionModelEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,22 @@ void InteractionModelEngine::OnActiveModeNotification(ScopedNodeId aPeer)
}
}

void InteractionModelEngine::OnPeerTypeChange(ScopedNodeId aPeer, ReadClient::PeerType aType)
{
// TODO: Follow up to use a iterator function to avoid copy/paste here.
for (ReadClient * pListItem = mpActiveReadClientList; pListItem != nullptr;)
{
// It is possible that pListItem is destroyed by the app in OnPeerTypeChange.
// Get the next item before invoking `OnPeerTypeChange`.
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
auto pNextItem = pListItem->GetNextClient();
if (ScopedNodeId(pListItem->GetPeerNodeId(), pListItem->GetFabricIndex()) == aPeer)
{
pListItem->OnPeerTypeChange(aType);
}
pListItem = pNextItem;
}
}

void InteractionModelEngine::AddReadClient(ReadClient * apReadClient)
{
apReadClient->SetNextClient(mpActiveReadClientList);
Expand Down
8 changes: 8 additions & 0 deletions src/app/InteractionModelEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
*/
void OnActiveModeNotification(ScopedNodeId aPeer);

/**
* Used to notify whether a peer becomes LIT ICD or vice versa.
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
*
* The read client will call this function when it found any updates of the OperationMode attribute from ICD management
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
* cluster. The application don't need to call this function usually.
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
*/
void OnPeerTypeChange(ScopedNodeId aPeer, ReadClient::PeerType aType);
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved

/**
* Add a read client to the internally tracked list of weak references. This list is used to
* correctly dispatch unsolicited reports to the right matching handler by subscription ID.
Expand Down
63 changes: 63 additions & 0 deletions src/app/ReadClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#include <messaging/ReliableMessageProtocolConfig.h>
#include <platform/LockTracker.h>

#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>

namespace chip {
namespace app {

Expand Down Expand Up @@ -451,6 +455,21 @@ void ReadClient::OnActiveModeNotification()
TriggerResubscriptionForLivenessTimeout(CHIP_ERROR_TIMEOUT);
}

void ReadClient::OnPeerTypeChange(PeerType aType)
{
VerifyOrDie(mpImEngine->InActiveReadClientList(this));

mIsPeerLIT = (aType == PeerType::kLITICD);

ChipLogProgress(DataManagement, "Peer is now %s LIT ICD.", mIsPeerLIT ? "a" : "not a");

// If the peer is no longer LIT, try to wake up the subscription and do resubsribe when necessary.
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
if (!mIsPeerLIT)
{
OnActiveModeNotification();
}
}

CHIP_ERROR ReadClient::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload)
{
Expand Down Expand Up @@ -663,6 +682,32 @@ void ReadClient::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContex
Close(CHIP_ERROR_TIMEOUT);
}

CHIP_ERROR ReadClient::ReadICDOperationModeFromAttributeDataIB(const TLV::TLVReader & aReader, PeerType & aType)
{
TLV::TLVReader reader;
reader.Init(aReader);

Clusters::IcdManagement::Attributes::OperatingMode::TypeInfo::DecodableType operatingMode;
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved

CHIP_ERROR err = DataModel::Decode(reader, operatingMode);
ReturnErrorOnFailure(err);

switch (operatingMode)
{
case Clusters::IcdManagement::OperatingModeEnum::kSit:
aType = PeerType::kNormal;
break;
case Clusters::IcdManagement::OperatingModeEnum::kLit:
aType = PeerType::kLITICD;
break;
default:
err = CHIP_ERROR_INVALID_ARGUMENT;
break;
}

return err;
}

CHIP_ERROR ReadClient::ProcessAttributePath(AttributePathIB::Parser & aAttributePathParser,
ConcreteDataAttributePath & aAttributePath)
{
Expand Down Expand Up @@ -757,6 +802,24 @@ CHIP_ERROR ReadClient::ProcessAttributeReportIBs(TLV::TLVReader & aAttributeRepo
attributePath.mListOp = ConcreteDataAttributePath::ListOperation::ReplaceAll;
}

if (attributePath ==
ConcreteDataAttributePath(kRootEndpointId, Clusters::IcdManagement::Id,
Clusters::IcdManagement::Attributes::OperatingMode::Id))
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
{
PeerType peerType;
if (CHIP_NO_ERROR == ReadICDOperationModeFromAttributeDataIB(dataReader, peerType))
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
{
// Since we are in the middle of parsing the attribute data, it is safe to call `OnPeerTypeChange`
// As it will only update the LIT / SIT bit for us without triggerring resubscription on this read client.
erjiaqing marked this conversation as resolved.
Show resolved Hide resolved
InteractionModelEngine::GetInstance()->OnPeerTypeChange(mPeer, peerType);
}
else
{
ChipLogError(DataManagement, "Failed to get ICD state from attribute data with error'%" CHIP_ERROR_FORMAT "'",
err.Format());
}
}

NoteReportingData();
mpCallback.OnAttributeData(attributePath, &dataReader, statusIB);
}
Expand Down
16 changes: 16 additions & 0 deletions src/app/ReadClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ class ReadClient : public Messaging::ExchangeDelegate
Subscribe,
};

enum class PeerType : uint8_t
{
kNormal,
kLITICD,
};

/**
*
* Constructor.
Expand Down Expand Up @@ -524,6 +530,15 @@ class ReadClient : public Messaging::ExchangeDelegate
System::PacketBufferHandle && aPayload) override;
void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;

/**
* Updates the type (LIT ICD or not) of the peer.
*
* When the subscription is active, this function will just set the flag. When the subscription is an InactiveICDSubscription,
* Setting the peer type to SIT or normal devices will also trigger resubscription.
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
*
*/
void OnPeerTypeChange(PeerType aType);

/**
* Check if current read client is being used
*
Expand All @@ -544,6 +559,7 @@ class ReadClient : public Messaging::ExchangeDelegate
CHIP_ERROR BuildDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
const Span<AttributePathParams> & aAttributePaths,
const Span<DataVersionFilter> & aDataVersionFilters, bool & aEncodedDataVersionList);
CHIP_ERROR ReadICDOperationModeFromAttributeDataIB(const TLV::TLVReader & aReader, PeerType & aType);
CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader);
CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader);

Expand Down
132 changes: 132 additions & 0 deletions src/controller/tests/data_model/TestRead.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ ResponseDirective responseDirective;
// Every read will increment this count by 1 and return the new value.
uint16_t totalReadCount = 0;

bool isLitIcd = false;

} // namespace

namespace chip {
Expand Down Expand Up @@ -169,6 +171,17 @@ CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescr
return err;
}
}
if (aPath.mClusterId == app::Clusters::IcdManagement::Id &&
aPath.mAttributeId == app::Clusters::IcdManagement::Attributes::OperatingMode::Id)
{
AttributeValueEncoder::AttributeEncodeState state =
(apEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aSubjectDescriptor.fabricIndex, aPath,
kDataVersion /* data version */, aIsFabricFiltered, state);

return valueEncoder.Encode(isLitIcd ? Clusters::IcdManagement::OperatingModeEnum::kLit
: Clusters::IcdManagement::OperatingModeEnum::kSit);
}

AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport();
ReturnErrorOnFailure(aAttributeReports.GetError());
Expand Down Expand Up @@ -298,6 +311,7 @@ class TestReadInteraction : public app::ReadHandler::ApplicationCallback
static void TestReadHandler_KeepSubscriptionTest(nlTestSuite * apSuite, void * apContext);
static void TestSubscribe_OnActiveModeNotification(nlTestSuite * apSuite, void * apContext);
static void TestSubscribe_ImmediatelyResubscriptionForLIT(nlTestSuite * apSuite, void * apContext);
static void TestSubscribe_DynamicLITSubscription(nlTestSuite * apSuite, void * apContext);

private:
static uint16_t mMaxInterval;
Expand Down Expand Up @@ -2722,6 +2736,123 @@ void TestReadInteraction::TestSubscribe_OnActiveModeNotification(nlTestSuite * a
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
}

/**
* When the liveness timeout of a subscription to ICD is reached, the subscription will enter "InactiveICDSubscription" state, the
* client should call "OnActiveModeNotification" to re-activate it again when the check-in message is received from the ICD.
*/
void TestReadInteraction::TestSubscribe_DynamicLITSubscription(nlTestSuite * apSuite, void * apContext)
{
TestContext & ctx = *static_cast<TestContext *>(apContext);
auto sessionHandle = ctx.GetSessionBobToAlice();

ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kResponsive);

{
TestResubscriptionCallback callback;
app::ReadClient readClient(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), callback,
app::ReadClient::InteractionType::Subscribe);

responseDirective = kSendDataResponse;
callback.mScheduleLITResubscribeImmediately = false;
callback.SetReadClient(&readClient);
isLitIcd = false;

app::ReadPrepareParams readPrepareParams(ctx.GetSessionBobToAlice());

// Read full wildcard paths, repeat twice to ensure chunking.
app::AttributePathParams attributePathParams[1];
readPrepareParams.mpAttributePathParamsList = attributePathParams;
readPrepareParams.mAttributePathParamsListSize = ArraySize(attributePathParams);
attributePathParams[0].mEndpointId = kRootEndpointId;
attributePathParams[0].mClusterId = app::Clusters::IcdManagement::Id;
attributePathParams[0].mAttributeId = app::Clusters::IcdManagement::Attributes::OperatingMode::Id;

constexpr uint16_t maxIntervalCeilingSeconds = 1;

readPrepareParams.mMaxIntervalCeilingSeconds = maxIntervalCeilingSeconds;
readPrepareParams.mIsPeerLIT = true;

auto err = readClient.SendAutoResubscribeRequest(std::move(readPrepareParams));
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);

//
// Drive servicing IO till we have established a subscription.
//
ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000),
[&]() { return callback.mOnSubscriptionEstablishedCount >= 1; });
NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1);
NL_TEST_ASSERT(apSuite, callback.mOnError == 0);
NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 0);
chip::app::ReadHandler * readHandler = app::InteractionModelEngine::GetInstance()->ActiveHandlerAt(0);

uint16_t minInterval;
uint16_t maxInterval;
readHandler->GetReportingIntervals(minInterval, maxInterval);

// Part 1. LIT -> SIT

//
// Disable packet transmission, and drive IO till timeout.
// We won't actually request resubscription, since the device is not active, the resubscription will be deferred until
// WakeUp() is called.
//
// Even if we set the peer type to LIT before, the report indicates that the peer is a SIT now, it will just bahve as
// normal, non-LIT subscriptions.
ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount;
ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)),
[&]() { return callback.mOnResubscriptionsAttempted != 0; });
NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1);
NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_TIMEOUT);

ctx.GetLoopback().mNumMessagesToDrop = 0;
callback.ClearCounters();

//
// Drive servicing IO till we have established a subscription.
//
ctx.GetIOContext().DriveIOUntil(System::Clock::Milliseconds32(2000),
[&]() { return callback.mOnSubscriptionEstablishedCount == 1; });
NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1);

//
// With re-sub enabled, we shouldn't have encountered any errors
//
NL_TEST_ASSERT(apSuite, callback.mOnError == 0);
NL_TEST_ASSERT(apSuite, callback.mOnDone == 0);

// Part 2. SIT -> LIT

isLitIcd = true;
{
app::AttributePathParams path;
path.mEndpointId = kRootEndpointId;
path.mClusterId = Clusters::IcdManagement::Id;
path.mAttributeId = Clusters::IcdManagement::Attributes::OperatingMode::Id;
app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(path);
}
callback.ClearCounters();
ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() {
return app::InteractionModelEngine::GetInstance()->GetNumDirtySubscriptions() == 0;
});

// When we received the update that OperatingMode becomes LIT, we automatically set the inner peer type to LIT ICD.
ctx.GetLoopback().mNumMessagesToDrop = chip::Test::LoopbackTransport::kUnlimitedMessageCount;
ctx.GetIOContext().DriveIOUntil(ComputeSubscriptionTimeout(System::Clock::Seconds16(maxInterval)), [&]() { return false; });
NL_TEST_ASSERT(apSuite, callback.mOnResubscriptionsAttempted == 1);
NL_TEST_ASSERT(apSuite, callback.mLastError == CHIP_ERROR_LIT_SUBSCRIBE_INACTIVE_TIMEOUT);

ctx.GetLoopback().mNumMessagesToDrop = 0;
callback.ClearCounters();
}

ctx.SetMRPMode(chip::Test::MessagingContext::MRPMode::kDefault);

app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();
NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);

isLitIcd = false;
}

/**
* When the liveness timeout of a subscription to ICD is reached, the app can issue resubscription immediately
* if they know the peer is active.
Expand Down Expand Up @@ -4920,6 +5051,7 @@ const nlTest sTests[] =
NL_TEST_DEF("TestReadHandler_KeepSubscriptionTest", TestReadInteraction::TestReadHandler_KeepSubscriptionTest),
NL_TEST_DEF("TestSubscribe_OnActiveModeNotification", TestReadInteraction::TestSubscribe_OnActiveModeNotification),
NL_TEST_DEF("TestSubscribe_ImmediatelyResubscriptionForLIT", TestReadInteraction::TestSubscribe_ImmediatelyResubscriptionForLIT),
NL_TEST_DEF("TestSubscribe_DynamicLITSubscription", TestReadInteraction::TestSubscribe_DynamicLITSubscription),
NL_TEST_SENTINEL()
};
// clang-format on
Expand Down
Loading