| /* |
| * |
| * 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 <app/server/Server.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; |
| |
| constexpr chip::EndpointId kJointFabricAdminEndpointId = 1; |
| |
| 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( |
| kJointFabricAdminEndpointId, fabricIndex); |
| |
| jfFabricIndex = fabricIndex; |
| |
| // Set AnchorRootCA |
| uint8_t mAnchorRootCABuffer[Credentials::kMaxDERCertLength]; |
| MutableByteSpan rootCertSpan(mAnchorRootCABuffer); |
| if (mServer->GetFabricTable().FetchRootCert(fabricIndex, rootCertSpan) == CHIP_NO_ERROR) |
| { |
| if (Server::GetInstance().GetJointFabricDatastore().SetAnchorRootCA(rootCertSpan) == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(JointFabric, "Set AnchorRootCA (%u bytes)", static_cast<unsigned>(rootCertSpan.size())); |
| } |
| else |
| { |
| ChipLogError(JointFabric, "Failed to set AnchorRootCA"); |
| } |
| } |
| |
| // obtain nodeid and use it to set anchornodeid |
| NodeId nodeId = fb.GetNodeId(); |
| if (nodeId != kUndefinedNodeId) |
| { |
| if (Server::GetInstance().GetJointFabricDatastore().SetAnchorNodeId(nodeId) == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(JointFabric, "Set AnchorNodeId to 0x" ChipLogFormatX64, ChipLogValueX64(nodeId)); |
| } |
| else |
| { |
| ChipLogError(JointFabric, "Failed to set AnchorNodeId to 0x" ChipLogFormatX64, ChipLogValueX64(nodeId)); |
| } |
| } |
| |
| VendorId vendorId = fb.GetVendorId(); |
| if (vendorId != VendorId::NotSpecified) |
| { |
| if (Server::GetInstance().GetJointFabricDatastore().SetAnchorVendorId(vendorId) == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(JointFabric, "Set AnchorVendorId to %d", vendorId); |
| } |
| else |
| { |
| ChipLogError(JointFabric, "Failed to set AnchorVendorId to %d", vendorId); |
| } |
| } |
| |
| if (vendorId != VendorId::NotSpecified && nodeId != kUndefinedNodeId) |
| { |
| char friendlyNameBuffer[32]; |
| int written = snprintf(friendlyNameBuffer, sizeof(friendlyNameBuffer), "jfa-0x%04X-0x" ChipLogFormatX64, |
| to_underlying(vendorId), ChipLogValueX64(nodeId)); |
| CharSpan friendlyName = CharSpan(friendlyNameBuffer, static_cast<size_t>(written)); |
| |
| if (Server::GetInstance().GetJointFabricDatastore().SetFriendlyName(friendlyName) == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(JointFabric, "Set FriendlyName to %.*s", static_cast<int>(friendlyName.size()), |
| friendlyName.data()); |
| } |
| else |
| { |
| ChipLogError(JointFabric, "Failed to set FriendlyName to %.*s", static_cast<int>(friendlyName.size()), |
| friendlyName.data()); |
| } |
| } |
| |
| Server::GetInstance().GetJointFabricDatastore().SetStatus( |
| Clusters::JointFabricDatastore::DatastoreStateEnum::kPending, |
| static_cast<uint32_t>(System::SystemClock().GetMonotonicTimestamp().count()), 0); |
| |
| Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type defaultGroupKeySetEntry{ 0 }; |
| LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().AddGroupKeySetEntry(defaultGroupKeySetEntry)); |
| LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddNodeKeySetEntry(0, nodeId)); |
| |
| // Add default group entry for the JFA itself |
| // Uses internal method that bypasses CAT restrictions since JFA setup needs both Admin and Anchor CATs |
| Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType addGroupCommandData; |
| addGroupCommandData.groupID = 0; |
| addGroupCommandData.friendlyName = CharSpan::fromCharString("Default JFA Group"); |
| addGroupCommandData.groupKeySetID = 0; |
| addGroupCommandData.groupCAT = kAdminCATIdentifier; // Use Admin CAT for default group |
| addGroupCommandData.groupCATVersion.SetNonNull(1); |
| addGroupCommandData.groupPermission = |
| Clusters::JointFabricDatastore::DatastoreAccessControlEntryPrivilegeEnum::kAdminister; |
| LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddGroup(addGroupCommandData)); |
| addGroupCommandData.groupID = 1; |
| addGroupCommandData.groupCAT = kAnchorCATIdentifier; // Use Anchor CAT for second default group |
| LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddGroup(addGroupCommandData)); |
| |
| Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type newAdmin; |
| newAdmin.nodeID = nodeId; |
| newAdmin.friendlyName = CharSpan::fromCharString("Default JFA Admin"); |
| newAdmin.vendorID = vendorId; |
| newAdmin.icac = ByteSpan(mICACBuffer, mICACBufferLen); |
| |
| LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().AddAdmin(newAdmin)); |
| |
| ChipLogProgress(JointFabric, "Joint Fabric Administrator commissioned on fabric index %d", 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 |
| mNodeId = scopedNodeId.GetNodeId(); |
| 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: { |
| TEMPORARY_RETURN_IGNORED jfaManager->SendCommissioningComplete(); |
| break; |
| } |
| case kJCMCommissioning: { |
| TEMPORARY_RETURN_IGNORED 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."); |
| request.endpointID = kJointFabricAdminEndpointId; |
| |
| 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 */ |
| TEMPORARY_RETURN_IGNORED 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"); |
| TEMPORARY_RETURN_IGNORED 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 * jfaManager = static_cast<JFAManager *>(context); |
| VerifyOrDie(jfaManager != nullptr); |
| |
| ChipLogProgress(JointFabric, "OnCommissioningCompleteResponse, Code=%u", to_underlying(data.errorCode)); |
| |
| if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk) |
| { |
| ChipLogProgress(JointFabric, "Commssioning Failed (nodeId=%ld, isJCM = %d), Code=%u", jfaManager->mNodeId, |
| jfaManager->mOnConnectedAction == kJCMCommissioning, to_underlying(data.errorCode)); |
| } |
| else |
| { |
| switch (jfaManager->mOnConnectedAction) |
| { |
| case kStandardCommissioningComplete: { |
| ChipLogProgress(JointFabric, "Standard Commissioning (nodeId=%ld) success", jfaManager->mNodeId); |
| break; |
| } |
| case kJCMCommissioning: { |
| ChipLogProgress(JointFabric, "Joint Commissioning Method (nodeId=%ld) success", jfaManager->mNodeId); |
| break; |
| } |
| } |
| } |
| |
| jfaManager->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; |
| } |