blob: 1082477baeda1b02865a010648be907291bc0ce4 [file]
/*
* 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