-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
base: master
Are you sure you want to change the base?
Changes from 19 commits
4eab278
fc2b8f8
78711e2
e8c57c0
8f7a492
9bf5747
576c374
a854a2c
2f39744
57f2321
4b9868c
0e38d1c
e42fd3a
c4b375c
ee94351
8bec18e
ac4cb60
1756691
cfbd633
dc1cd9f
cb93e4f
a3c0a21
f78587f
de4c749
1a6ff83
bfe9ab0
f30596c
b4e4307
0b917c1
2b581c4
9aa1f85
d7057ba
415d351
504277a
58953c6
3f588ac
3ee7a14
baf517b
4023dd8
1866c99
51a85b9
4ac95e1
96833fe
cd19370
7269aec
0785397
9e345d7
b2b80a1
e329977
c65e9a3
7b2a225
b43f24b
3274496
33aaf5c
265ed70
b7f8e79
75c7879
28a21f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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", | ||
] | ||
} |
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: | ||
IntrusiveSingleLinkedList() : mNext(static_cast<SELF *>(this)) {} | ||
~IntrusiveSingleLinkedList() { VerifyOrDie(!IsInList()); } | ||
|
||
// IMPLEMENTATION DETAILS: | ||
// Since `mNext == this` is used as a marker for "is in a list", | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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. | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[[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. | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// 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. | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// 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 | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// derives from `detail::IntrusiveSingleLinkedList` is NOT a public API and is only done | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not do this on purpose: we had the 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> | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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 | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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. | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[[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 | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 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 | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - ClusterRevision::Id - this is implementation defined | ||
/// | ||
/// This call WILL NOT be called for attributes that can be built out of cluster metadata. | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 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 | ||
andy31415 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).