| /* |
| * |
| * 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 "JFAManager.h" |
| |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/ids/Attributes.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app/ConcreteAttributePath.h> |
| |
| #include <controller/CHIPCluster.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::Controller; |
| using namespace chip::Crypto; |
| using namespace chip::app::Clusters::JointFabricAdministrator; |
| |
| constexpr uint8_t kJFAvailableShift = 0; |
| constexpr uint8_t kJFAdminShift = 1; |
| constexpr uint8_t kJFAnchorShift = 2; |
| constexpr uint8_t kJFDatastoreShift = 3; |
| |
| JFAManager JFAManager::sJFA; |
| |
| CHIP_ERROR JFAManager::Init(Server & server) |
| { |
| mServer = &server; |
| mCASESessionManager = server.GetCASESessionManager(); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR JFAManager::GetJointFabricMode(uint8_t & jointFabricMode) |
| { |
| jointFabricMode = ((IsDeviceCommissioned() ? 0 : 1) << kJFAvailableShift) | |
| (IsDeviceCommissioned() ? (IsDeviceJFAdmin() ? (1 << kJFAdminShift) : 0) : 0) | |
| (IsDeviceCommissioned() ? (IsDeviceJFAnchor() ? (1 << kJFAnchorShift) : 0) : 0) | |
| (IsDeviceCommissioned() ? (1 << kJFDatastoreShift) : 0); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR JFAManager::FinalizeCommissioning(NodeId nodeId, bool isJCM, P256PublicKey & trustedIcacPublicKeyB, uint16_t endpointId) |
| { |
| if (jfFabricIndex == kUndefinedFabricId) |
| { |
| return CHIP_ERROR_INCORRECT_STATE; |
| } |
| |
| ChipLogProgress(JointFabric, "FinalizeCommissioning for NodeID: 0x" ChipLogFormatX64 ", isJCM = %d, peerEndpointId = %d", |
| ChipLogValueX64(nodeId), isJCM, endpointId); |
| |
| peerAdminJFAdminClusterEndpointId = endpointId; |
| peerAdminICACPubKey = trustedIcacPublicKeyB; |
| ScopedNodeId scopedNodeId = ScopedNodeId(nodeId, jfFabricIndex); |
| ConnectToNode(scopedNodeId, isJCM ? kJCMCommissioning : kStandardCommissioningComplete); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void JFAManager::SetJFARpc(JFARpc & aJFARpc) |
| { |
| mJFARpc = &aJFARpc; |
| } |
| |
| JFARpc * JFAManager::GetJFARpc() |
| { |
| return mJFARpc; |
| } |
| |
| void JFAManager::HandleCommissioningCompleteEvent() |
| { |
| for (const auto & fb : mServer->GetFabricTable()) |
| { |
| FabricIndex fabricIndex = fb.GetFabricIndex(); |
| CATValues cats; |
| |
| if ((jfFabricIndex == kUndefinedFabricIndex) && mServer->GetFabricTable().FetchCATs(fabricIndex, cats) == CHIP_NO_ERROR) |
| { |
| /* When JFA is commissioned, it has to be issued a NOC with Anchor CAT and Administrator CAT */ |
| if (cats.ContainsIdentifier(kAdminCATIdentifier) && cats.ContainsIdentifier(kAnchorCATIdentifier)) |
| { |
| (void) app::Clusters::JointFabricAdministrator::Attributes::AdministratorFabricIndex::Set(1, fabricIndex); |
| |
| jfFabricIndex = fabricIndex; |
| } |
| } |
| } |
| } |
| |
| bool JFAManager::IsDeviceJFAdmin() |
| { |
| if (jfFabricIndex == kUndefinedFabricId) |
| { |
| return false; |
| } |
| |
| CATValues cats; |
| |
| if (mServer->GetFabricTable().FetchCATs(jfFabricIndex, cats) == CHIP_NO_ERROR) |
| { |
| if (cats.ContainsIdentifier(kAdminCATIdentifier)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool JFAManager::IsDeviceJFAnchor() |
| { |
| if (jfFabricIndex == kUndefinedFabricId) |
| { |
| return false; |
| } |
| |
| CATValues cats; |
| |
| if (mServer->GetFabricTable().FetchCATs(jfFabricIndex, cats) == CHIP_NO_ERROR) |
| { |
| if (cats.ContainsIdentifier(kAnchorCATIdentifier)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void JFAManager::ReleaseSession() |
| { |
| auto optionalSessionHandle = mSessionHolder.Get(); |
| |
| if (optionalSessionHandle.HasValue()) |
| { |
| if (optionalSessionHandle.Value()->IsActiveSession()) |
| { |
| optionalSessionHandle.Value()->AsSecureSession()->MarkAsDefunct(); |
| } |
| } |
| mSessionHolder.Release(); |
| } |
| |
| void JFAManager::ConnectToNode(ScopedNodeId scopedNodeId, OnConnectedAction onConnectedAction) |
| { |
| VerifyOrDie(mServer != nullptr); |
| |
| if ((scopedNodeId.GetFabricIndex() == kUndefinedFabricIndex) || (scopedNodeId.GetNodeId() == kUndefinedNodeId)) |
| { |
| ChipLogError(JointFabric, "Invalid node location!"); |
| return; |
| } |
| |
| // Set the action to take once connection is successfully established |
| mOnConnectedAction = onConnectedAction; |
| |
| ChipLogDetail(JointFabric, "Establishing session to node ID 0x" ChipLogFormatX64 " on fabric index %d", |
| ChipLogValueX64(scopedNodeId.GetNodeId()), scopedNodeId.GetFabricIndex()); |
| |
| mCASESessionManager->FindOrEstablishSession(scopedNodeId, &mOnConnectedCallback, &mOnConnectionFailureCallback); |
| } |
| |
| void JFAManager::OnConnected(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) |
| { |
| JFAManager * jfaManager = static_cast<JFAManager *>(context); |
| |
| VerifyOrDie(jfaManager != nullptr); |
| jfaManager->mSessionHolder.Grab(sessionHandle); |
| jfaManager->mExchangeMgr = &exchangeMgr; |
| |
| ChipLogProgress(JointFabric, "Established CASE"); |
| |
| switch (jfaManager->mOnConnectedAction) |
| { |
| case kStandardCommissioningComplete: { |
| jfaManager->SendCommissioningComplete(); |
| break; |
| } |
| case kJCMCommissioning: { |
| jfaManager->AnnounceJointFabricAdministrator(); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| void JFAManager::OnConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) |
| { |
| JFAManager * jfaManager = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManager != nullptr); |
| |
| ChipLogError(JointFabric, "Failed to establish connection to 0x" ChipLogFormatX64 " on fabric index %d", |
| ChipLogValueX64(peerId.GetNodeId()), peerId.GetFabricIndex()); |
| |
| jfaManager->ReleaseSession(); |
| } |
| |
| CHIP_ERROR JFAManager::AnnounceJointFabricAdministrator() |
| { |
| Commands::AnnounceJointFabricAdministrator::Type request; |
| |
| if (!mExchangeMgr) |
| { |
| return CHIP_ERROR_UNINITIALIZED; |
| } |
| |
| ChipLogProgress(JointFabric, "AnnounceJointFabricAdministrator: invoke cluster command."); |
| Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), peerAdminJFAdminClusterEndpointId); |
| return cluster.InvokeCommand(request, this, OnAnnounceJointFabricAdministratorResponse, |
| OnAnnounceJointFabricAdministratorFailure); |
| } |
| |
| void JFAManager::OnAnnounceJointFabricAdministratorResponse(void * context, const chip::app::DataModel::NullObjectType & data) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| |
| ChipLogProgress(JointFabric, "OnAnnounceJointFabricAdministratorResponse"); |
| |
| /* TODO: https://github.com/project-chip/connectedhomeip/issues/38202 */ |
| jfaManagerCore->SendICACSRRequest(); |
| } |
| |
| void JFAManager::OnAnnounceJointFabricAdministratorFailure(void * context, CHIP_ERROR error) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| jfaManagerCore->ReleaseSession(); |
| |
| ChipLogError(JointFabric, "OnAnnounceJointFabricAdministratorFailure: %s\n", chip::ErrorStr(error)); |
| } |
| |
| CHIP_ERROR JFAManager::SendICACSRRequest() |
| { |
| Commands::ICACCSRRequest::Type request; |
| |
| if (!mExchangeMgr) |
| { |
| return CHIP_ERROR_UNINITIALIZED; |
| } |
| |
| ChipLogProgress(JointFabric, "SendICACSRRequest: invoke cluster command."); |
| |
| Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), peerAdminJFAdminClusterEndpointId); |
| return cluster.InvokeCommand(request, this, OnSendICACSRRequestResponse, OnSendICACSRRequestFailure); |
| } |
| |
| void JFAManager::OnSendICACSRRequestResponse(void * context, const Commands::ICACCSRResponse::DecodableType & icaccsr) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| P256PublicKey pubKey; |
| |
| ChipLogProgress(JointFabric, "OnSendICACSRRequestResponse"); |
| |
| if ((CHIP_NO_ERROR == VerifyCertificateSigningRequest(icaccsr.icaccsr.data(), icaccsr.icaccsr.size(), pubKey)) && |
| jfaManagerCore->peerAdminICACPubKey.Matches(pubKey)) |
| { |
| ChipLogProgress(JointFabric, "OnSendICACSRRequestResponse: validated ICAC CSR"); |
| jfaManagerCore->SendCommissioningComplete(); |
| } |
| } |
| |
| void JFAManager::OnSendICACSRRequestFailure(void * context, CHIP_ERROR error) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| jfaManagerCore->ReleaseSession(); |
| |
| ChipLogError(JointFabric, "OnSendICACSRRequestFailure: %s\n", chip::ErrorStr(error)); |
| } |
| |
| CHIP_ERROR JFAManager::SendCommissioningComplete() |
| { |
| GeneralCommissioning::Commands::CommissioningComplete::Type request; |
| |
| if (!mExchangeMgr) |
| { |
| return CHIP_ERROR_UNINITIALIZED; |
| } |
| |
| ChipLogProgress(JointFabric, "SendCommissioningComplete: invoke cluster command."); |
| Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), kRootEndpointId); |
| return cluster.InvokeCommand(request, this, OnCommissioningCompleteResponse, OnCommissioningCompleteFailure); |
| } |
| |
| void JFAManager::OnCommissioningCompleteResponse( |
| void * context, const GeneralCommissioning::Commands::CommissioningCompleteResponse::DecodableType & data) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| jfaManagerCore->ReleaseSession(); |
| |
| ChipLogProgress(JointFabric, "OnCommissioningCompleteResponse, Code=%u", to_underlying(data.errorCode)); |
| |
| if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk) |
| { |
| // TODO |
| } |
| else |
| { |
| // TODO |
| } |
| |
| jfaManagerCore->ReleaseSession(); |
| } |
| |
| void JFAManager::OnCommissioningCompleteFailure(void * context, CHIP_ERROR error) |
| { |
| JFAManager * jfaManagerCore = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManagerCore != nullptr); |
| jfaManagerCore->ReleaseSession(); |
| |
| ChipLogError(JointFabric, "Received failure response %s\n", chip::ErrorStr(error)); |
| } |
| |
| CHIP_ERROR JFAManager::GetIcacCsr(MutableByteSpan & icacCsr) |
| { |
| JFARpc * jfaRpc = GetJFARpc(); |
| |
| if (jfaRpc) |
| { |
| return jfaRpc->GetICACCSRForJF(icacCsr); |
| } |
| |
| return CHIP_ERROR_UNINITIALIZED; |
| } |