blob: 2076d3601e63d7c21da7ca7125915e2080c46177 [file]
/*
* Copyright (c) 2026 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 <app/clusters/proximity-ranging-server/ProximityRangingCluster.h>
#include <algorithm>
#include <app/EventLogging.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <clusters/ProximityRanging/Metadata.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/ScopedMemoryBuffer.h>
#include <lib/support/logging/CHIPLogging.h>
namespace chip {
namespace app {
namespace Clusters {
namespace ProximityRanging {
using Status = Protocols::InteractionModel::Status;
namespace {
static constexpr uint8_t kInvalidSessionId = 0;
} // namespace
CHIP_ERROR ProximityRangingCluster::Startup(ServerClusterContext & context)
{
ReturnErrorOnFailure(DefaultServerCluster::Startup(context));
VerifyOrReturnError(mDriver != nullptr, CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = mDriver->Init(*this);
if (err != CHIP_NO_ERROR)
{
DefaultServerCluster::Shutdown(ClusterShutdownType::kClusterShutdown);
}
return err;
}
void ProximityRangingCluster::Shutdown(ClusterShutdownType shutdownType)
{
DefaultServerCluster::Shutdown(shutdownType);
if (mDriver != nullptr)
{
mDriver->Shutdown();
}
}
DataModel::ActionReturnStatus ProximityRangingCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
VerifyOrReturnError(mDriver != nullptr, CHIP_ERROR_INCORRECT_STATE);
switch (request.path.mAttributeId)
{
case Attributes::RangingCapabilities::Id:
return mDriver->GetRangingCapabilities(encoder);
case Attributes::BLEDeviceID::Id: {
auto config = mDriver->GetBleRbcConfig();
VerifyOrReturnError(config.has_value(), Status::UnsupportedAttribute);
return encoder.Encode(config->deviceId);
}
case Attributes::WiFiDevIK::Id: {
auto config = mDriver->GetWiFiUsdConfig();
VerifyOrReturnError(config.has_value(), Status::UnsupportedAttribute);
return encoder.Encode(ByteSpan(config->deviceIdentityKey));
}
case Attributes::BLTDevIK::Id: {
auto config = mDriver->GetBltcsConfig();
VerifyOrReturnError(config.has_value(), Status::UnsupportedAttribute);
return encoder.Encode(ByteSpan(config->deviceIdentityKey));
}
case Attributes::BLTCSSecurityLevel::Id: {
auto config = mDriver->GetBltcsConfig();
VerifyOrReturnError(config.has_value(), Status::UnsupportedAttribute);
return encoder.Encode(config->securityLevel);
}
case Attributes::BLTCSModeCapability::Id: {
auto config = mDriver->GetBltcsConfig();
VerifyOrReturnError(config.has_value(), Status::UnsupportedAttribute);
return encoder.Encode(config->modeCapability);
}
case Attributes::SessionIDList::Id: {
const size_t numSessions = mDriver->GetNumActiveSessionIds();
if (numSessions == 0)
{
return encoder.EncodeEmptyList();
}
Platform::ScopedMemoryBuffer<uint8_t> buf;
VerifyOrReturnError(buf.Calloc(numSessions), Status::ResourceExhausted);
Span<uint8_t> sessionIds(buf.Get(), numSessions);
ReturnErrorOnFailure(mDriver->GetActiveSessionIds(sessionIds));
return encoder.EncodeList([&sessionIds](const auto & listEncoder) -> CHIP_ERROR {
for (size_t i = 0; i < sessionIds.size(); i++)
{
ReturnErrorOnFailure(listEncoder.Encode(sessionIds.data()[i]));
}
return CHIP_NO_ERROR;
});
}
case Attributes::FeatureMap::Id:
return encoder.Encode(mFeatureMap);
case Attributes::ClusterRevision::Id:
return encoder.Encode(ProximityRanging::kRevision);
default:
return Status::UnsupportedAttribute;
}
}
CHIP_ERROR ProximityRangingCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
using OptionalEntry = AttributeListBuilder::OptionalAttributeEntry;
OptionalEntry featureAttributes[] = {
{ mFeatureMap.Has(Feature::kWiFiUsdProximityDetection), Attributes::WiFiDevIK::kMetadataEntry },
{ mFeatureMap.Has(Feature::kBleBeaconRssi), Attributes::BLEDeviceID::kMetadataEntry },
{ mFeatureMap.Has(Feature::kBluetoothChannelSounding), Attributes::BLTDevIK::kMetadataEntry },
{ mFeatureMap.Has(Feature::kBluetoothChannelSounding), Attributes::BLTCSSecurityLevel::kMetadataEntry },
{ mFeatureMap.Has(Feature::kBluetoothChannelSounding), Attributes::BLTCSModeCapability::kMetadataEntry },
};
AttributeListBuilder listBuilder(builder);
return listBuilder.Append(Span(ProximityRanging::Attributes::kMandatoryMetadata), Span(featureAttributes));
}
CHIP_ERROR ProximityRangingCluster::AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
{
static constexpr DataModel::AcceptedCommandEntry kCommands[] = {
Commands::StartRangingRequest::kMetadataEntry,
Commands::StopRangingRequest::kMetadataEntry,
};
return builder.ReferenceExisting(kCommands);
}
CHIP_ERROR ProximityRangingCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder)
{
static constexpr CommandId kGenerated[] = { Commands::StartRangingResponse::Id };
return builder.ReferenceExisting(kGenerated);
}
std::optional<DataModel::ActionReturnStatus> ProximityRangingCluster::InvokeCommand(const DataModel::InvokeRequest & request,
TLV::TLVReader & input_arguments,
CommandHandler * handler)
{
switch (request.path.mCommandId)
{
case Commands::StartRangingRequest::Id:
return HandleStartRangingRequest(request, input_arguments, handler);
case Commands::StopRangingRequest::Id:
return HandleStopRangingRequest(request, input_arguments);
default:
return Status::UnsupportedCommand;
}
}
std::optional<DataModel::ActionReturnStatus>
ProximityRangingCluster::HandleStartRangingRequest(const DataModel::InvokeRequest & request, TLV::TLVReader & reader,
CommandHandler * handler)
{
VerifyOrReturnError(mDriver != nullptr, CHIP_ERROR_INCORRECT_STATE);
Commands::StartRangingRequest::DecodableType commandData;
ReturnErrorOnFailure(commandData.Decode(reader));
Commands::StartRangingResponse::Type response;
ResultCodeEnum resultCode;
uint8_t sessionId = GenerateSessionId();
if (sessionId == kInvalidSessionId)
{
// Failed to generate session ID without collision
resultCode = ResultCodeEnum::kBusySessionCapacityReached;
}
else
{
resultCode = mDriver->HandleStartRanging(sessionId, commandData);
}
response.resultCode = resultCode;
if (resultCode == ResultCodeEnum::kAccepted)
{
response.sessionID.SetNonNull(sessionId);
}
else
{
response.sessionID.SetNull();
}
handler->AddResponse(request.path, response);
return std::nullopt;
}
DataModel::ActionReturnStatus ProximityRangingCluster::HandleStopRangingRequest(const DataModel::InvokeRequest & request,
TLV::TLVReader & reader)
{
VerifyOrReturnError(mDriver != nullptr, CHIP_ERROR_INCORRECT_STATE);
Commands::StopRangingRequest::DecodableType commandData;
VerifyOrReturnValue(commandData.Decode(reader) == CHIP_NO_ERROR, Status::InvalidCommand);
CHIP_ERROR err = mDriver->HandleStopRanging(commandData.sessionID);
if (err == CHIP_ERROR_NOT_FOUND)
{
// If SessionID does not match any active ranging session, the Server SHALL response with the status code INVALID_IN_STATE
return Status::InvalidInState;
}
return err;
}
void ProximityRangingCluster::OnMeasurementData(uint8_t sessionId, const Structs::RangingMeasurementDataStruct::Type & measurement)
{
VerifyOrReturn(mContext != nullptr);
Events::RangingResult::Type event;
event.sessionID = sessionId;
event.rangingResultData = measurement;
mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId);
}
void ProximityRangingCluster::OnSessionStopped(uint8_t sessionId, RangingSessionStatusEnum status)
{
VerifyOrReturn(mContext != nullptr);
Events::RangingSessionStatus::Type event;
event.sessionID = sessionId;
event.status = status;
mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId);
}
uint8_t ProximityRangingCluster::GenerateSessionId()
{
VerifyOrReturnValue(mDriver != nullptr, kInvalidSessionId);
const size_t numSessions = mDriver->GetNumActiveSessionIds();
if (numSessions == 0)
{
uint8_t candidate = mNextSessionId++;
if (candidate == kInvalidSessionId)
{
candidate = mNextSessionId++;
}
return candidate;
}
Platform::ScopedMemoryBuffer<uint8_t> buf;
VerifyOrReturnValue(buf.Calloc(numSessions), kInvalidSessionId);
Span<uint8_t> activeSessions(buf.Get(), numSessions);
if (mDriver->GetActiveSessionIds(activeSessions) != CHIP_NO_ERROR)
{
return kInvalidSessionId;
}
for (size_t attempt = 0; attempt <= numSessions; attempt++)
{
uint8_t candidate = mNextSessionId++;
if (candidate == kInvalidSessionId)
{
candidate = mNextSessionId++;
}
if (std::none_of(activeSessions.data(), activeSessions.data() + activeSessions.size(),
[candidate](uint8_t value) { return value == candidate; }))
{
return candidate;
}
}
return kInvalidSessionId;
}
void ProximityRangingCluster::OnAttributeChanged(AttributeId attributeId)
{
NotifyAttributeChanged(attributeId);
}
} // namespace ProximityRanging
} // namespace Clusters
} // namespace app
} // namespace chip