Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declare a "server cluster interface" that is intended to combine "attribute access interface" and "command handler interface" #37541

Open
wants to merge 58 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4eab278
Squashed commit of a server cluster interface.
andy31415 Feb 12, 2025
fc2b8f8
Fix some clang tidy warnings: fix self assignment, add some nodiscards
andy31415 Feb 13, 2025
78711e2
Slight comment update
andy31415 Feb 13, 2025
e8c57c0
Comment update
andy31415 Feb 13, 2025
8f7a492
Move the single list detail as a separate base class
andy31415 Feb 13, 2025
9bf5747
Switch to a cache for cluster interfaces instead of using linked list…
andy31415 Feb 13, 2025
576c374
Restyled by clang-format
restyled-commits Feb 13, 2025
a854a2c
Rename Invoke to InvokeCommand for DataModel::Provier and ClusterServ…
andy31415 Feb 13, 2025
2f39744
Fix an include
andy31415 Feb 13, 2025
57f2321
Fix some extra things and enforce that lifetimes are reasonable
andy31415 Feb 13, 2025
4b9868c
make ServerClusterInterface a pure virtual interface (for most purpos…
andreilitvin Feb 14, 2025
0e38d1c
Added some comments
andreilitvin Feb 14, 2025
e42fd3a
Restyle
andreilitvin Feb 14, 2025
c4b375c
Fix tests
andreilitvin Feb 14, 2025
ee94351
Restyle
andreilitvin Feb 14, 2025
8bec18e
Fix compilation and tests
andreilitvin Feb 14, 2025
ac4cb60
Merge branch 'master' into server-cluster-interface
andreilitvin Feb 14, 2025
1756691
Fix bug and get 100% coverage again
andreilitvin Feb 14, 2025
cfbd633
Make code for removal of list element cleaner
andreilitvin Feb 14, 2025
dc1cd9f
Update src/app/server-cluster/ServerClusterInterface.h
andy31415 Feb 18, 2025
cb93e4f
Update src/app/server-cluster/ServerClusterInterface.h
andy31415 Feb 18, 2025
a3c0a21
Update src/app/server-cluster/ServerClusterInterface.h
andy31415 Feb 18, 2025
f78587f
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
de4c749
Update src/app/server-cluster/ServerClusterInterface.h
andy31415 Feb 18, 2025
1a6ff83
Update src/app/server-cluster/ServerClusterInterfaceRegistry.cpp
andy31415 Feb 18, 2025
bfe9ab0
Update src/app/server-cluster/ServerClusterInterfaceRegistry.cpp
andy31415 Feb 18, 2025
f30596c
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
b4e4307
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
0b917c1
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
2b581c4
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
9aa1f85
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
d7057ba
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
415d351
Rename preallocated endpoints
andreilitvin Feb 18, 2025
504277a
Restyle
andreilitvin Feb 18, 2025
58953c6
Merge branch 'master' into server-cluster-interface
andreilitvin Feb 18, 2025
3f588ac
Update src/app/server-cluster/ServerClusterInterfaceRegistry.h
andy31415 Feb 18, 2025
3ee7a14
Address some review feedback
andreilitvin Feb 18, 2025
baf517b
Fix up tests: no more copying
andreilitvin Feb 18, 2025
4023dd8
Merge branch 'server-cluster-interface' of github.com:andy31415/conne…
andreilitvin Feb 18, 2025
1866c99
Update linked list clearning function
andreilitvin Feb 18, 2025
51a85b9
Comment updating
andreilitvin Feb 18, 2025
4ac95e1
Remove some code duplication
andreilitvin Feb 18, 2025
96833fe
Restyle
andreilitvin Feb 18, 2025
cd19370
Undo submodule change
andreilitvin Feb 18, 2025
7269aec
Use endpointId everywhere
andreilitvin Feb 18, 2025
0785397
One more rename
andreilitvin Feb 18, 2025
9e345d7
Rename StandardServerCluster to DefaultServerCluster
andreilitvin Feb 18, 2025
b2b80a1
Restyle
andreilitvin Feb 18, 2025
e329977
Merge branch 'master' into server-cluster-interface
andreilitvin Feb 18, 2025
c65e9a3
Use std::array instead of c-style array
andreilitvin Feb 18, 2025
7b2a225
Simpler code for empty return (clang-tidy suggestion)
andreilitvin Feb 18, 2025
b43f24b
A few more clang-tidy suggested fixes
andreilitvin Feb 18, 2025
3274496
Comment updated
andreilitvin Feb 18, 2025
33aaf5c
Restyled by clang-format
restyled-commits Feb 18, 2025
265ed70
Merge branch 'master' into server-cluster-interface
andy31415 Feb 19, 2025
b7f8e79
Merge branch 'master' into server-cluster-interface
andreilitvin Feb 25, 2025
75c7879
Add a "List clusters on endpoint" support into the server cluster reg…
andreilitvin Feb 25, 2025
28a21f7
Fix a static cast
andreilitvin Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/ids_and_codes/ERROR_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ This file was **AUTOMATICALLY** generated by
| 37 | 0x25 | `CHIP_ERROR_UNKNOWN_IMPLICIT_TLV_TAG` |
| 38 | 0x26 | `CHIP_ERROR_WRONG_TLV_TYPE` |
| 39 | 0x27 | `CHIP_ERROR_TLV_CONTAINER_OPEN` |
| 40 | 0x28 | `CHIP_ERROR_IN_USE` |
| 42 | 0x2A | `CHIP_ERROR_INVALID_MESSAGE_TYPE` |
| 43 | 0x2B | `CHIP_ERROR_UNEXPECTED_TLV_ELEMENT` |
| 45 | 0x2D | `CHIP_ERROR_NOT_IMPLEMENTED` |
Expand Down
5 changes: 4 additions & 1 deletion scripts/build_coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,19 @@ lcov --initial --capture --directory "$OUTPUT_ROOT/obj/src" \
--exclude="$PWD"/zzz_generated/* \
--exclude="$PWD"/third_party/* \
--exclude=/usr/include/* \
--ignore-errors inconsistent \
--output-file "$COVERAGE_ROOT/lcov_base.info"

lcov --capture --directory "$OUTPUT_ROOT/obj/src" \
--ignore-errors inconsistent \
--exclude="$PWD"/zzz_generated/* \
--exclude="$PWD"/third_party/* \
--exclude=/usr/include/* \
--ignore-errors inconsistent \
--output-file "$COVERAGE_ROOT/lcov_test.info"

lcov --add-tracefile "$COVERAGE_ROOT/lcov_base.info" \
lcov --ignore-errors inconsistent \
--add-tracefile "$COVERAGE_ROOT/lcov_base.info" \
--add-tracefile "$COVERAGE_ROOT/lcov_test.info" \
--ignore-errors inconsistent \
--output-file "$COVERAGE_ROOT/lcov_final.info"
Expand Down
5 changes: 4 additions & 1 deletion src/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ if (chip_build_tests) {
deps = []
tests = [
"${chip_root}/src/access/tests",
"${chip_root}/src/app/data-model/tests",
"${chip_root}/src/app/cluster-building-blocks/tests",
"${chip_root}/src/app/data-model-provider/tests",
"${chip_root}/src/app/data-model/tests",
"${chip_root}/src/app/icd/server/tests",
"${chip_root}/src/crypto/tests",
"${chip_root}/src/inet/tests",
Expand Down Expand Up @@ -108,6 +108,9 @@ if (chip_build_tests) {
# https://github.com/project-chip/connectedhomeip/issues/35624
"${chip_root}/src/credentials/tests",
"${chip_root}/src/lib/support/tests",

# Disabled on EFR32 since "include <random>" fails with `::fabs` not defined
"${chip_root}/src/app/server-cluster/tests",
]
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/InteractionModelEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1701,7 +1701,7 @@ void InteractionModelEngine::DispatchCommand(CommandHandlerImpl & apCommandObj,
request.invokeFlags.Set(DataModel::InvokeFlags::kTimed, apCommandObj.IsTimedInvoke());
request.subjectDescriptor = &subjectDescriptor;

std::optional<DataModel::ActionReturnStatus> status = GetDataModelProvider()->Invoke(request, apPayload, &apCommandObj);
std::optional<DataModel::ActionReturnStatus> status = GetDataModelProvider()->InvokeCommand(request, apPayload, &apCommandObj);

// Provider indicates that handler status or data was already set (or will be set asynchronously) by
// returning std::nullopt. If any other value is returned, it is requesting that a status is set. This
Expand Down
4 changes: 2 additions & 2 deletions src/app/data-model-provider/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ class Provider : public ProviderMetadataTree
/// - if a value is returned (not nullopt) then the handler response MUST NOT be filled. The caller
/// will then issue `handler->AddStatus(request.path, <return_value>->GetStatusCode())`. This is a
/// convenience to make writing Invoke calls easier.
virtual std::optional<ActionReturnStatus> Invoke(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments,
CommandHandler * handler) = 0;
virtual std::optional<ActionReturnStatus> InvokeCommand(const InvokeRequest & request, chip::TLV::TLVReader & input_arguments,
CommandHandler * handler) = 0;

private:
InteractionModelContext mContext = { nullptr };
Expand Down
6 changes: 2 additions & 4 deletions src/app/data-model-provider/tests/TestActionReturnStatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "lib/core/CHIPError.h"
#include "protocols/interaction_model/StatusCode.h"
#include "pw_unit_test/framework_backend.h"
#include <app/data-model-provider/ActionReturnStatus.h>
#include <app/data-model-provider/StringBuilderAdapters.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CodeUtils.h>
#include <protocols/interaction_model/StatusCode.h>

#include <pw_unit_test/framework.h>

Expand Down
49 changes: 49 additions & 0 deletions src/app/server-cluster/BUILD.gn
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) 2025 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.
import("//build_overrides/chip.gni")

source_set("server-cluster") {
sources = [
"ServerClusterInterface.h",
"StandardServerCluster.cpp",
"StandardServerCluster.h",
]

public_deps = [
"${chip_root}/src/app:attribute-access",
"${chip_root}/src/app:command-handler-interface",
"${chip_root}/src/app:paths",
"${chip_root}/src/app/common:ids",
"${chip_root}/src/app/data-model-provider",
"${chip_root}/src/app/data-model-provider:metadata",
"${chip_root}/src/crypto",
"${chip_root}/src/lib/core:error",
"${chip_root}/src/lib/core:types",
"${chip_root}/src/lib/support",
]
}

source_set("registry") {
sources = [
"ServerClusterInterfaceRegistry.cpp",
"ServerClusterInterfaceRegistry.h",
]

public_deps = [
":server-cluster",
"${chip_root}/src/app:paths",
"${chip_root}/src/lib/core:types",
"${chip_root}/src/lib/support",
]
}
200 changes: 200 additions & 0 deletions src/app/server-cluster/ServerClusterInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2025 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.
*/
#pragma once

#include <app/AttributeValueDecoder.h>
#include <app/AttributeValueEncoder.h>
#include <app/CommandHandler.h>
#include <app/ConcreteClusterPath.h>
#include <app/data-model-provider/ActionReturnStatus.h>
#include <app/data-model-provider/MetadataList.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model-provider/OperationTypes.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/BitFlags.h>

namespace chip {
namespace app {

namespace detail {

/// This class implements an intrusive single linked list
///
/// The class is an implementation detail for the use of ServerClusterInterfaceRegistry and
/// is NOT considered public API. No API-compatibility is guaranteed across different
/// SDK releases.
template <typename SELF>
class IntrusiveSingleLinkedList
{
public:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like almost everything on this class should be protected, not public?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if I make friend classes. I am trying hard to avoid friend since historically the pattern of "protect the outside by using protected/private and friend" just made code harder to use.

I need the Registry to be able to control the linked list. Realistically we want the registry to own the storage, the fact that we use an intrusive list is just to make it cheaper to create pre-allocated RAM storage (instead of some std::list I now can use potentially static allocated storage per cluster instance).

IntrusiveSingleLinkedList() : mNext(static_cast<SELF *>(this)) {}
~IntrusiveSingleLinkedList() { VerifyOrDie(!IsInList()); }

// IMPLEMENTATION DETAILS:
// Since `mNext == this` is used as a marker for "is in a list",
// the assignment of these interfaces is overloaded even for the move operator.
IntrusiveSingleLinkedList(IntrusiveSingleLinkedList && other) :
mNext((other.mNext == &other) ? static_cast<SELF *>(this) : other.mNext)
{
other.SetNotInList();
}
IntrusiveSingleLinkedList(const IntrusiveSingleLinkedList & other) :
mNext((other.mNext == &other) ? static_cast<SELF *>(this) : other.mNext)
{}
IntrusiveSingleLinkedList & operator=(const IntrusiveSingleLinkedList & other)
{
if (&other != this)
{
mNext = (other.mNext == &other) ? static_cast<SELF *>(this) : other.mNext;
}
return *this;
}
IntrusiveSingleLinkedList & operator=(IntrusiveSingleLinkedList && other)
{
mNext = (other.mNext == &other) ? static_cast<SELF *>(this) : other.mNext;
other.SetNotInList();
return *this;
}

/// Determines if this object is part of a linked list already or not.
[[nodiscard]] bool IsInList() const { return (mNext != this); }

/// Marks this object as not being part of a linked list
void SetNotInList() { mNext = static_cast<SELF *>(this); }

/// Returns a "next" pointer when the ServerClusterInterface is assumed to be
/// part of a SINGLE linked list.
[[nodiscard]] SELF * GetNextListItem() const
{
VerifyOrDie(mNext != this);
return mNext;
}

/// Sets the "next" pointer when the SELF is assumed to be
/// part of a SINGLE linked list.
///
/// Returns the old value of "next"
SELF * SetNextListItem(SELF * value)
{
VerifyOrDie(value != this);
auto previousValue = mNext;
mNext = value;
return previousValue;
}

private:
// The mNext pointer has 2 (!) states:
// - `this` means this item is NOT part of a linked list (used since we would generally
// not allow loops)
// - OTHER values, including nullptr, when this is part of a REAL LIST
SELF * mNext; /* = this (in constructor) */
};

} // namespace detail

/// Defines an active cluster on an endpoint.
///
/// Provides metadata as well as interaction processing (attribute read/write and command handling).
///
/// Implementation note:
/// - this class is highly coupled with ServerClusterInterfaceRegistry. The fact that it
/// derives from `detail::IntrusiveSingleLinkedList` is NOT a public API and is only done
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to ask: should this be protected inheritance with ServerClusterInterfaceRegistry marked as friend?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not do this on purpose: we had the hide away things and use friend throughout our codebase and it makes the code hard to maintain and test. AAI and CHI do not hide away their intrusive pointers either so this is no worse, except that this time things are documented.

I expect we may iterate here - I have already received feedback that this registry is for the convenience of the CodegenDataModel and maybe for non-codegen we could find a way to not pay this price.

/// for `ServerClusterInterfaceRegistry` usage. Code may be updated to support different
/// implementations for storing interface.
class ServerClusterInterface : public detail::IntrusiveSingleLinkedList<ServerClusterInterface>
{
public:
ServerClusterInterface() = default;
virtual ~ServerClusterInterface() = default;

ServerClusterInterface(const ServerClusterInterface & other) = default;
ServerClusterInterface(ServerClusterInterface && other) = default;
ServerClusterInterface & operator=(const ServerClusterInterface & other) = default;
ServerClusterInterface & operator=(ServerClusterInterface && other) = default;

///////////////////////////////////// Cluster Metadata Support //////////////////////////////////////////////////
[[nodiscard]] virtual ClusterId GetClusterId() const = 0;

// Every cluster must have a data version. Base class implementation to avoid
// code duplication
//
// SPEC - 7.10.3. Cluster Data Version
// A cluster data version is a metadata increment-only counter value, maintained for each cluster instance.
// [...]
// A cluster data version SHALL increment or be set (wrap) to zero
// if incrementing would exceed its maximum value. A cluster data version
// SHALL be maintained for each cluster instance.
// [...]
// A cluster data version SHALL be incremented if any attribute data changes.
//
[[nodiscard]] virtual DataVersion GetDataVersion() const = 0;

/// Cluster flags can be overridden, however most clusters likely have a default of "nothing special".
///
/// Default implementation returns a 0/empty quality list.
[[nodiscard]] virtual BitFlags<DataModel::ClusterQualityFlags> GetClusterFlags() const = 0;

///////////////////////////////////// Attribute Support ////////////////////////////////////////////////////////

/// ReadAttribute MUST be done on a valid attribute path. `request.path` is expected to have `GetClusterId` as the cluster
/// id as well as an attribute that is included in a `Attributes` call.
///
/// This MUST HANDLE the following global attributes:
/// - FeatureMap::Id - generally 0 as a default
/// - ClusterRevision::Id - this is implementation defined
///
/// This call WILL NOT be called for attributes that can be built out of cluster metadata.
/// Specifically this WILL NOT be called (and does not need to implement handling for) the
/// following attribute IDs:
/// - AcceptedCommandList::Id
/// - AttributeList::Id
/// - GeneratedCommandList::Id
virtual DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) = 0;

/// WriteAttribute MUST be done on a valid attribute path. `request.path` is expected to have `GetClusterId` as the cluster
/// id as well as an attribute that is included in a `Attributes` call.
virtual DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request,
AttributeValueDecoder & decoder) = 0;

/// Attribute list MUST contain global attributes.
///
/// Specifically these attributes MUST always exist in the list for all clusters:
/// - ClusterRevision::Id
/// - FeatureMap::Id
/// - AcceptedCommandList::Id
/// - AttributeList::Id
/// - GeneratedCommandList::Id
/// See SPEC 7.13 Global Elements: `Global Attributes` table
///
virtual CHIP_ERROR Attributes(const ConcreteClusterPath & path,
DataModel::ListBuilder<DataModel::AttributeEntry> & builder) = 0;

///////////////////////////////////// Command Support /////////////////////////////////////////////////////////

virtual std::optional<DataModel::ActionReturnStatus>
InvokeCommand(const DataModel::InvokeRequest & request, chip::TLV::TLVReader & input_arguments, CommandHandler * handler) = 0;

virtual CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
DataModel::ListBuilder<DataModel::AcceptedCommandEntry> & builder) = 0;

virtual CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, DataModel::ListBuilder<CommandId> & builder) = 0;
};

} // namespace app
} // namespace chip
Loading
Loading