| /* |
| * 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 <controller/NetworkIdentityManagementRegistrar.h> |
| |
| #include <app/ConcreteCommandPath.h> |
| #include <app/MessageDef/StatusIB.h> |
| #include <app/data-model/NullObject.h> |
| #include <clusters/NetworkIdentityManagement/Commands.h> |
| #include <clusters/NetworkIdentityManagement/Enums.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/Optional.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| |
| #include <cstring> |
| #include <utility> |
| |
| namespace chip { |
| namespace Controller { |
| |
| using namespace app::Clusters::NetworkIdentityManagement; |
| |
| namespace { |
| |
| // AddClient and RemoveClient are timed invokes. |
| constexpr uint16_t kTimedInvokeTimeoutMs = 10000; |
| |
| using OnIdleCallback = Callback::Callback<OnNetworkIdentityRegistrarIdleFunct>; |
| |
| } // namespace |
| |
| void NetworkIdentityManagementRegistrar::StopAcceptingRequests() |
| { |
| mStopped = true; |
| } |
| |
| void NetworkIdentityManagementRegistrar::Shutdown() |
| { |
| StopAcceptingRequests(); // refuse further calls first, otherwise a callback could attempt to start a new operation |
| |
| mQueryIdentity.AbortIfPending(); |
| mAddClient.AbortIfPending(); |
| mRemoveClient.AbortIfPending(); |
| OperationFinished(); // whether or not we finished any operations, see OperationFinished() sentinel logic. |
| } |
| |
| bool NetworkIdentityManagementRegistrar::IsIdle() const |
| { |
| return !mQueryIdentity.IsPending() && !mAddClient.IsPending() && !mRemoveClient.IsPending(); |
| } |
| |
| void NetworkIdentityManagementRegistrar::WaitForIdle(OnIdleCallback::Owned onIdle) |
| { |
| VerifyOrReturn(!IsIdle(), onIdle.Invoke()); |
| mIdleWaiters.Enqueue(onIdle.Take()); |
| } |
| |
| void NetworkIdentityManagementRegistrar::OperationFinished() |
| { |
| VerifyOrReturn(IsIdle() && !mIdleWaiters.IsEmpty()); |
| |
| // An idle callback can cause us to no longer be idle (by starting an operation), and can also |
| // deallocate us (which calls Shutdown() and recurses into OperationFinished()), or trigger |
| // a recursive OperationFinished() call in other ways. By always letting the innermost frame |
| // do the work of calling any remaining callbacks, we can avoid touching `this` after it may |
| // have been destroyed. The sentinel is enqueued last, so reaching the sentinel guarantees that |
| // all waiters have been called. |
| bool recursed = false; |
| OnIdleCallback sentinel([](void * context) { *static_cast<bool *>(context) = true; }, &recursed); |
| mIdleWaiters.Enqueue(sentinel.Cancel()); |
| |
| while (!mIdleWaiters.IsEmpty()) |
| { |
| auto * waiter = mIdleWaiters.First(); |
| Callback::CallbackDeque::Dequeue(waiter); |
| OnIdleCallback::FromCancelable(waiter)->Invoke(); |
| VerifyOrReturn(!recursed); // destructor recurses, so after this point we know we're still alive |
| VerifyOrReturn(IsIdle()); |
| } |
| } |
| |
| void NetworkIdentityManagementRegistrar::GetNetworkIdentity(Callback::Callback<OnNetworkIdentityAvailableFunct>::Owned onCompletion) |
| { |
| VerifyOrReturn(!mStopped, onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE, ByteSpan())); |
| VerifyOrReturn(!mQueryIdentity.IsPending(), onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE, ByteSpan())); |
| mQueryIdentity.Start(mController, mNodeId, mEndpoint, std::move(onCompletion)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::RegisterClient(ByteSpan clientIdentity, |
| Callback::Callback<OnClientRegisteredFunct>::Owned onCompletion) |
| { |
| VerifyOrReturn(!mStopped, onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE, /* determinate = */ true)); |
| VerifyOrReturn(!mAddClient.IsPending(), onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE, /* determinate = */ true)); |
| mAddClient.Start(mController, mNodeId, mEndpoint, clientIdentity, std::move(onCompletion)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::UnregisterClient(Credentials::CertificateKeyId clientIdentifier, |
| Callback::Callback<OnClientUnregisteredFunct>::Owned onCompletion) |
| { |
| VerifyOrReturn(!mStopped, onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE)); |
| VerifyOrReturn(!mRemoveClient.IsPending(), onCompletion.Invoke(CHIP_ERROR_INCORRECT_STATE)); |
| mRemoveClient.Start(mController, mNodeId, mEndpoint, clientIdentifier, std::move(onCompletion)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::Operation::AbortIfPending() |
| { |
| if (IsPending()) |
| { |
| Fail(CHIP_ERROR_CANCELLED); // don't DeferOperationFinished() |
| } |
| } |
| |
| void NetworkIdentityManagementRegistrar::Operation::OnConnectionFailure(CHIP_ERROR error) |
| { |
| ChipLogFailure(error, Controller, "Failed to establish a session with the Network Infrastructure Manager"); |
| auto notify = mRegistrar.DeferOperationFinished(); |
| Fail(error); |
| } |
| |
| void NetworkIdentityManagementRegistrar::Operation::OnFinished(bool cancelled) |
| { |
| // Tear down an invocation we are no longer interested in. Reaching this from within one of the |
| // handlers installed by InvokeCommand() is not a concern: they clear mCancelInvoke first. |
| if (mCancelInvoke) |
| { |
| mCancelInvoke(); |
| mCancelInvoke = nullptr; |
| } |
| |
| ControllerOperationBase::OnFinished(cancelled); |
| mCommandSent = false; // whatever we sent is done with; the operation is free to be started again |
| |
| if (cancelled) |
| { |
| mRegistrar.OperationFinished(); // no completion coming, we're finished now |
| } |
| } |
| |
| void NetworkIdentityManagementRegistrar::QueryIdentityOperation::OnConnected(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & session) |
| { |
| // Ask for the network's current identity of the only type PDC defines, rather than for a |
| // specific entry in the NIM's table: which one is current is up to the NIM. |
| Commands::QueryIdentity::Type request; |
| request.networkIdentityType.Emplace(IdentityTypeEnum::kEcdsa); |
| |
| InvokeCommand(exchangeMgr, session, request, |
| [this](const app::ConcreteCommandPath &, const app::StatusIB &, |
| const Commands::QueryIdentityResponse::DecodableType & response) { |
| // The identity points into the response message, which is exactly as long-lived |
| // as the OnNetworkIdentityAvailable callback needs it to be. |
| Complete(CHIP_NO_ERROR, response.identity); |
| }); |
| } |
| |
| void NetworkIdentityManagementRegistrar::AddClientOperation::Start(DeviceController & controller, NodeId nodeId, |
| EndpointId endpoint, ByteSpan clientIdentity, |
| Completion::Owned onCompletion) |
| { |
| // Refuse before starting, so that the completion is delivered without the operation ever taking |
| // it on. This is also why the identity buffer is only touched once we know we are taking the |
| // call on. Also reject an empty identity outright (otherwise memcpy would need a null guard). |
| VerifyOrReturn(!clientIdentity.empty() && clientIdentity.size() <= sizeof(mClientIdentity), |
| onCompletion.Invoke(CHIP_ERROR_INVALID_ARGUMENT, /* determinate = */ true)); |
| memcpy(mClientIdentity, clientIdentity.data(), clientIdentity.size()); |
| mClientIdentityLength = static_cast<decltype(mClientIdentityLength)>(clientIdentity.size()); // range asserted at declaration |
| |
| Base::Start(controller, nodeId, endpoint, std::move(onCompletion)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::AddClientOperation::OnConnected(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & session) |
| { |
| Commands::AddClient::Type request; |
| request.clientIdentity = ByteSpan(mClientIdentity, mClientIdentityLength); |
| |
| InvokeCommand( |
| exchangeMgr, session, request, |
| [this](const app::ConcreteCommandPath &, const app::StatusIB &, |
| const Commands::AddClientResponse::DecodableType & response) { |
| ChipLogProgress(Controller, "Network Client Identity registered at client index %u", response.clientIndex); |
| Complete(CHIP_NO_ERROR, /* determinate = */ true); |
| }, |
| MakeOptional(kTimedInvokeTimeoutMs)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::RemoveClientOperation::Start(DeviceController & controller, NodeId nodeId, |
| EndpointId endpoint, |
| Credentials::CertificateKeyId clientIdentifier, |
| Completion::Owned onCompletion) |
| { |
| memcpy(mClientIdentifier.data(), clientIdentifier.data(), mClientIdentifier.size()); |
| Base::Start(controller, nodeId, endpoint, std::move(onCompletion)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::RemoveClientOperation::OnConnected(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & session) |
| { |
| Commands::RemoveClient::Type request; |
| request.clientIdentifier.Emplace(ByteSpan(mClientIdentifier)); |
| |
| InvokeCommand( |
| exchangeMgr, session, request, |
| [this](const app::ConcreteCommandPath &, const app::StatusIB &, const app::DataModel::NullObjectType &) { |
| ChipLogProgress(Controller, "Network Client Identity revoked"); |
| Complete(CHIP_NO_ERROR); |
| }, |
| MakeOptional(kTimedInvokeTimeoutMs)); |
| } |
| |
| void NetworkIdentityManagementRegistrar::RemoveClientOperation::Fail(CHIP_ERROR error) |
| { |
| if (error == CHIP_IM_GLOBAL_STATUS(NotFound)) |
| { |
| // Revocation is required to be idempotent, so this is a success as far as we care. |
| ChipLogDetail(Controller, "Network Client Identity was already revoked"); |
| Complete(CHIP_NO_ERROR); |
| return; |
| } |
| Complete(error); |
| } |
| |
| } // namespace Controller |
| } // namespace chip |