blob: 452813c919f8ce2c7c2c01289b4413cb6f3f18a3 [file] [log] [blame]
/**
*
* 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.
*/
/**
*
* Copyright (c) 2020 Silicon Labs
*
* 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.
*/
/****************************************************************************
* @file
* @brief Routines for the Groups Server plugin, the
*server implementation of the Groups cluster.
*******************************************************************************
******************************************************************************/
// *******************************************************************
// * groups-server.c
// *
// *
// * Copyright 2010 by Ember Corporation. All rights reserved. *80*
// *******************************************************************
#include "groups-server.h"
#include <app-common/zap-generated/att-storage.h>
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/command-id.h>
#include <app/CommandHandler.h>
#include <app/util/af.h>
#include <credentials/GroupDataProvider.h>
#include <inttypes.h>
#include <lib/support/CodeUtils.h>
#ifdef EMBER_AF_PLUGIN_SCENES
#include <app/clusters/scenes/scenes.h>
#endif // EMBER_AF_PLUGIN_SCENES
using namespace chip;
using namespace app::Clusters;
using namespace app::Clusters::Groups;
using namespace chip::Credentials;
static FabricIndex GetFabricIndex(app::CommandHandler * commandObj)
{
VerifyOrReturnError(nullptr != commandObj, 0);
VerifyOrReturnError(nullptr != commandObj->GetExchangeContext(), 0);
return commandObj->GetExchangeContext()->GetSessionHandle().GetFabricIndex();
}
static bool GroupExists(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId)
{
GroupDataProvider * provider = GetGroupDataProvider();
VerifyOrReturnError(nullptr != provider, false);
GroupDataProvider::GroupMapping mapping(endpointId, groupId);
return provider->GroupMappingExists(fabricIndex, mapping);
}
static EmberAfStatus GroupAdd(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId, const CharSpan & groupName)
{
VerifyOrReturnError(IsFabricGroupId(groupId), EMBER_ZCL_STATUS_INVALID_VALUE);
// Check for duplicates.
if (GroupExists(fabricIndex, endpointId, groupId))
{
// Even if the group already exists, tell the application about the name,
// so it can cope with renames.
return EMBER_ZCL_STATUS_DUPLICATE_EXISTS;
}
GroupDataProvider * provider = GetGroupDataProvider();
VerifyOrReturnError(nullptr != provider, EMBER_ZCL_STATUS_NOT_FOUND);
GroupDataProvider::GroupMapping mapping(endpointId, groupId, groupName);
CHIP_ERROR err = provider->AddGroupMapping(fabricIndex, mapping);
if (CHIP_NO_ERROR == err)
{
return EMBER_ZCL_STATUS_SUCCESS;
}
else
{
ChipLogDetail(Zcl, "ERR: Failed to add mapping (end:%d, group:0x%x), err:%" CHIP_ERROR_FORMAT, endpointId, groupId,
err.Format());
return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
}
}
static EmberAfStatus GroupRemove(FabricIndex fabricIndex, EndpointId endpointId, GroupId groupId)
{
VerifyOrReturnError(IsFabricGroupId(groupId), EMBER_ZCL_STATUS_INVALID_VALUE);
VerifyOrReturnError(GroupExists(fabricIndex, endpointId, groupId), EMBER_ZCL_STATUS_NOT_FOUND);
GroupDataProvider * provider = GetGroupDataProvider();
VerifyOrReturnError(nullptr != provider, EMBER_ZCL_STATUS_NOT_FOUND);
GroupDataProvider::GroupMapping mapping(endpointId, groupId);
CHIP_ERROR err = provider->RemoveGroupMapping(fabricIndex, mapping);
if (CHIP_NO_ERROR == err)
{
return EMBER_ZCL_STATUS_SUCCESS;
}
else
{
ChipLogDetail(Zcl, "ERR: Failed to remove mapping (end:%d, group:0x%x), err:%" CHIP_ERROR_FORMAT, endpointId, groupId,
err.Format());
return EMBER_ZCL_STATUS_NOT_FOUND;
}
}
void emberAfGroupsClusterServerInitCallback(EndpointId endpointId)
{
GroupDataProvider * provider = GetGroupDataProvider();
uint8_t nameSupport = (provider && provider->HasGroupNamesSupport()) ? 0x80 : 0x00;
EmberAfStatus status = Attributes::NameSupport::Set(endpointId, nameSupport);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
ChipLogDetail(Zcl, "ERR: writing name support %x", status);
}
}
bool emberAfGroupsClusterAddGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::AddGroup::DecodableType & commandData)
{
auto fabricIndex = GetFabricIndex(commandObj);
auto groupId = commandData.groupId;
auto groupName = commandData.groupName;
auto endpointId = commandPath.mEndpointId;
EmberAfStatus status;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogDetail(Zcl, "RX: AddGroup 0x%2x, \"%.*s\"", groupId, static_cast<int>(groupName.size()), groupName.data());
status = GroupAdd(fabricIndex, endpointId, groupId, groupName);
// For all networks, Add Group commands are only responded to when
// they are addressed to a single device.
if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY)
{
return true;
}
{
app::CommandPathParams cmdParams = { emberAfCurrentEndpoint(), /* group id */ 0, Groups::Id, Commands::AddGroupResponse::Id,
(app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfGroupsClusterViewGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::ViewGroup::DecodableType & commandData)
{
auto fabricIndex = GetFabricIndex(commandObj);
auto groupId = commandData.groupId;
auto endpointId = commandPath.mEndpointId;
GroupDataProvider::GroupMapping mapping;
EmberAfStatus status = EMBER_ZCL_STATUS_NOT_FOUND;
CHIP_ERROR err = CHIP_NO_ERROR;
size_t nameSize = 0;
if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY)
{
return true;
}
if (IsFabricGroupId(groupId))
{
GroupDataProvider * provider = GetGroupDataProvider();
VerifyOrReturnError(nullptr != provider, false);
auto * groupIt = provider->IterateGroupMappings(fabricIndex, endpointId);
VerifyOrReturnError(nullptr != groupIt, false);
while (groupIt->Next(mapping))
{
if (mapping.group == groupId)
{
nameSize = strnlen(mapping.name, GroupDataProvider::GroupMapping::kGroupNameMax);
status = EMBER_ZCL_STATUS_SUCCESS;
break;
}
}
groupIt->Release();
}
else
{
status = EMBER_ZCL_STATUS_INVALID_VALUE;
}
{
app::CommandPathParams cmdParams = { endpointId, /* group id */ 0, Groups::Id, Commands::ViewGroupResponse::Id,
(app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = writer->PutString(TLV::ContextTag(2), mapping.name, static_cast<uint32_t>(nameSize)));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
struct GroupMembershipResponse
{
// A capacity of 0xFF means that it is unknown if any further groups MAY be added.
static constexpr uint8_t kCapacityUnknown = 0xff;
// 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,
GroupDataProvider::GroupMappingIterator * iter) :
mCommandData(data),
mIterator(iter)
{}
const Commands::GetGroupMembership::DecodableType & mCommandData;
GroupDataProvider::GroupMappingIterator * 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(to_underlying(Commands::GetGroupMembershipResponse::Fields::kCapacity)), kCapacityUnknown));
{
TLV::TLVType type;
ReturnErrorOnFailure(
writer.StartContainer(TLV::ContextTag(to_underlying(Commands::GetGroupMembershipResponse::Fields::kGroupList)),
TLV::kTLVType_Array, type));
{
GroupDataProvider::GroupMapping mapping;
size_t requestedCount = 0;
size_t matchCount = 0;
ReturnErrorOnFailure(mCommandData.groupList.ComputeSize(&requestedCount));
ChipLogDetail(Zcl, "RX: GetGroupMembership [");
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->Next(mapping))
{
ReturnErrorOnFailure(app::DataModel::Encode(writer, TLV::AnonymousTag, mapping.group));
matchCount++;
ChipLogDetail(Zcl, " 0x%02" PRIx16, mapping.group);
}
}
else
{
while (mIterator->Next(mapping))
{
auto iter = mCommandData.groupList.begin();
while (iter.Next())
{
if (mapping.group == iter.GetValue())
{
ReturnErrorOnFailure(app::DataModel::Encode(writer, TLV::AnonymousTag, mapping.group));
matchCount++;
ChipLogDetail(Zcl, " 0x%02" PRIx16, mapping.group);
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)
{
auto fabricIndex = GetFabricIndex(commandObj);
auto * provider = GetGroupDataProvider();
EmberStatus status = EMBER_ZCL_STATUS_FAILURE;
CHIP_ERROR err = CHIP_NO_ERROR;
if (nullptr == provider)
{
status = emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
else
{
auto iter = provider->IterateGroupMappings(fabricIndex, commandPath.mEndpointId);
VerifyOrExit(nullptr != iter, err = CHIP_ERROR_NO_MEMORY);
err = commandObj->AddResponseData(commandPath, GroupMembershipResponse(commandData, iter));
iter->Release();
if (CHIP_NO_ERROR == err)
{
status = EMBER_SUCCESS;
}
}
exit:
if (EMBER_SUCCESS != status)
{
ChipLogDetail(Zcl, "Groups: failed to send get_group_membership response: 0x%x", status);
}
return true;
}
bool emberAfGroupsClusterRemoveGroupCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::RemoveGroup::DecodableType & commandData)
{
auto fabricIndex = GetFabricIndex(commandObj);
auto groupId = commandData.groupId;
auto endpointId = commandPath.mEndpointId;
EmberAfStatus status;
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogDetail(Zcl, "RX: RemoveGroup 0x%2x", groupId);
status = GroupRemove(fabricIndex, endpointId, groupId);
// For all networks, Remove Group commands are only responded to when
// they are addressed to a single device.
if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY)
{
return true;
}
#ifdef EMBER_AF_PLUGIN_SCENES
// If a group is, removed the scenes associated with that group SHOULD be removed.
emberAfScenesClusterRemoveScenesInGroupCallback(endpointId, groupId);
#endif
{
app::CommandPathParams cmdParams = { endpointId, /* group id */ 0, Groups::Id, Commands::RemoveGroupResponse::Id,
(app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfGroupsClusterRemoveAllGroupsCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::RemoveAllGroups::DecodableType & commandData)
{
FabricIndex fabricIndex = GetFabricIndex(commandObj);
GroupDataProvider * provider = GetGroupDataProvider();
EndpointId endpointId = commandPath.mEndpointId;
EmberStatus sendStatus = EMBER_SUCCESS;
CHIP_ERROR err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
ChipLogDetail(Zcl, "RX: RemoveAllGroups");
VerifyOrReturnError(nullptr != provider, true);
#ifdef EMBER_AF_PLUGIN_SCENES
auto groupIter = provider->IterateGroupMappings(fabricIndex, endpointId);
VerifyOrReturnError(nullptr != groupIter, true);
GroupDataProvider::GroupMapping mapping;
while (groupIter->Next(mapping))
{
emberAfScenesClusterRemoveScenesInGroupCallback(endpointId, mapping.group);
}
groupIter->Release();
emberAfScenesClusterRemoveScenesInGroupCallback(endpointId, ZCL_SCENES_GLOBAL_SCENE_GROUP_ID);
#endif
err = provider->RemoveAllGroupMappings(fabricIndex, endpointId);
sendStatus = emberAfSendImmediateDefaultResponse(CHIP_NO_ERROR == err ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_STATUS_FAILURE);
if (EMBER_SUCCESS != sendStatus)
{
ChipLogDetail(Zcl, "Groups: failed to send %s: 0x%x", "default_response", sendStatus);
}
return true;
}
bool emberAfGroupsClusterAddGroupIfIdentifyingCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::AddGroupIfIdentifying::DecodableType & commandData)
{
auto fabricIndex = GetFabricIndex(commandObj);
auto groupId = commandData.groupId;
auto groupName = commandData.groupName;
auto endpointId = commandPath.mEndpointId;
EmberAfStatus status;
EmberStatus sendStatus = EMBER_SUCCESS;
ChipLogDetail(Zcl, "RX: AddGroupIfIdentifying 0x%2x, \"%.*s\"", groupId, static_cast<int>(groupName.size()), groupName.data());
if (!emberAfIsDeviceIdentifying(endpointId))
{
// If not identifying, ignore add group -> success; not a failure.
status = EMBER_ZCL_STATUS_SUCCESS;
}
else
{
status = GroupAdd(fabricIndex, endpointId, groupId, groupName);
}
sendStatus = emberAfSendImmediateDefaultResponse(status);
if (EMBER_SUCCESS != sendStatus)
{
ChipLogDetail(Zcl, "Groups: failed to send %s: 0x%x", "default_response", sendStatus);
}
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() {}