Line data Source code
1 : /*
2 : * Copyright (c) 2025 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 : #include <app/server-cluster/ServerClusterInterfaceRegistry.h>
18 :
19 : #include <app/ConcreteClusterPath.h>
20 : #include <app/server-cluster/ServerClusterInterface.h>
21 : #include <lib/core/CHIPError.h>
22 : #include <lib/core/DataModelTypes.h>
23 : #include <lib/support/CHIPMem.h>
24 : #include <lib/support/CodeUtils.h>
25 : #include <optional>
26 :
27 : namespace chip {
28 : namespace app {
29 :
30 241 : ServerClusterInterfaceRegistry::~ServerClusterInterfaceRegistry()
31 : {
32 459 : while (mRegistrations != nullptr)
33 : {
34 218 : ServerClusterRegistration * next = mRegistrations->next;
35 218 : if (mContext.has_value())
36 : {
37 5 : mRegistrations->serverClusterInterface->Shutdown(ClusterShutdownType::kClusterShutdown);
38 : }
39 218 : mRegistrations->next = nullptr;
40 218 : mRegistrations = next;
41 : }
42 241 : }
43 :
44 1072 : CHIP_ERROR ServerClusterInterfaceRegistry::Register(ServerClusterRegistration & entry)
45 : {
46 : // we have no strong way to check if entry is already registered somewhere else, so we use "next" as some
47 : // form of double-check
48 1072 : VerifyOrReturnError(entry.next == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
49 1071 : VerifyOrReturnError(entry.serverClusterInterface != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
50 :
51 1071 : Span<const ConcreteClusterPath> paths = entry.serverClusterInterface->GetPaths();
52 1071 : VerifyOrReturnError(!paths.empty(), CHIP_ERROR_INVALID_ARGUMENT);
53 :
54 2145 : for (const ConcreteClusterPath & path : paths)
55 : {
56 1081 : VerifyOrReturnError(path.HasValidIds(), CHIP_ERROR_INVALID_ARGUMENT);
57 :
58 : // Double-checking for duplicates makes the checks O(n^2) on the total number of registered
59 : // items. We preserve this however we may want to make this optional at some point in time.
60 1078 : VerifyOrReturnError(Get(path) == nullptr, CHIP_ERROR_DUPLICATE_KEY_ID);
61 : }
62 :
63 1064 : if (mContext.has_value())
64 : {
65 : // To preserve similarity with SetContext, do not fail the register even if Startup fails.
66 : // This will cause Shutdown to be called for both successful and failed startups.
67 24 : LogErrorOnFailure(entry.serverClusterInterface->Startup(*mContext));
68 : }
69 :
70 1064 : entry.next = mRegistrations;
71 1064 : mRegistrations = &entry;
72 :
73 1064 : return CHIP_NO_ERROR;
74 : }
75 :
76 843 : CHIP_ERROR ServerClusterInterfaceRegistry::Unregister(ServerClusterInterface * what, ClusterShutdownType clusterShutdownType)
77 : {
78 843 : ServerClusterRegistration * prev = nullptr;
79 843 : ServerClusterRegistration * current = mRegistrations;
80 :
81 60106 : while (current != nullptr)
82 : {
83 59703 : if (current->serverClusterInterface == what)
84 : {
85 : // take the item out of the current list and return it.
86 440 : ServerClusterRegistration * next = current->next;
87 :
88 440 : if (prev == nullptr)
89 : {
90 44 : mRegistrations = next;
91 : }
92 : else
93 : {
94 396 : prev->next = next;
95 : }
96 :
97 440 : if (mCachedInterface == current->serverClusterInterface)
98 : {
99 417 : mCachedInterface = nullptr;
100 : }
101 :
102 440 : current->next = nullptr; // Make sure current does not look like part of a list.
103 440 : if (mContext.has_value())
104 : {
105 10 : current->serverClusterInterface->Shutdown(clusterShutdownType);
106 : }
107 :
108 440 : return CHIP_NO_ERROR;
109 : }
110 :
111 59263 : prev = current;
112 59263 : current = current->next;
113 : }
114 :
115 403 : return CHIP_ERROR_NOT_FOUND;
116 : }
117 :
118 38438 : ServerClusterInterface * ServerClusterInterfaceRegistry::Get(const ConcreteClusterPath & clusterPath)
119 : {
120 : // Check the cache to speed things up
121 38438 : if ((mCachedInterface != nullptr) && mCachedInterface->PathsContains(clusterPath))
122 : {
123 21 : return mCachedInterface;
124 : }
125 :
126 : // The cluster searched for is not cached, do a linear search for it
127 38417 : ServerClusterRegistration * current = mRegistrations;
128 :
129 1736744 : while (current != nullptr)
130 : {
131 1699757 : if (current->serverClusterInterface->PathsContains(clusterPath))
132 : {
133 1430 : mCachedInterface = current->serverClusterInterface;
134 1430 : return mCachedInterface;
135 : }
136 :
137 1698327 : current = current->next;
138 : }
139 :
140 : // not found
141 36987 : return nullptr;
142 : }
143 :
144 167 : CHIP_ERROR ServerClusterInterfaceRegistry::SetContext(ServerClusterContext && context)
145 : {
146 167 : if (mContext.has_value())
147 : {
148 : // if there is no difference, do not re-initialize.
149 4 : VerifyOrReturnError(*mContext != context, CHIP_NO_ERROR);
150 2 : ClearContext();
151 : }
152 :
153 165 : mContext.emplace(std::move(context));
154 165 : bool had_failure = false;
155 :
156 179 : for (ServerClusterRegistration * registration = mRegistrations; registration != nullptr; registration = registration->next)
157 : {
158 14 : CHIP_ERROR err = registration->serverClusterInterface->Startup(*mContext);
159 28 : if (err != CHIP_NO_ERROR)
160 : {
161 : #if CHIP_ERROR_LOGGING
162 : // Paths MUST contain at least one element. Log the first one for identification (even though there may be more)
163 3 : const ConcreteClusterPath path = registration->serverClusterInterface->GetPaths().front();
164 3 : ChipLogError(DataManagement, "Cluster %u/" ChipLogFormatMEI " startup failed: %" CHIP_ERROR_FORMAT, path.mEndpointId,
165 : ChipLogValueMEI(path.mClusterId), err.Format());
166 : #endif
167 3 : had_failure = true;
168 : // NOTE: this makes the object be in an awkward state:
169 : // - cluster is not initialized
170 : // - mContext is valid
171 : // As a result, ::Shutdown on this cluster WILL be called even if startup failed.
172 : }
173 : }
174 :
175 165 : if (had_failure)
176 : {
177 2 : return CHIP_ERROR_HAD_FAILURES;
178 : }
179 :
180 163 : return CHIP_NO_ERROR;
181 : }
182 :
183 185 : void ServerClusterInterfaceRegistry::ClearContext()
184 : {
185 185 : if (!mContext.has_value())
186 : {
187 29 : return;
188 : }
189 166 : for (ServerClusterRegistration * registration = mRegistrations; registration != nullptr; registration = registration->next)
190 : {
191 10 : registration->serverClusterInterface->Shutdown(ClusterShutdownType::kClusterShutdown);
192 : }
193 :
194 156 : mContext.reset();
195 : }
196 :
197 300 : ServerClusterInterfaceRegistry::ServerClusterInstances ServerClusterInterfaceRegistry::AllServerClusterInstances()
198 : {
199 300 : return { mRegistrations };
200 : }
201 :
202 : } // namespace app
203 : } // namespace chip
|