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

Add a server cluster interface registry for use by codegen. #37850

Merged
merged 29 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f301851
Server cluster interface
andy31415 Mar 3, 2025
bb89093
ServerClusterInterfaceRegistry adding
andy31415 Mar 3, 2025
e238230
Merge branch 'master' into server-cluster-interface
andy31415 Mar 3, 2025
c3cb1aa
Merge branch 'server-cluster-interface' into server_cluster_registry
andy31415 Mar 3, 2025
bbb33e4
Merge branch 'master' into server_cluster_registry
andreilitvin Mar 4, 2025
7fdd6b4
Update src/data-model-providers/codegen/ServerClusterInterfaceRegistry.h
andy31415 Mar 4, 2025
0a6a7f9
Code review: forward declare
andreilitvin Mar 4, 2025
ab8a9b2
Review comment: switch order of members
andreilitvin Mar 4, 2025
d9aa48e
Updated logic: the condition is not relevant anymore
andreilitvin Mar 4, 2025
a062ca2
Code review: added comment
andreilitvin Mar 4, 2025
a92317c
Restyle
andreilitvin Mar 4, 2025
e73955c
Merge branch 'master' into server_cluster_registry
andreilitvin Mar 4, 2025
b60ebad
Comment update
andy31415 Mar 5, 2025
2daa37c
Updates to make callers use memory allocation
andreilitvin Mar 6, 2025
7a2bb76
Self review: fix an include
andreilitvin Mar 6, 2025
4e819fa
Fix constexpr logic
andreilitvin Mar 6, 2025
7da6312
Restyle
andreilitvin Mar 6, 2025
0c1bc26
minor comment update so github is not confused about head SHA
andreilitvin Mar 6, 2025
ea3e4d0
Added useless unit test to get coverage
andreilitvin Mar 6, 2025
86aeb05
Restyle
andreilitvin Mar 6, 2025
4fec4f1
Update src/app/server-cluster/ServerClusterInterface.h
andy31415 Mar 6, 2025
868d8a9
Update src/data-model-providers/codegen/ServerClusterInterfaceRegistr…
andy31415 Mar 6, 2025
30f5679
Update src/data-model-providers/codegen/ServerClusterInterfaceRegistr…
andy31415 Mar 6, 2025
c88e835
Update src/data-model-providers/codegen/ServerClusterInterfaceRegistry.h
andy31415 Mar 6, 2025
4ea82ec
Update src/data-model-providers/codegen/ServerClusterInterfaceRegistry.h
andy31415 Mar 6, 2025
af23a93
Disallow null ptr registrations
andreilitvin Mar 6, 2025
73c46b6
Merge branch 'server_cluster_registry' of github.com:andy31415/connec…
andreilitvin Mar 6, 2025
4463e95
Restyled by clang-format
restyled-commits Mar 6, 2025
2fa2125
Reworded based on code review
andreilitvin Mar 6, 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
14 changes: 14 additions & 0 deletions src/data-model-providers/codegen/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ source_set("instance-header") {
# generally being unit tests or data_model.gni/data_model.cmake files)
sources = [ "Instance.h" ]
}

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

public_deps = [
"${chip_root}/src/app:paths",
"${chip_root}/src/app/server-cluster",
"${chip_root}/src/lib/core:types",
"${chip_root}/src/lib/support",
]
}
224 changes: 224 additions & 0 deletions src/data-model-providers/codegen/ServerClusterInterfaceRegistry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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.
*/
#include <data-model-providers/codegen/ServerClusterInterfaceRegistry.h>

#include <app/ConcreteClusterPath.h>
#include <app/server-cluster/ServerClusterInterface.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>

namespace chip {
namespace app {

void ServerClusterInterfaceRegistry::ClearSingleLinkedList(RegisteredServerClusterInterface * clusters)
{
while (clusters != nullptr)
{
RegisteredServerClusterInterface * next = clusters->next;
Platform::Delete(clusters);
clusters = next;
}
}

ServerClusterInterfaceRegistry::~ServerClusterInterfaceRegistry()
{
while (mEndpoints != nullptr)
{
UnregisterAllFromEndpoint(mEndpoints->endpointId);
}
}

CHIP_ERROR ServerClusterInterfaceRegistry::AllocateNewEndpointClusters(EndpointId endpointId, EndpointClusters *& dest)
{
auto result = Platform::New<EndpointClusters>();
VerifyOrReturnError(result != nullptr, CHIP_ERROR_NO_MEMORY);

result->endpointId = endpointId;
result->firstCluster = nullptr;
result->next = mEndpoints;
mEndpoints = result;
dest = result;

return CHIP_NO_ERROR;
}

CHIP_ERROR ServerClusterInterfaceRegistry::Register(EndpointId endpointId, ServerClusterInterface * cluster)
{
VerifyOrReturnError(cluster != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(endpointId != kInvalidEndpointId, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(cluster->GetClusterId() != kInvalidClusterId, CHIP_ERROR_INVALID_ARGUMENT);

// duplicate registrations are disallowed
VerifyOrReturnError(Get(ConcreteClusterPath(endpointId, cluster->GetClusterId())) == nullptr, CHIP_ERROR_DUPLICATE_KEY_ID);

EndpointClusters * endpointClusters = FindClusters(endpointId);
if (endpointClusters == nullptr)
{
ReturnErrorOnFailure(AllocateNewEndpointClusters(endpointId, endpointClusters));
}

auto entry = Platform::New<RegisteredServerClusterInterface>(cluster, endpointClusters->firstCluster);
VerifyOrReturnError(entry != nullptr, CHIP_ERROR_NO_MEMORY);

endpointClusters->firstCluster = entry;

return CHIP_NO_ERROR;
}

ServerClusterInterface * ServerClusterInterfaceRegistry::Unregister(const ConcreteClusterPath & path)
{
EndpointClusters * endpointClusters = FindClusters(path.mEndpointId);
VerifyOrReturnValue(endpointClusters != nullptr, nullptr);
VerifyOrReturnValue(endpointClusters->firstCluster != nullptr, nullptr);

RegisteredServerClusterInterface * prev = nullptr;
RegisteredServerClusterInterface * current = endpointClusters->firstCluster;

while (current != nullptr)
{
if (current->serverClusterInterface->GetClusterId() == path.mClusterId)
{
// take the item out of the current list and return it.
RegisteredServerClusterInterface * next = current->next;

if (prev == nullptr)
{
endpointClusters->firstCluster = next;
}
else
{
prev->next = next;
}

if (mCachedInterface == current->serverClusterInterface)
{
mCachedClusterEndpointId = kInvalidEndpointId;
mCachedInterface = nullptr;
}

ServerClusterInterface * result = current->serverClusterInterface;
Platform::MemoryFree(current);

return result;
}

prev = current;
current = current->next;
}

// Not found.
return nullptr;
}

ServerClusterInterfaceRegistry::ClustersList ServerClusterInterfaceRegistry::ClustersOnEndpoint(EndpointId endpointId)
{
EndpointClusters * clusters = FindClusters(endpointId);

if (clusters == nullptr)
{
return nullptr;
}

return clusters->firstCluster;
}

void ServerClusterInterfaceRegistry::UnregisterAllFromEndpoint(EndpointId endpointId)
{
if (mCachedClusterEndpointId == endpointId)
{
// all clusters on the given endpoint will be unregistered.
mCachedInterface = nullptr;
mCachedClusterEndpointId = kInvalidEndpointId;
}

EndpointClusters * prev = nullptr;
EndpointClusters * current = mEndpoints;
while (current != nullptr)
{
if (current->endpointId == endpointId)
{
if (prev == nullptr)
{
mEndpoints = current->next;
}
else
{
prev->next = current->next;
}
ClearSingleLinkedList(current->firstCluster);
Platform::Delete(current);
return;
}

prev = current;
current = current->next;
}
}

ServerClusterInterface * ServerClusterInterfaceRegistry::Get(const ConcreteClusterPath & path)
{
EndpointClusters * endpointClusters = FindClusters(path.mEndpointId);
VerifyOrReturnValue(endpointClusters != nullptr, nullptr);
VerifyOrReturnValue(endpointClusters->firstCluster != nullptr, nullptr);

// Check the cache to speed things up
if ((mCachedClusterEndpointId == path.mEndpointId) && (mCachedInterface != nullptr) &&
(mCachedInterface->GetClusterId() == path.mClusterId))
{
return mCachedInterface;
}

// The cluster searched for is not cached, do a linear search for it
RegisteredServerClusterInterface * current = endpointClusters->firstCluster;

while (current != nullptr)
{
if (current->serverClusterInterface->GetClusterId() == path.mClusterId)
{
mCachedClusterEndpointId = path.mEndpointId;
mCachedInterface = current->serverClusterInterface;
return mCachedInterface;
}

current = current->next;
}

// not found
return nullptr;
}

ServerClusterInterfaceRegistry::EndpointClusters * ServerClusterInterfaceRegistry::FindClusters(EndpointId endpointId)
{
// invalid cluster id is NOT acceptable (since static allocation uses the
// invalid cluster id as a marker of "not used")
VerifyOrReturnValue(endpointId != kInvalidEndpointId, nullptr);

for (EndpointClusters * p = mEndpoints; p != nullptr; p = p->next)
{
if (p->endpointId == endpointId)
{
return p;
}
}

return nullptr;
}

} // namespace app
} // namespace chip
138 changes: 138 additions & 0 deletions src/data-model-providers/codegen/ServerClusterInterfaceRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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/ConcreteClusterPath.h>
#include <app/server-cluster/ServerClusterInterface.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>

namespace chip {
namespace app {

/// Allows registering and retrieving ServerClusterInterface instances for specific cluster paths.
class ServerClusterInterfaceRegistry
{
private:
/// A single-linked list of registered server cluster interfaces
struct RegisteredServerClusterInterface
{
// A single-linked list of clusters registered for the given `endpointId`
ServerClusterInterface * serverClusterInterface = nullptr;
RegisteredServerClusterInterface * next = nullptr;

constexpr RegisteredServerClusterInterface(ServerClusterInterface * cluster, RegisteredServerClusterInterface * n) :
serverClusterInterface(cluster), next(n)
{}
};

public:
/// represents an iterable list of clusters
class ClustersList
{
public:
class Iterator
{
public:
Iterator(RegisteredServerClusterInterface * interface) : mInterface(interface) {}

Iterator & operator++()
{
if (mInterface != nullptr)
{
mInterface = mInterface->next;
}
return *this;
}
bool operator==(const Iterator & other) const { return mInterface == other.mInterface; }
bool operator!=(const Iterator & other) const { return mInterface != other.mInterface; }
ServerClusterInterface * operator*() { return mInterface->serverClusterInterface; }

private:
RegisteredServerClusterInterface * mInterface;
};

ClustersList(RegisteredServerClusterInterface * start) : mStart(start) {}
Iterator begin() { return mStart; }
Iterator end() { return nullptr; }

private:
RegisteredServerClusterInterface * mStart;
};

~ServerClusterInterfaceRegistry();

/// Associate a specific interface with the given endpoint.
///
/// There can be only a single registration for a given `endpointId/clusterId` path.
/// A registration will return an error if a registration already exists on
/// the given `endpointId/cluster->GetClusterID()` already exists
///
/// Registrations need a valid endpointId and cluster MUST return a valid cluster id.
[[nodiscard]] CHIP_ERROR Register(EndpointId endpointId, ServerClusterInterface * cluster);

/// Remove an existing registration for a given endpoint/cluster path.
///
/// Returns the previous registration if any exists (or nullptr if nothing
/// to unregister)
ServerClusterInterface * Unregister(const ConcreteClusterPath & path);

/// Return the interface registered for the given cluster path or nullptr if one does not exist
ServerClusterInterface * Get(const ConcreteClusterPath & path);

/// ClustersList is valid as a snapshot only and should not be saved.
ClustersList ClustersOnEndpoint(EndpointId endpointId);

/// Unregister all registrations for the given endpoint.
void UnregisterAllFromEndpoint(EndpointId endpointId);

private:
/// tracks clusters registered to a particular endpoint
struct EndpointClusters
{
// The endpointId for this registration. kInvalidEndpointId means
// not allocated/used
EndpointId endpointId = kInvalidEndpointId;

// A single-linked list of clusters registered for the given `endpointId`
RegisteredServerClusterInterface * firstCluster = nullptr;

/// A single-linked list of endpoint clusters that is dynamically
/// allocated.
EndpointClusters * next;
};

// Dynamic allocated endpoint cluters.
EndpointClusters * mEndpoints = nullptr;

// A one-element cache to speed up finding a cluster within an endpoint.
// The endpointId specifies which endpoint the cache belongs to.
ClusterId mCachedClusterEndpointId = kInvalidEndpointId;
ServerClusterInterface * mCachedInterface = nullptr;

/// returns nullptr if not found
EndpointClusters * FindClusters(EndpointId endpointId);

/// Get a new usable endpoint cluster
CHIP_ERROR AllocateNewEndpointClusters(EndpointId endpointId, EndpointClusters *& dest);

/// Clear and free memory for the given linked list
static void ClearSingleLinkedList(RegisteredServerClusterInterface * clusters);
};

} // namespace app
} // namespace chip
2 changes: 2 additions & 0 deletions src/data-model-providers/codegen/model.gni
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ codegen_data_model_SOURCES = [
codegen_data_model_PUBLIC_DEPS = [
"${chip_root}/src/app/common:attribute-type",
"${chip_root}/src/app/data-model-provider",
"${chip_root}/src/app/server-cluster",
"${chip_root}/src/data-model-providers/codegen:instance-header",
"${chip_root}/src/data-model-providers/codegen:registry",
"${chip_root}/src/app/util/persistence",
]
1 change: 1 addition & 0 deletions src/data-model-providers/codegen/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ chip_test_suite("tests") {
test_sources = [
"TestCodegenModelViaMocks.cpp",
"TestEmberAttributeDataBuffer.cpp",
"TestServerClusterInterfaceRegistry.cpp",
]

cflags = [ "-Wconversion" ]
Expand Down
Loading
Loading