blob: abc485a6c96ec1dda704d6784c4c855ee66fafad [file] [log] [blame]
#include "GroupcastCluster.h"
#include <access/AccessControl.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <clusters/Groupcast/AttributeIds.h>
#include <clusters/Groupcast/Attributes.h>
#include <clusters/Groupcast/Metadata.h>
#include <credentials/GroupDataProvider.h>
using chip::Protocols::InteractionModel::Status;
using namespace chip::Credentials;
using GroupDataProvider = GroupDataProvider;
using GroupInfo = GroupDataProvider::GroupInfo;
using GroupEndpoint = GroupDataProvider::GroupEndpoint;
using GroupInfoIterator = GroupDataProvider::GroupInfoIterator;
using EndpointIterator = GroupDataProvider::EndpointIterator;
namespace chip {
namespace app {
namespace Clusters {
namespace {
constexpr DataModel::AcceptedCommandEntry kAcceptedCommands[] = {
Groupcast::Commands::JoinGroup::kMetadataEntry, Groupcast::Commands::LeaveGroup::kMetadataEntry,
Groupcast::Commands::UpdateGroupKey::kMetadataEntry, Groupcast::Commands::ConfigureAuxiliaryACL::kMetadataEntry,
Groupcast::Commands::GroupcastTesting::kMetadataEntry,
};
constexpr CommandId kGeneratedCommands[] = {
Groupcast::Commands::LeaveGroupResponse::Id,
};
} // namespace
GroupcastCluster::GroupcastCluster(GroupcastContext && context) : GroupcastCluster(std::move(context), {}) {}
GroupcastCluster::GroupcastCluster(GroupcastContext && context, BitFlags<Groupcast::Feature> features) :
DefaultServerCluster({ kRootEndpointId, Groupcast::Id }), mContext(std::move(context)), mFeatures(features),
mMembershipChangedTimer(*this), mGroupcastTestingTimer(*this)
{
mContext.groupDataProvider.SetListener(this);
UpdateUsedMcastAddrCount();
}
GroupcastCluster::~GroupcastCluster() {}
CHIP_ERROR GroupcastCluster::Startup(ServerClusterContext & context)
{
ReturnErrorOnFailure(DefaultServerCluster::Startup(context));
mContext.groupDataProvider.SetListener(this);
SetDataModelProvider(context.provider);
return CHIP_NO_ERROR;
}
void GroupcastCluster::Shutdown(ClusterShutdownType shutdownType)
{
mGroupcastTestingTimer.Cancel();
mMembershipChangedTimer.Cancel();
mContext.groupDataProvider.RemoveListener(this);
ResetDataModelProvider();
DefaultServerCluster::Shutdown(shutdownType);
}
DataModel::ActionReturnStatus GroupcastCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
switch (request.path.mAttributeId)
{
case Groupcast::Attributes::FeatureMap::Id:
return encoder.Encode(Features());
case Groupcast::Attributes::ClusterRevision::Id:
return encoder.Encode(Groupcast::kRevision);
case Groupcast::Attributes::Membership::Id:
return ReadMembership(request.subjectDescriptor, request.path.mEndpointId, encoder);
case Groupcast::Attributes::MaxMembershipCount::Id:
return ReadMaxMembershipCount(request.path.mEndpointId, encoder);
case Groupcast::Attributes::MaxMcastAddrCount::Id:
return ReadMaxMcastAddrCount(request.path.mEndpointId, encoder);
case Groupcast::Attributes::UsedMcastAddrCount::Id:
return ReadUsedMcastAddrCount(request.path.mEndpointId, encoder);
case Groupcast::Attributes::FabricUnderTest::Id:
return encoder.Encode(mFabricUnderTest);
}
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}
CHIP_ERROR GroupcastCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
AttributeListBuilder listBuilder(builder);
return listBuilder.Append(Span(Groupcast::Attributes::kMandatoryMetadata), {});
}
std::optional<DataModel::ActionReturnStatus> GroupcastCluster::InvokeCommand(const DataModel::InvokeRequest & request,
chip::TLV::TLVReader & arguments,
CommandHandler * handler)
{
VerifyOrReturnValue(nullptr != handler, Protocols::InteractionModel::Status::InvalidAction);
FabricIndex fabric_index = handler->GetAccessingFabricIndex();
Protocols::InteractionModel::Status status = Protocols::InteractionModel::Status::UnsupportedCommand;
switch (request.path.mCommandId)
{
case Groupcast::Commands::JoinGroup::Id: {
Groupcast::Commands::JoinGroup::DecodableType data;
ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
status = JoinGroup(fabric_index, data);
}
break;
case Groupcast::Commands::LeaveGroup::Id: {
Groupcast::Commands::LeaveGroup::DecodableType data;
Groupcast::Commands::LeaveGroupResponse::Type response;
EndpointList endpoints;
ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
status = LeaveGroup(fabric_index, data, endpoints);
if (Protocols::InteractionModel::Status::Success == status)
{
NotifyAttributeChanged(Groupcast::Attributes::Membership::Id);
response.groupID = data.groupID;
response.endpoints = DataModel::List<const chip::EndpointId>(endpoints.entries, endpoints.count);
handler->AddResponse(request.path, response);
return std::nullopt; // Response added, must return nullopt.
}
}
break;
case Groupcast::Commands::UpdateGroupKey::Id: {
Groupcast::Commands::UpdateGroupKey::DecodableType data;
ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
status = UpdateGroupKey(fabric_index, data);
}
break;
case Groupcast::Commands::ConfigureAuxiliaryACL::Id: {
Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType data;
ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
status = ConfigureAuxiliaryACL(fabric_index, data);
}
break;
case Groupcast::Commands::GroupcastTesting::Id: {
Groupcast::Commands::GroupcastTesting::DecodableType data;
ReturnErrorOnFailure(data.Decode(arguments, fabric_index));
return GroupcastTesting(fabric_index, data);
}
break;
default:
break;
}
return status;
}
CHIP_ERROR GroupcastCluster::AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
{
return builder.ReferenceExisting(kAcceptedCommands);
}
CHIP_ERROR GroupcastCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder)
{
return builder.ReferenceExisting(kGeneratedCommands);
}
Status GroupcastCluster::GroupcastTesting(FabricIndex fabricIndex, Groupcast::Commands::GroupcastTesting::DecodableType data)
{
VerifyOrReturnError(mFabricUnderTest == kUndefinedFabricIndex || mFabricUnderTest == fabricIndex, Status::ConstraintError);
if (data.testOperation == Groupcast::GroupcastTestingEnum::kDisableTesting)
{
// cancel any existing GroupcastTesting timer
mGroupcastTestingTimer.Cancel();
mTestingState = data.testOperation;
SetFabricUnderTest(kUndefinedFabricIndex);
return Status::Success;
}
constexpr uint16_t kDefaultDurationSeconds = 60;
uint16_t durationSeconds = kDefaultDurationSeconds;
if (data.durationSeconds.HasValue())
{
constexpr uint16_t kMinDurationSeconds = 10, kMaxDurationSeconds = 1200;
VerifyOrReturnError(data.durationSeconds.Value() >= kMinDurationSeconds &&
data.durationSeconds.Value() <= kMaxDurationSeconds,
Status::ConstraintError);
durationSeconds = data.durationSeconds.Value();
}
mGroupcastTestingTimer.Start(durationSeconds);
mTestingState = data.testOperation;
SetFabricUnderTest(fabricIndex);
return Status::Success;
}
void GroupcastCluster::SetFabricUnderTest(FabricIndex fabricUnderTest)
{
SetAttributeValue(mFabricUnderTest, fabricUnderTest, Groupcast::Attributes::FabricUnderTest::Id);
}
// MembershipChangedTimer implementation
void GroupcastCluster::MembershipChangedTimer::Start()
{
VerifyOrReturn(!mCluster.GetTimerDelegate().IsTimerActive(this));
constexpr System::Clock::Milliseconds32 kChangeTemporisation = System::Clock::Milliseconds32(250);
ReturnAndLogOnFailure(mCluster.GetTimerDelegate().StartTimer(this, kChangeTemporisation), AppServer,
"Failed to start MembershipChangedTimer");
}
void GroupcastCluster::MembershipChangedTimer::Cancel()
{
mCluster.GetTimerDelegate().CancelTimer(this);
}
void GroupcastCluster::MembershipChangedTimer::TimerFired()
{
mCluster.NotifyAttributeChanged(Groupcast::Attributes::Membership::Id);
}
// GroupcastTestingTimer implementation
void GroupcastCluster::GroupcastTestingTimer::Start(uint32_t seconds)
{
Cancel();
ReturnAndLogOnFailure(mCluster.GetTimerDelegate().StartTimer(this, System::Clock::Seconds32(seconds)), AppServer,
"Failed to start GroupcastTestingTimer");
}
void GroupcastCluster::GroupcastTestingTimer::Cancel()
{
if (mCluster.GetTimerDelegate().IsTimerActive(this))
{
mCluster.GetTimerDelegate().CancelTimer(this);
}
}
void GroupcastCluster::GroupcastTestingTimer::TimerFired()
{
mCluster.SetFabricUnderTest(kUndefinedFabricIndex);
mCluster.mTestingState = Groupcast::GroupcastTestingEnum::kDisableTesting;
}
CHIP_ERROR GroupcastCluster::ReadMembership(const chip::Access::SubjectDescriptor * subject, EndpointId endpoint,
AttributeValueEncoder & aEncoder)
{
VerifyOrReturnError(nullptr != subject, CHIP_ERROR_INVALID_ARGUMENT);
FabricIndex fabric_index = subject->fabricIndex;
GroupDataProvider * groups = &Provider();
CHIP_ERROR err = aEncoder.EncodeList([fabric_index, groups, this](const auto & encoder) -> CHIP_ERROR {
EndpointList endpoints;
CHIP_ERROR status = CHIP_NO_ERROR;
GroupInfoIterator * group_iter = groups->IterateGroupInfo(fabric_index);
VerifyOrReturnError(nullptr != group_iter, CHIP_ERROR_NO_MEMORY);
GroupInfo info;
while (group_iter->Next(info) && (CHIP_NO_ERROR == status))
{
// Group Key
KeysetId keyset_id = kInvalidKeysetId;
// Since keys are managed by the GroupKeyManagement cluster, groups may not have an associated keyset
status = groups->GetGroupKey(fabric_index, info.group_id, keyset_id).NoErrorIf(CHIP_ERROR_NOT_FOUND);
if (CHIP_NO_ERROR != status)
{
break;
}
// Endpoints
EndpointIterator * end_iter = groups->IterateEndpoints(fabric_index, info.group_id);
if (nullptr == end_iter)
{
status = CHIP_ERROR_NO_MEMORY;
break;
}
Groupcast::Structs::MembershipStruct::Type group;
group.fabricIndex = fabric_index;
group.groupID = info.group_id;
group.keySetID = keyset_id;
group.mcastAddrPolicy = info.UsePerGroupAddress() ? Groupcast::MulticastAddrPolicyEnum::kPerGroup
: Groupcast::MulticastAddrPolicyEnum::kIanaAddr;
if (mFeatures.Has(Groupcast::Feature::kListener))
{
group.hasAuxiliaryACL = MakeOptional(info.HasAuxiliaryACL());
}
// Return endpoints in kMaxMembershipEndpoints chunks or less
size_t group_total = end_iter->Count();
size_t group_count = 0;
size_t split_count = 0;
GroupEndpoint mapping;
while (end_iter->Next(mapping) && (CHIP_NO_ERROR == status))
{
group_count++;
endpoints.entries[split_count++] = mapping.endpoint_id;
if ((group_count == group_total) || (split_count == kMaxMembershipEndpoints))
{
group.endpoints = MakeOptional(DataModel::List<const chip::EndpointId>(endpoints.entries, split_count));
status = encoder.Encode(group);
split_count = 0;
}
}
end_iter->Release();
if (group_count == 0)
{
if (mFeatures.Has(Groupcast::Feature::kListener))
{
// If listener is supported, the membership SHALL contain the endpoints list.
group.endpoints = MakeOptional(DataModel::List<const chip::EndpointId>());
}
status = encoder.Encode(group);
}
}
group_iter->Release();
return status;
});
return err;
}
CHIP_ERROR GroupcastCluster::ReadMaxMembershipCount(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
GroupDataProvider & groups = Provider();
return aEncoder.Encode(groups.getMaxMembershipCount());
}
CHIP_ERROR GroupcastCluster::ReadMaxMcastAddrCount(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
GroupDataProvider & groups = Provider();
return aEncoder.Encode(groups.getMaxMcastAddrCount());
}
CHIP_ERROR GroupcastCluster::ReadUsedMcastAddrCount(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
return aEncoder.Encode(mUsedMcastAddrCount);
}
Status GroupcastCluster::JoinGroup(FabricIndex fabric_index, const Groupcast::Commands::JoinGroup::DecodableType & data)
{
GroupDataProvider & groups = Provider();
CHIP_ERROR err = CHIP_NO_ERROR;
// Check groupID
VerifyOrReturnError(data.groupID != kUndefinedGroupId, Status::ConstraintError);
// Check useAuxiliaryACL
if (data.useAuxiliaryACL.HasValue())
{
// AuxiliaryACL can only be present if LN feature is supported
VerifyOrReturnError(mFeatures.Has(Groupcast::Feature::kListener), Status::ConstraintError);
}
// ReplaceEndpoints can only be present if kListener feature is supported
VerifyOrReturnError(!data.replaceEndpoints.HasValue() || mFeatures.Has(Groupcast::Feature::kListener), Status::ConstraintError);
if (data.mcastAddrPolicy.HasValue() && data.mcastAddrPolicy.Value() == Groupcast::MulticastAddrPolicyEnum::kPerGroup)
{
VerifyOrReturnError(mFeatures.Has(Groupcast::Feature::kPerGroup), Status::ConstraintError);
}
// Check endpoints
size_t endpoint_count = 0;
err = data.endpoints.ComputeSize(&endpoint_count);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
if (mFeatures.Has(Groupcast::Feature::kListener) && !mFeatures.Has(Groupcast::Feature::kSender))
{
// Listener only, endpoints cannot be empty
VerifyOrReturnError(endpoint_count > 0 && endpoint_count <= kMaxCommandEndpoints, Status::ConstraintError);
}
else if (!mFeatures.Has(Groupcast::Feature::kListener) && mFeatures.Has(Groupcast::Feature::kSender))
{
// Sender only, endpoints must be empty
VerifyOrReturnError(0 == endpoint_count, Status::ConstraintError);
}
// Verify endpoint values
{
// The endpoint list SHALL not contain the root endpoint and must be a valid endpoint on the device.
VerifyOrDie(mDataModelProvider != nullptr);
ReadOnlyBufferBuilder<DataModel::EndpointEntry> endpointsBuilder;
VerifyOrReturnError(CHIP_NO_ERROR == mDataModelProvider->Endpoints(endpointsBuilder), Status::Failure);
auto endpointsSpan = endpointsBuilder.TakeBuffer();
auto iter = data.endpoints.begin();
while (iter.Next())
{
bool foundEndpoint = false;
EndpointId ep = iter.GetValue();
VerifyOrReturnError((ep > kRootEndpointId), Status::UnsupportedEndpoint);
for (const auto & epEntry : endpointsSpan)
{
if (epEntry.id == ep)
{
foundEndpoint = true;
break;
}
}
VerifyOrReturnError(foundEndpoint, Status::UnsupportedEndpoint);
}
}
// Check fabric membership entries limit
GroupDataProvider::GroupInfo info;
err = groups.GetGroupInfo(fabric_index, data.groupID, info);
bool is_new_group = (CHIP_ERROR_NOT_FOUND == err);
VerifyOrReturnError(is_new_group || (CHIP_NO_ERROR == err), Status::Failure);
// If the group is new, the fabric entries will increase
uint16_t new_count = (is_new_group) ? info.count + 1 : info.count;
uint16_t max_fabric_memberships = static_cast<uint16_t>(groups.getMaxMembershipCount() / 2);
VerifyOrReturnError(new_count <= max_fabric_memberships, Status::ResourceExhausted);
// Gather group info
info.group_id = data.groupID;
info.flags = 0;
if (data.useAuxiliaryACL.HasValue() && data.useAuxiliaryACL.Value())
{
info.flags |= chip::to_underlying(GroupInfo::Flags::kHasAuxiliaryACL);
}
if (data.mcastAddrPolicy.HasValue() && (Groupcast::MulticastAddrPolicyEnum::kPerGroup == data.mcastAddrPolicy.Value()))
{
// PerGroup address
VerifyOrReturnError(!is_new_group || (mUsedMcastAddrCount < groups.getMaxMcastAddrCount()), Status::ResourceExhausted);
info.flags |= chip::to_underlying(GroupInfo::Flags::kMcastAddrPolicy);
}
else
{
// Iana address
VerifyOrReturnError(!is_new_group || (mIanaAddressUsed || (mUsedMcastAddrCount < groups.getMaxMcastAddrCount())),
Status::ResourceExhausted);
}
// Key handling
Status stat = SetKeySet(fabric_index, data.groupID, data.keySetID, data.key);
VerifyOrReturnError(Status::Success == stat, stat);
// Add/update entry in the group table
err = groups.SetGroupInfo(fabric_index, info);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
if (data.replaceEndpoints.HasValue() && data.replaceEndpoints.Value())
{
// Replace endpoints
err = groups.RemoveEndpoints(fabric_index, data.groupID);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
}
// Add Endpoints
{
size_t group_count = 0;
auto iter = data.endpoints.begin();
while (iter.Next() && (group_count++ < kMaxCommandEndpoints))
{
err = groups.AddEndpoint(fabric_index, data.groupID, iter.GetValue());
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
}
}
return Status::Success;
}
Status GroupcastCluster::LeaveGroup(FabricIndex fabric_index, const Groupcast::Commands::LeaveGroup::DecodableType & data,
EndpointList & endpoints)
{
GroupDataProvider & groups = Provider();
Status err = Status::Success;
endpoints.count = 0;
if (kUndefinedGroupId == data.groupID)
{
// Apply changes to all groups
GroupInfoIterator * iter = groups.IterateGroupInfo(fabric_index);
VerifyOrReturnError(nullptr != iter, Status::ResourceExhausted);
VerifyOrReturnError(iter->Count() > 0, Status::NotFound);
GroupInfo info;
while (iter->Next(info) && (Status::Success == err))
{
// For leave group, the leaveGroupResponse SHALL NOT contain the endpoints that were removed.
err = RemoveGroup(fabric_index, info.group_id, data, nullptr /* endpoints */);
}
iter->Release();
}
else
{
// Modify specific group
err = RemoveGroup(fabric_index, data.groupID, data, &endpoints);
}
return err;
}
Status GroupcastCluster::UpdateGroupKey(FabricIndex fabric_index, const Groupcast::Commands::UpdateGroupKey::DecodableType & data)
{
// Validate that the group exists early before trying to set the keyset
GroupDataProvider::GroupInfo info;
CHIP_ERROR err = Provider().GetGroupInfo(fabric_index, data.groupID, info);
VerifyOrReturnError(CHIP_ERROR_NOT_FOUND != err, Status::NotFound);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
return SetKeySet(fabric_index, data.groupID, data.keySetID, data.key);
}
Status GroupcastCluster::ConfigureAuxiliaryACL(FabricIndex fabric_index,
const Groupcast::Commands::ConfigureAuxiliaryACL::DecodableType & data)
{
GroupDataProvider & groups = Provider();
CHIP_ERROR err = CHIP_NO_ERROR;
// AuxiliaryACL can only be present if LN feature is supported
VerifyOrReturnError(mFeatures.Has(Groupcast::Feature::kListener), Status::ConstraintError);
// Get group info
GroupDataProvider::GroupInfo info;
err = groups.GetGroupInfo(fabric_index, data.groupID, info);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::NotFound);
// Update group info
if (data.useAuxiliaryACL)
{
info.flags |= chip::to_underlying(GroupInfo::Flags::kHasAuxiliaryACL);
}
else
{
info.flags &= ~chip::to_underlying(GroupInfo::Flags::kHasAuxiliaryACL);
}
err = groups.SetGroupInfo(fabric_index, info);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
return Status::Success;
}
Status GroupcastCluster::SetKeySet(FabricIndex fabric_index, GroupId group_id, KeysetId keyset_id,
const chip::Optional<chip::ByteSpan> & key)
{
GroupDataProvider & groups = Provider();
GroupDataProvider::KeySet ks;
CHIP_ERROR err = groups.GetKeySet(fabric_index, keyset_id, ks);
if (key.HasValue())
{
// Key provided, the keyset must not exist
VerifyOrReturnError(CHIP_NO_ERROR != err, Status::AlreadyExists);
VerifyOrReturnError(CHIP_ERROR_NOT_FOUND == err, Status::Failure);
// Create new key
const FabricInfo * fabric = Fabrics().FindFabricWithIndex(fabric_index);
VerifyOrReturnValue(nullptr != fabric, Status::NotFound);
ks.keyset_id = keyset_id;
ks.policy = GroupDataProvider::SecurityPolicy::kTrustFirst;
ks.num_keys_used = 1;
GroupDataProvider::EpochKey & epoch = ks.epoch_keys[0];
VerifyOrReturnValue(key.Value().size() == GroupDataProvider::EpochKey::kLengthBytes, Status::ConstraintError);
memcpy(epoch.key, key.Value().data(), GroupDataProvider::EpochKey::kLengthBytes);
{
// Get compressed fabric
uint8_t compressed_fabric_id_buffer[sizeof(uint64_t)];
MutableByteSpan compressed_fabric_id(compressed_fabric_id_buffer);
err = fabric->GetCompressedFabricIdBytes(compressed_fabric_id);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
// Set keys
err = groups.SetKeySet(fabric_index, compressed_fabric_id, ks);
VerifyOrReturnError(CHIP_ERROR_INVALID_LIST_LENGTH != err, Status::ResourceExhausted);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
}
}
else
{
// No key provided, the keyset must exist
VerifyOrReturnError(CHIP_ERROR_NOT_FOUND != err, Status::NotFound);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
}
// Assign keyset to group
err = groups.SetGroupKey(fabric_index, group_id, keyset_id);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
return Status::Success;
}
Status GroupcastCluster::RemoveGroup(FabricIndex fabric_index, GroupId group_id,
const Groupcast::Commands::LeaveGroup::DecodableType & data, EndpointList * endpoints)
{
GroupDataProvider & groups = Provider();
Status stat = Status::Success;
if (data.endpoints.HasValue())
{
// Remove endpoints
auto iter = data.endpoints.Value().begin();
while (iter.Next())
{
auto endpoint_id = iter.GetValue();
if (groups.HasEndpoint(fabric_index, group_id, endpoint_id))
{
stat = RemoveGroupEndpoint(fabric_index, group_id, endpoint_id, endpoints);
VerifyOrReturnError(Status::Success == stat, stat);
}
}
}
else
{
if (endpoints != nullptr)
{
// Get the endpoints list for the LeaveGroupResponse
EndpointIterator * epIter = groups.IterateEndpoints(fabric_index, group_id);
VerifyOrReturnError(nullptr != epIter, Status::ResourceExhausted);
if (epIter->Count() <= kMaxCommandEndpoints)
{
GroupEndpoint ep;
while (epIter->Next(ep) && (endpoints->count < kMaxCommandEndpoints))
{
endpoints->entries[endpoints->count++] = ep.endpoint_id;
}
}
epIter->Release();
}
// Remove whole group (with all endpoints)
CHIP_ERROR err = groups.RemoveGroupInfo(fabric_index, group_id);
VerifyOrReturnError(CHIP_ERROR_NOT_FOUND != err, Status::NotFound);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
}
return Status::Success;
}
Status GroupcastCluster::RemoveGroupEndpoint(FabricIndex fabric_index, GroupId group_id, EndpointId endpoint_id,
EndpointList * endpoints)
{
GroupDataProvider & groups = Provider();
CHIP_ERROR err = groups.RemoveEndpoint(fabric_index, group_id, endpoint_id,
mFeatures.Has(Groupcast::Feature::kSender)
? GroupDataProvider::GroupCleanupPolicy::kKeepGroupIfEmpty
: GroupDataProvider::GroupCleanupPolicy::kDeleteGroupIfEmpty);
VerifyOrReturnError(CHIP_NO_ERROR == err, Status::Failure);
VerifyOrReturnError(endpoints != nullptr, Status::Success);
bool found = false;
for (size_t i = 0; !found && i < endpoints->count; ++i)
{
found = (endpoints->entries[i] == endpoint_id);
}
if (!found)
{
endpoints->entries[endpoints->count++] = endpoint_id;
}
return Status::Success;
}
void GroupcastCluster::OnGroupAdded(FabricIndex fabric_index, const GroupInfo & new_group)
{
(void) fabric_index;
(void) new_group;
NotifyMembershipChanged();
NotifyUsedMcastAddrCountOnChange();
}
void GroupcastCluster::OnGroupRemoved(FabricIndex fabric_index, const GroupInfo & old_group)
{
(void) fabric_index;
(void) old_group;
NotifyMembershipChanged();
NotifyUsedMcastAddrCountOnChange();
}
void GroupcastCluster::OnGroupModified(FabricIndex fabric_index, const GroupId & modified_group_id)
{
(void) fabric_index;
(void) modified_group_id;
NotifyMembershipChanged();
NotifyUsedMcastAddrCountOnChange();
}
void GroupcastCluster::UpdateUsedMcastAddrCount()
{
uint16_t per_group_count = 0;
uint16_t iana_address = 0;
// Iterate all fabrics
for (const FabricInfo & fabric : Fabrics())
{
// Count distinct group addresses
GroupInfoIterator * iter = Provider().IterateGroupInfo(fabric.GetFabricIndex());
VerifyOrReturn(nullptr != iter);
GroupInfo group;
while (iter->Next(group))
{
if (group.UsePerGroupAddress())
{
per_group_count++;
}
else
{
iana_address = 1;
}
}
iter->Release();
}
mIanaAddressUsed = (iana_address > 0);
mUsedMcastAddrCount = per_group_count + iana_address;
}
void GroupcastCluster::NotifyMembershipChanged()
{
mMembershipChangedTimer.Start();
}
void GroupcastCluster::NotifyUsedMcastAddrCountOnChange()
{
uint16_t old_count = mUsedMcastAddrCount;
UpdateUsedMcastAddrCount();
if (old_count != mUsedMcastAddrCount)
{
NotifyAttributeChanged(Groupcast::Attributes::UsedMcastAddrCount::Id);
}
}
} // namespace Clusters
} // namespace app
} // namespace chip