/** * * Copyright (c) 2020 Project CHIP Authors * * 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 "groups-server.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT #include #endif // MATTER_DM_PLUGIN_SCENES_MANAGEMENT using namespace chip; using namespace app::Clusters; using namespace app::Clusters::Groups; using namespace chip::Credentials; using Protocols::InteractionModel::Status; // Is the device identifying? static bool emberAfIsDeviceIdentifying(EndpointId endpoint) { #ifdef ZCL_USING_IDENTIFY_CLUSTER_SERVER uint16_t identifyTime; Status status = app::Clusters::Identify::Attributes::IdentifyTime::Get(endpoint, &identifyTime); return (status == Status::Success && 0 < identifyTime); #else return false; #endif } /** * @brief Checks if group-endpoint association exist for the given fabric */ static bool GroupExists(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId) { GroupDataProvider * provider = GetGroupDataProvider(); VerifyOrReturnError(nullptr != provider, false); return provider->HasEndpoint(fabricIndex, groupId, endpointId); } /** * @brief Checks if there are key set associated with the given GroupId */ static bool KeyExists(FabricIndex fabricIndex, GroupId groupId) { GroupDataProvider * provider = GetGroupDataProvider(); VerifyOrReturnError(nullptr != provider, false); GroupDataProvider::GroupKey entry; auto it = provider->IterateGroupKeys(fabricIndex); bool found = false; while (!found && it->Next(entry)) { if (entry.group_id == groupId) { GroupDataProvider::KeySet keys; found = (CHIP_NO_ERROR == provider->GetKeySet(fabricIndex, entry.keyset_id, keys)); } } it->Release(); return found; } static Status GroupAdd(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId, const CharSpan & groupName) { VerifyOrReturnError(IsValidGroupId(groupId), Status::ConstraintError); VerifyOrReturnError(groupName.size() <= GroupDataProvider::GroupInfo::kGroupNameMax, Status::ConstraintError); GroupDataProvider * provider = GetGroupDataProvider(); VerifyOrReturnError(nullptr != provider, Status::NotFound); VerifyOrReturnError(KeyExists(fabricIndex, groupId), Status::UnsupportedAccess); // Add a new entry to the GroupTable CHIP_ERROR err = provider->SetGroupInfo(fabricIndex, GroupDataProvider::GroupInfo(groupId, groupName)); if (CHIP_NO_ERROR == err) { err = provider->AddEndpoint(fabricIndex, groupId, endpointId); } if (CHIP_NO_ERROR == err) { MatterReportingAttributeChangeCallback(kRootEndpointId, GroupKeyManagement::Id, GroupKeyManagement::Attributes::GroupTable::Id); return Status::Success; } ChipLogDetail(Zcl, "ERR: Failed to add mapping (end:%d, group:0x%x), err:%" CHIP_ERROR_FORMAT, endpointId, groupId, err.Format()); return Status::ResourceExhausted; } static Status GroupRemove(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId) { VerifyOrReturnError(IsValidGroupId(groupId), Status::ConstraintError); VerifyOrReturnError(GroupExists(fabricIndex, endpointId, groupId), Status::NotFound); GroupDataProvider * provider = GetGroupDataProvider(); VerifyOrReturnError(nullptr != provider, Status::NotFound); CHIP_ERROR err = provider->RemoveEndpoint(fabricIndex, groupId, endpointId); if (CHIP_NO_ERROR == err) { MatterReportingAttributeChangeCallback(kRootEndpointId, GroupKeyManagement::Id, GroupKeyManagement::Attributes::GroupTable::Id); return Status::Success; } ChipLogDetail(Zcl, "ERR: Failed to remove mapping (end:%d, group:0x%x), err:%" CHIP_ERROR_FORMAT, endpointId, groupId, err.Format()); return Status::NotFound; } void emberAfGroupsClusterServerInitCallback(EndpointId endpointId) { // According to spec, highest bit (Group Names) MUST match feature bit 0 (Group Names) Status status = Attributes::NameSupport::Set(endpointId, NameSupportBitmap::kGroupNames); if (status != Status::Success) { ChipLogDetail(Zcl, "ERR: writing NameSupport %x", to_underlying(status)); } status = Attributes::FeatureMap::Set(endpointId, static_cast(Feature::kGroupNames)); if (status != Status::Success) { ChipLogDetail(Zcl, "ERR: writing group feature map %x", to_underlying(status)); } } bool emberAfGroupsClusterAddGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::AddGroup::DecodableType & commandData) { MATTER_TRACE_SCOPE("AddGroup", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); Groups::Commands::AddGroupResponse::Type response; response.groupID = commandData.groupID; response.status = to_underlying(GroupAdd(fabricIndex, commandPath.mEndpointId, commandData.groupID, commandData.groupName)); commandObj->AddResponse(commandPath, response); return true; } bool emberAfGroupsClusterViewGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::ViewGroup::DecodableType & commandData) { MATTER_TRACE_SCOPE("ViewGroup", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto groupId = commandData.groupID; GroupDataProvider * provider = GetGroupDataProvider(); GroupDataProvider::GroupInfo info; Groups::Commands::ViewGroupResponse::Type response; CHIP_ERROR err = CHIP_NO_ERROR; Status status = Status::NotFound; VerifyOrExit(IsValidGroupId(groupId), status = Status::ConstraintError); VerifyOrExit(nullptr != provider, status = Status::Failure); VerifyOrExit(provider->HasEndpoint(fabricIndex, groupId, commandPath.mEndpointId), status = Status::NotFound); err = provider->GetGroupInfo(fabricIndex, groupId, info); VerifyOrExit(CHIP_NO_ERROR == err, status = Status::NotFound); response.groupName = CharSpan(info.name, strnlen(info.name, GroupDataProvider::GroupInfo::kGroupNameMax)); status = Status::Success; exit: response.groupID = groupId; response.status = to_underlying(status); commandObj->AddResponse(commandPath, response); return true; } struct GroupMembershipResponse { // A null capacity means that it is unknown if any further groups MAY be added. const chip::app::DataModel::Nullable kCapacityUnknown; // Use GetCommandId instead of commandId directly to avoid naming conflict with CommandIdentification in ExecutionOfACommand static constexpr CommandId GetCommandId() { return Commands::GetGroupMembershipResponse::Id; } static constexpr ClusterId GetClusterId() { return Groups::Id; } GroupMembershipResponse(const Commands::GetGroupMembership::DecodableType & data, chip::EndpointId endpoint, GroupDataProvider::EndpointIterator * iter) : mCommandData(data), mEndpoint(endpoint), mIterator(iter) {} const Commands::GetGroupMembership::DecodableType & mCommandData; chip::EndpointId mEndpoint = kInvalidEndpointId; GroupDataProvider::EndpointIterator * mIterator = nullptr; CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const { TLV::TLVType outer; ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer)); ReturnErrorOnFailure(app::DataModel::Encode( writer, TLV::ContextTag(Commands::GetGroupMembershipResponse::Fields::kCapacity), kCapacityUnknown)); { TLV::TLVType type; ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(Commands::GetGroupMembershipResponse::Fields::kGroupList), TLV::kTLVType_Array, type)); { GroupDataProvider::GroupEndpoint mapping; size_t requestedCount = 0; ReturnErrorOnFailure(mCommandData.groupList.ComputeSize(&requestedCount)); if (0 == requestedCount) { // 1.3.6.3.1. If the GroupList field is empty, the entity SHALL respond with all group identifiers of which the // entity is a member. while (mIterator && mIterator->Next(mapping)) { if (mapping.endpoint_id == mEndpoint) { ReturnErrorOnFailure(app::DataModel::Encode(writer, TLV::AnonymousTag(), mapping.group_id)); ChipLogDetail(Zcl, " 0x%02x", mapping.group_id); } } } else { while (mIterator && mIterator->Next(mapping)) { auto iter = mCommandData.groupList.begin(); while (iter.Next()) { if (mapping.endpoint_id == mEndpoint && mapping.group_id == iter.GetValue()) { ReturnErrorOnFailure(app::DataModel::Encode(writer, TLV::AnonymousTag(), mapping.group_id)); ChipLogDetail(Zcl, " 0x%02x", mapping.group_id); break; } } ReturnErrorOnFailure(iter.GetStatus()); } } ChipLogDetail(Zcl, "]"); } ReturnErrorOnFailure(writer.EndContainer(type)); } ReturnErrorOnFailure(writer.EndContainer(outer)); return CHIP_NO_ERROR; } }; bool emberAfGroupsClusterGetGroupMembershipCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::GetGroupMembership::DecodableType & commandData) { MATTER_TRACE_SCOPE("GetGroupMembership", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto * provider = GetGroupDataProvider(); Status status = Status::Failure; VerifyOrExit(nullptr != provider, status = Status::Failure); { GroupDataProvider::EndpointIterator * iter = nullptr; iter = provider->IterateEndpoints(fabricIndex); VerifyOrExit(nullptr != iter, status = Status::Failure); commandObj->AddResponse(commandPath, GroupMembershipResponse(commandData, commandPath.mEndpointId, iter)); iter->Release(); status = Status::Success; } exit: if (Status::Success != status) { ChipLogDetail(Zcl, "GroupsCluster: GetGroupMembership failed: failed: 0x%x", to_underlying(status)); commandObj->AddStatus(commandPath, status); } return true; } bool emberAfGroupsClusterRemoveGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::RemoveGroup::DecodableType & commandData) { MATTER_TRACE_SCOPE("RemoveGroup", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); Groups::Commands::RemoveGroupResponse::Type response; #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT // If a group is removed the scenes associated with that group SHOULD be removed. ScenesManagement::ScenesServer::Instance().GroupWillBeRemoved(fabricIndex, commandPath.mEndpointId, commandData.groupID); #endif response.groupID = commandData.groupID; response.status = to_underlying(GroupRemove(fabricIndex, commandPath.mEndpointId, commandData.groupID)); commandObj->AddResponse(commandPath, response); return true; } bool emberAfGroupsClusterRemoveAllGroupsCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::RemoveAllGroups::DecodableType & commandData) { MATTER_TRACE_SCOPE("RemoveAllGroups", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto * provider = GetGroupDataProvider(); Status status = Status::Failure; VerifyOrExit(nullptr != provider, status = Status::Failure); #ifdef MATTER_DM_PLUGIN_SCENES_MANAGEMENT { GroupDataProvider::EndpointIterator * iter = provider->IterateEndpoints(fabricIndex); GroupDataProvider::GroupEndpoint mapping; VerifyOrExit(nullptr != iter, status = Status::Failure); while (iter->Next(mapping)) { if (commandPath.mEndpointId == mapping.endpoint_id) { ScenesManagement::ScenesServer::Instance().GroupWillBeRemoved(fabricIndex, mapping.endpoint_id, mapping.group_id); } } iter->Release(); ScenesManagement::ScenesServer::Instance().GroupWillBeRemoved(fabricIndex, commandPath.mEndpointId, ZCL_SCENES_GLOBAL_SCENE_GROUP_ID); } #endif provider->RemoveEndpoint(fabricIndex, commandPath.mEndpointId); status = Status::Success; MatterReportingAttributeChangeCallback(kRootEndpointId, GroupKeyManagement::Id, GroupKeyManagement::Attributes::GroupTable::Id); exit: commandObj->AddStatus(commandPath, status); if (Status::Success != status) { ChipLogDetail(Zcl, "GroupsCluster: RemoveAllGroups failed: 0x%x", to_underlying(status)); } return true; } bool emberAfGroupsClusterAddGroupIfIdentifyingCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, const Commands::AddGroupIfIdentifying::DecodableType & commandData) { MATTER_TRACE_SCOPE("AddGroupIfIdentifying", "Groups"); auto fabricIndex = commandObj->GetAccessingFabricIndex(); auto groupId = commandData.groupID; auto groupName = commandData.groupName; auto endpointId = commandPath.mEndpointId; Status status; if (!emberAfIsDeviceIdentifying(endpointId)) { // If not identifying, ignore add group -> success; not a failure. status = Status::Success; } else { status = GroupAdd(fabricIndex, endpointId, groupId, groupName); } commandObj->AddStatus(commandPath, status); return true; } bool emberAfGroupsClusterEndpointInGroupCallback(chip::FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId) { return GroupExists(fabricIndex, endpointId, groupId); } void emberAfPluginGroupsServerSetGroupNameCallback(EndpointId endpoint, GroupId groupId, const CharSpan & groupName) {} void MatterGroupsPluginServerInitCallback() {}