Skip to content

Commit

Permalink
Add Ecosystem Information Cluster Server implementation (#34459)
Browse files Browse the repository at this point in the history
  • Loading branch information
tehampson authored Jul 26, 2024
1 parent 686a715 commit 55786a0
Show file tree
Hide file tree
Showing 4 changed files with 585 additions and 1 deletion.
2 changes: 2 additions & 0 deletions scripts/tools/check_includes_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@
'src/app/clusters/application-launcher-server/application-launcher-server.cpp': {'string'},
'src/app/clusters/application-launcher-server/application-launcher-delegate.h': {'list'},
'src/app/clusters/audio-output-server/audio-output-delegate.h': {'list'},
# EcosystemInformationCluster is for Fabric Sync and is intended to run on device that are capable of handling these types.
'src/app/clusters/ecosystem-information-server/ecosystem-information-server.h': {'map', 'string', 'vector'},
'src/app/clusters/channel-server/channel-delegate.h': {'list'},
'src/app/clusters/content-launch-server/content-launch-delegate.h': {'list'},
'src/app/clusters/content-launch-server/content-launch-server.cpp': {'list'},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
/*
*
* 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 "ecosystem-information-server.h"

#include <app/AttributeAccessInterface.h>
#include <app/AttributeAccessInterfaceRegistry.h>

namespace chip {
namespace app {
namespace Clusters {
namespace EcosystemInformation {
namespace {

constexpr size_t kDeviceNameMaxSize = 64;
constexpr size_t kUniqueLocationIdMaxSize = 64;
constexpr size_t kUniqueLocationIdsListMaxSize = 64;
constexpr size_t kLocationDescriptorNameMaxSize = 128;

constexpr size_t kDeviceDirectoryMaxSize = 256;
constexpr size_t kLocationDirectoryMaxSize = 64;

class AttrAccess : public AttributeAccessInterface
{
public:
// Register for the EcosystemInformationCluster on all endpoints.
AttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), Clusters::EcosystemInformation::Id) {}

CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
};

CHIP_ERROR AttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
VerifyOrDie(aPath.mClusterId == Clusters::EcosystemInformation::Id);
switch (aPath.mAttributeId)
{
case Attributes::RemovedOn::Id:
return EcosystemInformationServer::Instance().EncodeRemovedOnAttribute(aPath.mEndpointId, aEncoder);
case Attributes::DeviceDirectory ::Id:
return EcosystemInformationServer::Instance().EncodeDeviceDirectoryAttribute(aPath.mEndpointId, aEncoder);
case Attributes::LocationDirectory ::Id:
return EcosystemInformationServer::Instance().EncodeLocationStructAttribute(aPath.mEndpointId, aEncoder);
default:
break;
}
return CHIP_NO_ERROR;
}

// WARNING: caller is expected to use the returned LocationDescriptorStruct::Type immediately. Caller must
// be certain that the provided aLocationDescriptor has not been destroyed, prior to using the return
// struct to encode.
// TODO(#33223) To improve safety we could make GetEncodableLocationDescriptorStruct a private
// memeber method where we explicitly delete member method for the parameter that matches
// (LocationDescriptorStruct && aLocationDescriptor).
Structs::LocationDescriptorStruct::Type GetEncodableLocationDescriptorStruct(const LocationDescriptorStruct & aLocationDescriptor)
{
Structs::LocationDescriptorStruct::Type locationDescriptor;
// This would imply data is either not properly validated before being
// stored here or corruption has occurred.
VerifyOrDie(!aLocationDescriptor.mLocationName.empty());
locationDescriptor.locationName = CharSpan(aLocationDescriptor.mLocationName.c_str(), aLocationDescriptor.mLocationName.size());

if (aLocationDescriptor.mFloorNumber.has_value())
{
locationDescriptor.floorNumber.SetNonNull(aLocationDescriptor.mFloorNumber.value());
}
else
{
locationDescriptor.floorNumber.SetNull();
}

if (aLocationDescriptor.mAreaType.has_value())
{
locationDescriptor.areaType.SetNonNull(aLocationDescriptor.mAreaType.value());
}
else
{
locationDescriptor.areaType.SetNull();
}
return locationDescriptor;
}

} // namespace

EcosystemDeviceStruct::Builder & EcosystemDeviceStruct::Builder::SetDeviceName(std::string aDeviceName,
uint64_t aDeviceNameLastEditEpochUs)
{
VerifyOrDie(!mIsAlreadyBuilt);
mDeviceName = std::move(aDeviceName);
mDeviceNameLastEditEpochUs = aDeviceNameLastEditEpochUs;
return *this;
}

EcosystemDeviceStruct::Builder & EcosystemDeviceStruct::Builder::SetBrigedEndpoint(EndpointId aBridgedEndpoint)
{
VerifyOrDie(!mIsAlreadyBuilt);
mBridgedEndpoint = aBridgedEndpoint;
return *this;
}

EcosystemDeviceStruct::Builder & EcosystemDeviceStruct::Builder::SetOriginalEndpoint(EndpointId aOriginalEndpoint)
{
VerifyOrDie(!mIsAlreadyBuilt);
mOriginalEndpoint = aOriginalEndpoint;
return *this;
}

EcosystemDeviceStruct::Builder & EcosystemDeviceStruct::Builder::AddDeviceType(Structs::DeviceTypeStruct::Type aDeviceType)
{
VerifyOrDie(!mIsAlreadyBuilt);
mDeviceTypes.push_back(std::move(aDeviceType));
return *this;
}

EcosystemDeviceStruct::Builder & EcosystemDeviceStruct::Builder::AddUniqueLocationId(std::string aUniqueLocationId,
uint64_t aUniqueLocationIdsLastEditEpochUs)
{
VerifyOrDie(!mIsAlreadyBuilt);
mUniqueLocationIds.push_back(std::move(aUniqueLocationId));
mUniqueLocationIdsLastEditEpochUs = aUniqueLocationIdsLastEditEpochUs;
return *this;
}

std::unique_ptr<EcosystemDeviceStruct> EcosystemDeviceStruct::Builder::Build()
{
VerifyOrReturnValue(!mIsAlreadyBuilt, nullptr, ChipLogError(Zcl, "Build() already called"));
VerifyOrReturnValue(mDeviceName.size() <= kDeviceNameMaxSize, nullptr, ChipLogError(Zcl, "Device name too large"));
VerifyOrReturnValue(mOriginalEndpoint != kInvalidEndpointId, nullptr, ChipLogError(Zcl, "Invalid original endpoint"));
VerifyOrReturnValue(!mDeviceTypes.empty(), nullptr, ChipLogError(Zcl, "No device types added"));
VerifyOrReturnValue(mUniqueLocationIds.size() <= kUniqueLocationIdsListMaxSize, nullptr,
ChipLogError(Zcl, "Too many location ids"));

for (auto & locationId : mUniqueLocationIds)
{
VerifyOrReturnValue(locationId.size() <= kUniqueLocationIdMaxSize, nullptr, ChipLogError(Zcl, "Location id too long"));
}

// std::make_unique does not have access to private constructor we workaround with using new
std::unique_ptr<EcosystemDeviceStruct> ret{ new EcosystemDeviceStruct(
std::move(mDeviceName), mDeviceNameLastEditEpochUs, mBridgedEndpoint, mOriginalEndpoint, std::move(mDeviceTypes),
std::move(mUniqueLocationIds), mUniqueLocationIdsLastEditEpochUs) };
mIsAlreadyBuilt = true;
return ret;
}

CHIP_ERROR EcosystemDeviceStruct::Encode(const AttributeValueEncoder::ListEncodeHelper & aEncoder, const FabricIndex & aFabricIndex)
{
Structs::EcosystemDeviceStruct::Type deviceStruct;
if (!mDeviceName.empty())
{
deviceStruct.deviceName.SetValue(CharSpan(mDeviceName.c_str(), mDeviceName.size()));
// When there is a device name we also include mDeviceNameLastEditEpochUs
deviceStruct.deviceNameLastEdit.SetValue(mDeviceNameLastEditEpochUs);
}
deviceStruct.bridgedEndpoint = mBridgedEndpoint;
deviceStruct.originalEndpoint = mOriginalEndpoint;
deviceStruct.deviceTypes = DataModel::List<const Structs::DeviceTypeStruct::Type>(mDeviceTypes.data(), mDeviceTypes.size());

std::vector<CharSpan> locationIds;
locationIds.reserve(mUniqueLocationIds.size());
for (auto & id : mUniqueLocationIds)
{
locationIds.push_back(CharSpan(id.c_str(), id.size()));
}
deviceStruct.uniqueLocationIDs = DataModel::List<CharSpan>(locationIds.data(), locationIds.size());

deviceStruct.uniqueLocationIDsLastEdit = mUniqueLocationIdsLastEditEpochUs;

// TODO(#33223) this is a hack, use mFabricIndex when it exists.
deviceStruct.SetFabricIndex(aFabricIndex);
return aEncoder.Encode(deviceStruct);
}

EcosystemLocationStruct::Builder & EcosystemLocationStruct::Builder::SetLocationName(std::string aLocationName)
{
VerifyOrDie(!mIsAlreadyBuilt);
mLocationDescriptor.mLocationName = std::move(aLocationName);
return *this;
}

EcosystemLocationStruct::Builder & EcosystemLocationStruct::Builder::SetFloorNumber(std::optional<int16_t> aFloorNumber)
{
VerifyOrDie(!mIsAlreadyBuilt);
mLocationDescriptor.mFloorNumber = aFloorNumber;
return *this;
}

EcosystemLocationStruct::Builder & EcosystemLocationStruct::Builder::SetAreaTypeTag(std::optional<AreaTypeTag> aAreaTypeTag)
{
VerifyOrDie(!mIsAlreadyBuilt);
mLocationDescriptor.mAreaType = aAreaTypeTag;
return *this;
}

EcosystemLocationStruct::Builder &
EcosystemLocationStruct::Builder::SetLocationDescriptorLastEdit(uint64_t aLocationDescriptorLastEditEpochUs)
{
VerifyOrDie(!mIsAlreadyBuilt);
mLocationDescriptorLastEditEpochUs = aLocationDescriptorLastEditEpochUs;
return *this;
}

std::unique_ptr<EcosystemLocationStruct> EcosystemLocationStruct::Builder::Build()
{
VerifyOrReturnValue(!mIsAlreadyBuilt, nullptr, ChipLogError(Zcl, "Build() already called"));
VerifyOrReturnValue(!mLocationDescriptor.mLocationName.empty(), nullptr, ChipLogError(Zcl, "Must Provided Location Name"));
VerifyOrReturnValue(mLocationDescriptor.mLocationName.size() <= kLocationDescriptorNameMaxSize, nullptr,
ChipLogError(Zcl, "Must Location Name must be less than 64 bytes"));

// std::make_unique does not have access to private constructor we workaround with using new
std::unique_ptr<EcosystemLocationStruct> ret{ new EcosystemLocationStruct(std::move(mLocationDescriptor),
mLocationDescriptorLastEditEpochUs) };
mIsAlreadyBuilt = true;
return ret;
}

CHIP_ERROR EcosystemLocationStruct::Encode(const AttributeValueEncoder::ListEncodeHelper & aEncoder,
const std::string & aUniqueLocationId, const FabricIndex & aFabricIndex)
{
Structs::EcosystemLocationStruct::Type locationStruct;
VerifyOrDie(!aUniqueLocationId.empty());
locationStruct.uniqueLocationID = CharSpan(aUniqueLocationId.c_str(), aUniqueLocationId.size());
locationStruct.locationDescriptor = GetEncodableLocationDescriptorStruct(mLocationDescriptor);
locationStruct.locationDescriptorLastEdit = mLocationDescriptorLastEditEpochUs;

// TODO(#33223) this is a hack, use mFabricIndex when it exists.
locationStruct.SetFabricIndex(aFabricIndex);
return aEncoder.Encode(locationStruct);
}

EcosystemInformationServer EcosystemInformationServer::mInstance;

EcosystemInformationServer & EcosystemInformationServer::Instance()
{
return mInstance;
}

CHIP_ERROR EcosystemInformationServer::AddDeviceInfo(EndpointId aEndpoint, std::unique_ptr<EcosystemDeviceStruct> aDevice)
{
VerifyOrReturnError(aDevice, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError((aEndpoint != kRootEndpointId && aEndpoint != kInvalidEndpointId), CHIP_ERROR_INVALID_ARGUMENT);

auto & deviceInfo = mDevicesMap[aEndpoint];
VerifyOrReturnError((deviceInfo.mDeviceDirectory.size() < kDeviceDirectoryMaxSize), CHIP_ERROR_NO_MEMORY);
deviceInfo.mDeviceDirectory.push_back(std::move(aDevice));
return CHIP_NO_ERROR;
}

CHIP_ERROR EcosystemInformationServer::AddLocationInfo(EndpointId aEndpoint, const std::string & aLocationId,
std::unique_ptr<EcosystemLocationStruct> aLocation)
{
VerifyOrReturnError(aLocation, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError((aEndpoint != kRootEndpointId && aEndpoint != kInvalidEndpointId), CHIP_ERROR_INVALID_ARGUMENT);

auto & deviceInfo = mDevicesMap[aEndpoint];
VerifyOrReturnError((deviceInfo.mLocationDirectory.find(aLocationId) == deviceInfo.mLocationDirectory.end()),
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError((deviceInfo.mLocationDirectory.size() < kLocationDirectoryMaxSize), CHIP_ERROR_NO_MEMORY);
deviceInfo.mLocationDirectory[aLocationId] = std::move(aLocation);
return CHIP_NO_ERROR;
}

CHIP_ERROR EcosystemInformationServer::RemoveDevice(EndpointId aEndpoint, uint64_t aEpochUs)
{
auto it = mDevicesMap.find(aEndpoint);
VerifyOrReturnError((it != mDevicesMap.end()), CHIP_ERROR_INVALID_ARGUMENT);
auto & deviceInfo = it->second;
deviceInfo.mRemovedOn.SetValue(aEpochUs);
return CHIP_NO_ERROR;
}

CHIP_ERROR EcosystemInformationServer::EncodeRemovedOnAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder)
{
auto it = mDevicesMap.find(aEndpoint);
if (it == mDevicesMap.end())
{
// We are always going to be given a valid endpoint. If the endpoint
// doesn't exist in our map that indicate that the cluster was not
// added on this endpoint, hence UnsupportedCluster.
return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster);
}

auto & deviceInfo = it->second;
if (!deviceInfo.mRemovedOn.HasValue())
{
aEncoder.EncodeNull();
return CHIP_NO_ERROR;
}

aEncoder.Encode(deviceInfo.mRemovedOn.Value());
return CHIP_NO_ERROR;
}

CHIP_ERROR EcosystemInformationServer::EncodeDeviceDirectoryAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder)
{

auto it = mDevicesMap.find(aEndpoint);
if (it == mDevicesMap.end())
{
// We are always going to be given a valid endpoint. If the endpoint
// doesn't exist in our map that indicate that the cluster was not
// added on this endpoint, hence UnsupportedCluster.
return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster);
}

auto & deviceInfo = it->second;
if (deviceInfo.mDeviceDirectory.empty() || deviceInfo.mRemovedOn.HasValue())
{
return aEncoder.EncodeEmptyList();
}

FabricIndex fabricIndex = aEncoder.AccessingFabricIndex();
return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
for (auto & device : deviceInfo.mDeviceDirectory)
{
ReturnErrorOnFailure(device->Encode(encoder, fabricIndex));
}
return CHIP_NO_ERROR;
});
}

CHIP_ERROR EcosystemInformationServer::EncodeLocationStructAttribute(EndpointId aEndpoint, AttributeValueEncoder & aEncoder)
{
auto it = mDevicesMap.find(aEndpoint);
if (it == mDevicesMap.end())
{
// We are always going to be given a valid endpoint. If the endpoint
// doesn't exist in our map that indicate that the cluster was not
// added on this endpoint, hence UnsupportedCluster.
return CHIP_IM_GLOBAL_STATUS(UnsupportedCluster);
}

auto & deviceInfo = it->second;
if (deviceInfo.mLocationDirectory.empty() || deviceInfo.mRemovedOn.HasValue())
{
return aEncoder.EncodeEmptyList();
}

FabricIndex fabricIndex = aEncoder.AccessingFabricIndex();
return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
for (auto & [id, device] : deviceInfo.mLocationDirectory)
{
ReturnErrorOnFailure(device->Encode(encoder, id, fabricIndex));
}
return CHIP_NO_ERROR;
});
return CHIP_NO_ERROR;
}

} // namespace EcosystemInformation
} // namespace Clusters
} // namespace app
} // namespace chip

// -----------------------------------------------------------------------------
// Plugin initialization

chip::app::Clusters::EcosystemInformation::AttrAccess gAttrAccess;

void MatterEcosystemInformationPluginServerInitCallback()
{
registerAttributeAccessOverride(&gAttrAccess);
}
Loading

0 comments on commit 55786a0

Please sign in to comment.