|  | /* | 
|  | * | 
|  | *    Copyright (c) 2021-2022 Project CHIP Authors | 
|  | * | 
|  | *    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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @brief Defines a table of fabrics that have provisioned the device. | 
|  | */ | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include <app/util/basic-types.h> | 
|  | #include <credentials/CHIPCert.h> | 
|  | #include <credentials/CHIPCertificateSet.h> | 
|  | #include <credentials/CertificateValidityPolicy.h> | 
|  | #include <credentials/LastKnownGoodTime.h> | 
|  | #include <credentials/OperationalCertificateStore.h> | 
|  | #include <crypto/CHIPCryptoPAL.h> | 
|  | #include <crypto/OperationalKeystore.h> | 
|  | #include <lib/core/CHIPEncoding.h> | 
|  | #include <lib/core/CHIPPersistentStorageDelegate.h> | 
|  | #include <lib/core/CHIPSafeCasts.h> | 
|  | #include <lib/core/Optional.h> | 
|  | #include <lib/core/ScopedNodeId.h> | 
|  | #include <lib/core/TLV.h> | 
|  | #include <lib/support/BitFlags.h> | 
|  | #include <lib/support/CHIPMem.h> | 
|  | #include <lib/support/DLLUtil.h> | 
|  | #include <lib/support/ScopedBuffer.h> | 
|  | #include <lib/support/Span.h> | 
|  |  | 
|  | namespace chip { | 
|  |  | 
|  | static constexpr uint8_t kFabricLabelMaxLengthInBytes = 32; | 
|  |  | 
|  | static_assert(kUndefinedFabricIndex < chip::kMinValidFabricIndex, "Undefined fabric index should not be valid"); | 
|  |  | 
|  | /** | 
|  | * Provides access to the core metadata for a given fabric to which a node is joined. | 
|  | * | 
|  | * This metadata includes: | 
|  | * | 
|  | * - FabricIndex within the local set of fabrics | 
|  | * - Operational Identity | 
|  | *   - NodeId | 
|  | *   - Fabric Id | 
|  | *   - Public key of operational root CA (to avoid keeping/reloading RCAC (Root CA Certificate) too often) | 
|  | *   - Pre-computed "Compressed Fabric ID" used for discovery | 
|  | * - Operational public key (if externally injected as opposed to present in an OperationalKeystore) | 
|  | * - Fabric Label | 
|  | * - VendorID allocated at fabric joining by commissioner | 
|  | * | 
|  | * NOTE: All the setters of this class are private and only accessible by FabricTable, the | 
|  | *       friend class that owns these. The reason is that there are data dependencies between | 
|  | *       fabrics that require FabricTable to be the single entrypoint for all mutations, rather | 
|  | *       than directly on a FabricInfo instance. | 
|  | */ | 
|  | class DLL_EXPORT FabricInfo | 
|  | { | 
|  | public: | 
|  | FabricInfo() { Reset(); } | 
|  | ~FabricInfo() { Reset(); } | 
|  |  | 
|  | // Non-copyable | 
|  | FabricInfo(FabricInfo const &)     = delete; | 
|  | void operator=(FabricInfo const &) = delete; | 
|  |  | 
|  | // Returns a span into our internal storage. | 
|  | CharSpan GetFabricLabel() const { return CharSpan(mFabricLabel, strnlen(mFabricLabel, kFabricLabelMaxLengthInBytes)); } | 
|  | CHIP_ERROR SetFabricLabel(const CharSpan & fabricLabel); | 
|  |  | 
|  | NodeId GetNodeId() const { return mNodeId; } | 
|  | ScopedNodeId GetScopedNodeId() const { return ScopedNodeId(mNodeId, mFabricIndex); } | 
|  | ScopedNodeId GetScopedNodeIdForNode(const NodeId node) const { return ScopedNodeId(node, mFabricIndex); } | 
|  |  | 
|  | // TODO(#15049): Refactor/rename PeerId to OperationalId or OpId throughout source | 
|  | PeerId GetPeerId() const { return PeerId(mCompressedFabricId, mNodeId); } | 
|  | PeerId GetPeerIdForNode(const NodeId node) const { return PeerId(mCompressedFabricId, node); } | 
|  |  | 
|  | FabricId GetFabricId() const { return mFabricId; } | 
|  | FabricIndex GetFabricIndex() const { return mFabricIndex; } | 
|  |  | 
|  | CompressedFabricId GetCompressedFabricId() const { return mCompressedFabricId; } | 
|  | CHIP_ERROR GetCompressedFabricIdBytes(MutableByteSpan & compressedFabricId) const | 
|  | { | 
|  | VerifyOrReturnError(compressedFabricId.size() == sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | Encoding::BigEndian::Put64(compressedFabricId.data(), GetCompressedFabricId()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR FetchRootPubkey(Crypto::P256PublicKey & outPublicKey) const; | 
|  |  | 
|  | void SetVendorId(VendorId vendorId) { mVendorId = vendorId; } | 
|  | VendorId GetVendorId() const { return mVendorId; } | 
|  |  | 
|  | bool IsInitialized() const { return (mFabricIndex != kUndefinedFabricIndex) && IsOperationalNodeId(mNodeId); } | 
|  |  | 
|  | bool HasOperationalKey() const { return mOperationalKey != nullptr; } | 
|  |  | 
|  | bool ShouldAdvertiseIdentity() const { return mShouldAdvertiseIdentity; } | 
|  |  | 
|  | friend class FabricTable; | 
|  |  | 
|  | private: | 
|  | struct InitParams | 
|  | { | 
|  | CompressedFabricId compressedFabricId    = kUndefinedCompressedFabricId; | 
|  | NodeId nodeId                            = kUndefinedNodeId; | 
|  | FabricIndex fabricIndex                  = kUndefinedFabricIndex; | 
|  | Crypto::P256Keypair * operationalKeypair = nullptr; | 
|  | FabricId fabricId                        = kUndefinedFabricId; | 
|  | Crypto::P256PublicKey rootPublicKey; | 
|  | VendorId vendorId              = VendorId::NotSpecified; /**< Vendor ID for commissioner of fabric */ | 
|  | bool hasExternallyOwnedKeypair = false; | 
|  | bool advertiseIdentity         = false; | 
|  |  | 
|  | CHIP_ERROR AreValid() const | 
|  | { | 
|  | VerifyOrReturnError((fabricId != kUndefinedFabricId) && (fabricIndex != kUndefinedFabricIndex), | 
|  | CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(IsOperationalNodeId(nodeId), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | // We don't check the root public key validity or the compressed fabric ID, since in the | 
|  | // very small usage that exists in private use, the rest should be OK. | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Move assignment operator to support setting from pending on fabric table commit | 
|  | void operator=(FabricInfo && other); | 
|  |  | 
|  | /** | 
|  | * @brief Initialize a FabricInfo object's metadata given init parameters. | 
|  | * | 
|  | * Note that certificates are never owned by this object and are assumed pre-validated | 
|  | * | 
|  | * @param initParams Init parameters to use to initialize the given fabric. | 
|  | * @return CHIP_NO_ERROR on success or another internal CHIP_ERROR_* value on failure | 
|  | */ | 
|  | CHIP_ERROR Init(const InitParams & initParams); | 
|  |  | 
|  | /** | 
|  | * Sets the P256Keypair used for this fabric.  This will make a copy of the keypair | 
|  | * via the P256Keypair::Serialize and P256Keypair::Deserialize methods. | 
|  | * | 
|  | * The keyPair argument is safe to deallocate once this method returns. | 
|  | * | 
|  | * If your P256Keypair does not support serialization, use the | 
|  | * `SetExternallyOwnedOperationalKeypair` method instead. | 
|  | */ | 
|  | CHIP_ERROR SetOperationalKeypair(const Crypto::P256Keypair * keyPair); | 
|  |  | 
|  | /** | 
|  | * Sets the P256Keypair used for this fabric, delegating ownership of the | 
|  | * key to the caller. The P256Keypair provided here must be freed later by | 
|  | * the caller of this method if it was allocated dynamically. | 
|  | * | 
|  | * This should be used if your P256Keypair does not support serialization | 
|  | * and deserialization (e.g. your private key is held in a secure element | 
|  | * and cannot be accessed directly), or if you back your operational | 
|  | * private keys by external implementation of the cryptographic interfaces. | 
|  | * | 
|  | * To have the ownership of the key managed for you, use | 
|  | * SetOperationalKeypair instead. | 
|  | */ | 
|  | CHIP_ERROR SetExternallyOwnedOperationalKeypair(Crypto::P256Keypair * keyPair); | 
|  |  | 
|  | /** | 
|  | * @brief Sign a message with the fabric's operational private key. This ONLY | 
|  | *        works if `SetOperationalKeypair` or `SetExternallyOwnedOperationalKeypair` | 
|  | *        had been called and is an API that is present ONLY to be called by FabricTable. | 
|  | * | 
|  | * @param message - message to sign | 
|  | * @param outSignature - buffer to hold the signature | 
|  | * @return CHIP_NO_ERROR on success or another CHIP_ERROR on crypto internal errors | 
|  | */ | 
|  | CHIP_ERROR SignWithOpKeypair(ByteSpan message, Crypto::P256ECDSASignature & outSignature) const; | 
|  |  | 
|  | /** | 
|  | *  Reset the state to a completely uninitialized status. | 
|  | */ | 
|  | void Reset() | 
|  | { | 
|  | mNodeId             = kUndefinedNodeId; | 
|  | mFabricId           = kUndefinedFabricId; | 
|  | mFabricIndex        = kUndefinedFabricIndex; | 
|  | mCompressedFabricId = kUndefinedCompressedFabricId; | 
|  |  | 
|  | mVendorId       = VendorId::NotSpecified; | 
|  | mFabricLabel[0] = '\0'; | 
|  |  | 
|  | if (!mHasExternallyOwnedOperationalKey && mOperationalKey != nullptr) | 
|  | { | 
|  | chip::Platform::Delete(mOperationalKey); | 
|  | } | 
|  | mOperationalKey                   = nullptr; | 
|  | mHasExternallyOwnedOperationalKey = false; | 
|  | mShouldAdvertiseIdentity          = true; | 
|  |  | 
|  | mFabricIndex = kUndefinedFabricIndex; | 
|  | mNodeId      = kUndefinedNodeId; | 
|  | } | 
|  |  | 
|  | void SetShouldAdvertiseIdentity(bool advertiseIdentity) { mShouldAdvertiseIdentity = advertiseIdentity; } | 
|  |  | 
|  | static constexpr size_t MetadataTLVMaxSize() | 
|  | { | 
|  | return TLV::EstimateStructOverhead(sizeof(uint16_t), kFabricLabelMaxLengthInBytes); | 
|  | } | 
|  |  | 
|  | static constexpr size_t OpKeyTLVMaxSize() | 
|  | { | 
|  | return TLV::EstimateStructOverhead(sizeof(uint16_t), Crypto::P256SerializedKeypair::Capacity()); | 
|  | } | 
|  |  | 
|  | NodeId mNodeId     = kUndefinedNodeId; | 
|  | FabricId mFabricId = kUndefinedFabricId; | 
|  | // We cache the compressed fabric id since it's used so often and costly to get. | 
|  | CompressedFabricId mCompressedFabricId = kUndefinedCompressedFabricId; | 
|  | // We cache the root public key since it's used so often and costly to get. | 
|  | Crypto::P256PublicKey mRootPublicKey; | 
|  |  | 
|  | // mFabricLabel is 33 bytes, so ends on a 1 mod 4 byte boundary. | 
|  | char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' }; | 
|  |  | 
|  | // mFabricIndex, mVendorId, mHasExternallyOwnedOperationalKey, | 
|  | // mShouldAdvertiseIdentity are 5 bytes and do not include any padding if | 
|  | // they come after the 33-byte mFabricLabel, so end on a 2 mod 4 byte | 
|  | // boundary. | 
|  | FabricIndex mFabricIndex               = kUndefinedFabricIndex; | 
|  | VendorId mVendorId                     = VendorId::NotSpecified; | 
|  | bool mHasExternallyOwnedOperationalKey = false; | 
|  | bool mShouldAdvertiseIdentity          = true; | 
|  |  | 
|  | // 2 bytes of padding here, since mOperationalKey needs to be void*-aligned, | 
|  | // so has to be at a 0 mod 4 byte location. | 
|  |  | 
|  | mutable Crypto::P256Keypair * mOperationalKey = nullptr; | 
|  |  | 
|  | CHIP_ERROR CommitToStorage(PersistentStorageDelegate * storage) const; | 
|  | CHIP_ERROR LoadFromStorage(PersistentStorageDelegate * storage, FabricIndex newFabricIndex, const ByteSpan & rcac, | 
|  | const ByteSpan & noc); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Iterates over valid fabrics within a list | 
|  | */ | 
|  |  | 
|  | class ConstFabricIterator | 
|  | { | 
|  | public: | 
|  | using value_type = FabricInfo; | 
|  | using pointer    = FabricInfo *; | 
|  | using reference  = FabricInfo &; | 
|  |  | 
|  | ConstFabricIterator(const FabricInfo * start, const FabricInfo * pending, size_t index, size_t maxSize) : | 
|  | mStart(start), mPending(pending), mIndex(index), mMaxSize(maxSize) | 
|  | { | 
|  | if (mIndex >= maxSize) | 
|  | { | 
|  | mIndex = maxSize; | 
|  | } | 
|  | else if (!mStart[mIndex].IsInitialized()) | 
|  | { | 
|  | Advance(); | 
|  | } | 
|  | } | 
|  | ConstFabricIterator(const ConstFabricIterator &)             = default; | 
|  | ConstFabricIterator & operator=(const ConstFabricIterator &) = default; | 
|  |  | 
|  | ConstFabricIterator & operator++() { return Advance(); } | 
|  | ConstFabricIterator operator++(int) | 
|  | { | 
|  | ConstFabricIterator other(*this); | 
|  | Advance(); | 
|  | return other; | 
|  | } | 
|  |  | 
|  | const FabricInfo & operator*() const | 
|  | { | 
|  | VerifyOrDie(!IsAtEnd()); | 
|  |  | 
|  | return *GetCurrent(); | 
|  | } | 
|  | const FabricInfo * operator->() const | 
|  | { | 
|  | VerifyOrDie(!IsAtEnd()); | 
|  |  | 
|  | return GetCurrent(); | 
|  | } | 
|  |  | 
|  | bool operator==(const ConstFabricIterator & other) const | 
|  | { | 
|  | if (IsAtEnd()) | 
|  | { | 
|  | return other.IsAtEnd(); | 
|  | } | 
|  |  | 
|  | // Pending entry does not participate in finding this. | 
|  | return (mStart == other.mStart) && (mIndex == other.mIndex) && (mMaxSize == other.mMaxSize); | 
|  | } | 
|  | bool operator!=(const ConstFabricIterator & other) const { return !(*this == other); } | 
|  |  | 
|  | bool IsAtEnd() const { return (mIndex == mMaxSize); } | 
|  |  | 
|  | private: | 
|  | const FabricInfo * mStart; | 
|  | const FabricInfo * mPending; ///< Pointer to the shadow pending entry, nullptr if none | 
|  | size_t mIndex; | 
|  | size_t mMaxSize; | 
|  |  | 
|  | // Helper to get either a given entry of the fabric table, or its pending shadow if | 
|  | // a fabric update is currently pending. | 
|  | const FabricInfo * GetCurrent() const | 
|  | { | 
|  | const auto * current = mStart + mIndex; | 
|  |  | 
|  | // If we reached the pending entry, return that instead of the underlying entry from the mStates. | 
|  | if ((mPending != nullptr) && mPending->IsInitialized() && (current->GetFabricIndex() == mPending->GetFabricIndex())) | 
|  | { | 
|  | current = mPending; | 
|  | } | 
|  |  | 
|  | return current; | 
|  | } | 
|  |  | 
|  | ConstFabricIterator & Advance() | 
|  | { | 
|  | do | 
|  | { | 
|  | if (mIndex < mMaxSize) | 
|  | { | 
|  | mIndex++; | 
|  | } | 
|  | } while (!IsAtEnd() && !mStart[mIndex].IsInitialized()); | 
|  |  | 
|  | return *this; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class DLL_EXPORT FabricTable | 
|  | { | 
|  | public: | 
|  | struct DLL_EXPORT InitParams | 
|  | { | 
|  | // PersistentStorageDelegate for Fabric Info metadata storage and Fabric Table index (MANDATORY). | 
|  | PersistentStorageDelegate * storage = nullptr; | 
|  | // Operational Keystore to abstract access to key. Mandatory for commissionable devices  (e.g. | 
|  | // chip::Server-based things) and recommended for controllers. With this set to false, FabricInfo | 
|  | // added as new fabrics need to have directly injected operational keys with FabricInfo::Set*OperationalKey. | 
|  | Crypto::OperationalKeystore * operationalKeystore = nullptr; | 
|  | // Operational Certificate store to hold the NOC/ICAC/RCAC chains (MANDATORY). | 
|  | Credentials::OperationalCertificateStore * opCertStore = nullptr; | 
|  | }; | 
|  |  | 
|  | class DLL_EXPORT Delegate | 
|  | { | 
|  | public: | 
|  | Delegate() {} | 
|  | virtual ~Delegate() {} | 
|  |  | 
|  | /** | 
|  | * Gets called when a fabric is about to be deleted, such as on | 
|  | * FabricTable::Delete().  This allows actions to be taken that need the | 
|  | * fabric to still be around before we delete it. | 
|  | **/ | 
|  | virtual void FabricWillBeRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) {} | 
|  |  | 
|  | /** | 
|  | * Gets called when a fabric is deleted, such as on FabricTable::Delete(). | 
|  | **/ | 
|  | virtual void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) {} | 
|  |  | 
|  | /** | 
|  | * Gets called when a fabric in Fabric Table is persisted to storage, by CommitPendingFabricData. | 
|  | **/ | 
|  | virtual void OnFabricCommitted(const FabricTable & fabricTable, FabricIndex fabricIndex){}; | 
|  |  | 
|  | /** | 
|  | * Gets called when operational credentials are changed, which may not be persistent. | 
|  | * | 
|  | * Can be used to affect what is needed for UpdateNOC prior to commit. | 
|  | **/ | 
|  | virtual void OnFabricUpdated(const FabricTable & fabricTable, FabricIndex fabricIndex){}; | 
|  |  | 
|  | // Intrusive list pointer for FabricTable to manage the entries. | 
|  | Delegate * next = nullptr; | 
|  | }; | 
|  |  | 
|  | /** Data to convey whenever a SignVIDVerificationRequest is executed. */ | 
|  | struct SignVIDVerificationResponseData | 
|  | { | 
|  | FabricIndex fabricIndex; | 
|  | uint8_t fabricBindingVersion; | 
|  | // The next field is a variable size buffer to account for different signature | 
|  | // algorithms over time, which may have different sizes. | 
|  | Platform::ScopedMemoryBufferWithSize<uint8_t> signature; | 
|  | }; | 
|  |  | 
|  | public: | 
|  | FabricTable()  = default; | 
|  | ~FabricTable() = default; | 
|  |  | 
|  | // Non-copyable | 
|  | FabricTable(FabricTable const &)    = delete; | 
|  | void operator=(FabricTable const &) = delete; | 
|  |  | 
|  | enum class AdvertiseIdentity : uint8_t | 
|  | { | 
|  | Yes, | 
|  | No | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Delete the fabric with given `fabricIndex`. | 
|  | * | 
|  | * @param fabricIndex - Index of fabric for deletion | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_NOT_FOUND if there is no fabric for that index | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds | 
|  | * @retval other CHIP_ERROR on internal errors | 
|  | */ | 
|  | CHIP_ERROR Delete(FabricIndex fabricIndex); | 
|  | void DeleteAllFabrics(); | 
|  |  | 
|  | // TODO this #if CONFIG_BUILD_FOR_HOST_UNIT_TEST is temporary. There is a change incoming soon | 
|  | // that will allow triggering NOC update directly. | 
|  | #if CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
|  | void SendUpdateFabricNotificationForTest(FabricIndex fabricIndex) { NotifyFabricUpdated(fabricIndex); } | 
|  | #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
|  |  | 
|  | /** | 
|  | * Collection of methods to help find a matching FabricInfo instance given a set of query criteria | 
|  | * | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Finds a matching FabricInfo instance given a root public key and fabric ID that uniquely identifies the fabric in any scope. | 
|  | * | 
|  | * Returns nullptr if no matching instance is found. | 
|  | * | 
|  | */ | 
|  | const FabricInfo * FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const; | 
|  |  | 
|  | /** | 
|  | * Finds a matching FabricInfo instance given a locally-scoped fabric index. | 
|  | * | 
|  | * Returns nullptr if no matching instance is found. | 
|  | * | 
|  | */ | 
|  | const FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex) const; | 
|  |  | 
|  | /** | 
|  | * Finds a matching FabricInfo instance given a root public key, fabric ID AND a matching NodeId. This variant of find | 
|  | * is only to be used when it is possible to have colliding fabrics in the table that are on the same logical fabric | 
|  | * but may be associated with different node identities. | 
|  | * | 
|  | * Returns nullptr if no matching instance is found. | 
|  | * | 
|  | */ | 
|  | const FabricInfo * FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const; | 
|  |  | 
|  | /** | 
|  | * Finds a matching FabricInfo instance given a compressed fabric ID. If there are multiple | 
|  | * matching FabricInfo instances given the low but non-zero probability of collision, there is no guarantee | 
|  | * on which instance will be returned. | 
|  | * | 
|  | * Returns nullptr if no matching instance is found. | 
|  | */ | 
|  | const FabricInfo * FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const; | 
|  |  | 
|  | CHIP_ERROR Init(const FabricTable::InitParams & initParams); | 
|  | void Shutdown(); | 
|  |  | 
|  | /** | 
|  | * @brief If `Init()` caused a Delete due to partial commit, the fabric index at play is returned. | 
|  | * | 
|  | * Allows caller to schedule more clean-up. This is because at Init() time, none of the delegates | 
|  | * are registered yet, so no other modules would learn of the removal. | 
|  | * | 
|  | * The value is auto-reset to `kUndefinedFabricIndex` on being returned, so that subsequent | 
|  | * `GetDeletedFabricFromCommitMarker()` after one that has a fabric index to give will provide | 
|  | * `kUndefinedFabricIndex`. | 
|  | * | 
|  | * @return the fabric index of a just-deleted fabric, or kUndefinedFabricIndex if none were deleted. | 
|  | */ | 
|  | FabricIndex GetDeletedFabricFromCommitMarker(); | 
|  |  | 
|  | /** | 
|  | * @brief Clear the commit marker when we are sure we have proceeded with any remaining clean-up | 
|  | */ | 
|  | void ClearCommitMarker(); | 
|  |  | 
|  | // Forget a fabric in memory: doesn't delete any persistent state, just | 
|  | // reverts any pending state (blindly) and then resets the fabric table | 
|  | // entry. | 
|  | // | 
|  | // TODO: We have to determine if we should remove this call. | 
|  | void Forget(FabricIndex fabricIndex); | 
|  |  | 
|  | CHIP_ERROR AddFabricDelegate(FabricTable::Delegate * delegate); | 
|  | void RemoveFabricDelegate(FabricTable::Delegate * delegate); | 
|  |  | 
|  | /** | 
|  | * @brief Set the Fabric Label for the fabric referred by `fabricIndex`. | 
|  | * | 
|  | * If a fabric add/update is pending, only the pending version will be updated, | 
|  | * so that on fail-safe expiry, you would actually see the only fabric label if | 
|  | * Update fails. If the fabric label is set before UpdateNOC, then the change is immediate. | 
|  | * | 
|  | * @param fabricIndex - Fabric Index for which to set the label | 
|  | * @param fabricLabel - Label to set on the fabric | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to an fabric in the table | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT on fabric label error (e.g. too large) | 
|  | * @retval other CHIP_ERROR on internal errors | 
|  | */ | 
|  | CHIP_ERROR SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel); | 
|  |  | 
|  | /** | 
|  | * @brief Get the Fabric Label for a given fabric | 
|  | * | 
|  | * NOTE: The outFabricLabel argument points to internal memory of the fabric info. | 
|  | *       It may become invalid on the next FabricTable API call due to shadow | 
|  | *       storage of data. | 
|  | * | 
|  | * @param fabricIndex - Fabric index for which to get the label | 
|  | * @param outFabricLabel - char span that will be set to the label value | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX on error | 
|  | * @retval other CHIP_ERROR on internal errors | 
|  | */ | 
|  | CHIP_ERROR GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel); | 
|  |  | 
|  | /** | 
|  | * Get the current Last Known Good Time. | 
|  | * | 
|  | * @param lastKnownGoodChipEpochTime (out) the current last known good time, if any is known | 
|  | * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR | 
|  | */ | 
|  | CHIP_ERROR GetLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const | 
|  | { | 
|  | return mLastKnownGoodTime.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Validate that the passed Last Known Good Time is within bounds and then | 
|  | * store this and write back to storage.  Legal values are those which are | 
|  | * not earlier than firmware build time or any of our stored certificates' | 
|  | * NotBefore times: | 
|  | * | 
|  | *    3.5.6.1. Last Known Good UTC Time | 
|  | * | 
|  | *    A Node MAY adjust the Last Known Good UTC Time backwards if it | 
|  | *    believes the current Last Known Good UTC Time is incorrect and it has | 
|  | *    a good time value from a trusted source. The Node SHOULD NOT adjust | 
|  | *    the Last Known Good UTC to a time before the later of: | 
|  | *      • The build timestamp of its currently running software image | 
|  | *      • The not-before timestamp of any of its operational certificates | 
|  | * | 
|  | * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch | 
|  | * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR | 
|  | */ | 
|  | CHIP_ERROR SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime); | 
|  |  | 
|  | /** | 
|  | * @return the number of fabrics currently accessible/usable/iterable. | 
|  | */ | 
|  | uint8_t FabricCount() const { return mFabricCount; } | 
|  |  | 
|  | ConstFabricIterator cbegin() const | 
|  | { | 
|  | const FabricInfo * pending = GetShadowPendingFabricEntry(); | 
|  | return ConstFabricIterator(mStates, pending, 0, CHIP_CONFIG_MAX_FABRICS); | 
|  | } | 
|  | ConstFabricIterator cend() const | 
|  | { | 
|  | return ConstFabricIterator(mStates, nullptr, CHIP_CONFIG_MAX_FABRICS, CHIP_CONFIG_MAX_FABRICS); | 
|  | } | 
|  | ConstFabricIterator begin() const { return cbegin(); } | 
|  | ConstFabricIterator end() const { return cend(); } | 
|  |  | 
|  | /** | 
|  | * @brief Get the RCAC (operational root certificate) associated with a fabric. | 
|  | * | 
|  | * If a root is pending for `fabricIndex` from `AddNewPendingTrustedRootCert`, it is returned. | 
|  | * | 
|  | * @param fabricIndex - Fabric for which to get the RCAC | 
|  | * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size. | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small | 
|  | * @retval CHIP_ERROR_NOT_FOUND if not found/available | 
|  | * @retval other CHIP_ERROR values on invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the pending root certificate which is not associated with a fabric, if there is one. | 
|  | * | 
|  | * If a root is pending from `AddNewPendingTrustedRootCert`, and there is no | 
|  | * fabric associated with the corresponding fabric index yet | 
|  | * (i.e. `AddNewPendingFabric*` has not been called yet) it is returned. | 
|  | * | 
|  | * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size. | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small. | 
|  | * @retval CHIP_ERROR_NOT_FOUND if there is no pending root certificate | 
|  | *                              that's not yet associated with a fabric. | 
|  | * @retval other CHIP_ERROR values on invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the ICAC (operational intermediate certificate) associated with a fabric. | 
|  | * | 
|  | * If a fabric is pending from add/update operation for the given `fabricIndex`, its | 
|  | * ICAC is returned. | 
|  | * | 
|  | * If an NOC exists, but the ICAC is not present in the chain, CHIP_NO_ERROR is | 
|  | * returned and `outCert` is resized to 0 length so that its `empty()` method returns true. | 
|  | * | 
|  | * @param fabricIndex - Fabric for which to get the ICAC | 
|  | * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size. | 
|  | * @retval CHIP_NO_ERROR on success, including if absent within an existing chain | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small | 
|  | * @retval CHIP_ERROR_NOT_FOUND if not found/available | 
|  | * @retval other CHIP_ERROR values on invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the NOC (Node Operational Certificate) associated with a fabric. | 
|  | * | 
|  | * If a fabric is pending from add/update operation for the given `fabricIndex`, its | 
|  | * NOC is returned. | 
|  | * | 
|  | * @param fabricIndex - Fabric for which to get the NOC | 
|  | * @param outCert - MutableByteSpan to receive the certificate. Resized to actual size. | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small | 
|  | * @retval CHIP_ERROR_NOT_FOUND if not found/available | 
|  | * @retval other CHIP_ERROR values on invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const; | 
|  |  | 
|  | /** | 
|  | * @brief Recover the VIDVerificationStatement for a fabric, if any exists. | 
|  | * | 
|  | * This includes returning a pending one if one exists. | 
|  | * | 
|  | * @param fabricIndex - Fabric index for which to find a VIDVerificationStatement | 
|  | * @param outVIDVerificationStatement - Buffer to receive the VIDVerificationstatement. Must be large enough for the maximum | 
|  | * size. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success, **including if missing**. | 
|  | * @retval other CHIP_ERROR value on internal errors | 
|  | */ | 
|  | CHIP_ERROR FetchVIDVerificationStatement(FabricIndex fabricIndex, MutableByteSpan & outVIDVerificationStatement) const; | 
|  |  | 
|  | /** | 
|  | * @brief Recover the VVSC (VID Verification Signing Certificate) for a fabric, if any exists. | 
|  | * | 
|  | * This includes returning a pending one if one exists. | 
|  | * | 
|  | * @param fabricIndex - Fabric index for which to find a VVSC | 
|  | * @param outVIDVerificationStatement - Buffer to receive the VVSC in Matter TLV certificate format. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success, **including if missing**. | 
|  | * @retval other CHIP_ERROR value on internal errors | 
|  | */ | 
|  | CHIP_ERROR FetchVVSC(FabricIndex fabricIndex, MutableByteSpan & outVVSC) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the root public key by value for the given `fabricIndex`. | 
|  | * | 
|  | * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC) | 
|  | * @param outPublicKey - PublicKey instance to receive the public key contents | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCert` is too small | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value | 
|  | * @retval other CHIP_ERROR values on other invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the CASE Authenticated Tags from the NOC for the given `fabricIndex`. | 
|  | * | 
|  | * @param fabricIndex - Fabric for which to get the root public key (subject public key of RCAC) | 
|  | * @param cats - CATValues struct to write the NOC CATs for the given fabric index | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if not found/available, or `fabricIndex` has a bad value | 
|  | * @retval other CHIP_ERROR values on other invalid arguments or internal errors. | 
|  | */ | 
|  | CHIP_ERROR FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const; | 
|  |  | 
|  | /** | 
|  | * @brief Sign a message with a given fabric's operational keypair. This is used for | 
|  | *        CASE and the only way the key should be used. | 
|  | * | 
|  | * This will use a pending key activated with `ActivatePendingOperationalKey` but | 
|  | * not yet persisted, if one is available for the fabric. | 
|  | * | 
|  | * @param fabricIndex - Fabric index whose operational key to use | 
|  | * @param message - Message to sign | 
|  | * @param outSignature - Signature object to receive the signature | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if no active key is found for the given `fabricIndex` or if | 
|  | *                                         `fabricIndex` is invalid. | 
|  | * @retval other CHIP_ERROR value on internal errors | 
|  | */ | 
|  | CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, Crypto::P256ECDSASignature & outSignature) const; | 
|  |  | 
|  | /** | 
|  | * @brief Create an ephemeral keypair for use in session establishment. | 
|  | * | 
|  | * WARNING: The return value MUST be released by `ReleaseEphemeralKeypair`. This is because | 
|  | *          Matter CHIPMem.h does not properly support UniquePtr in a way that would | 
|  | *          safely allow classes derived from Crypto::P256Keypair to be released properly. | 
|  | * | 
|  | * This delegates to the OperationalKeystore if one exists, otherwise it directly allocates a base | 
|  | * Crypto::P256Keypair instance | 
|  | * | 
|  | * @return a pointer to a dynamically P256Keypair (or derived class thereof), which may evaluate to nullptr | 
|  | *         if running out of memory. | 
|  | */ | 
|  | Crypto::P256Keypair * AllocateEphemeralKeypairForCASE(); | 
|  |  | 
|  | /** | 
|  | * @brief Release an ephemeral keypair previously created by `AllocateEphemeralKeypairForCASE()` | 
|  | */ | 
|  | void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair); | 
|  |  | 
|  | /** | 
|  | * This initializes a new keypair for the given fabric and generates a CSR for it, | 
|  | * so that it can be passed in a CSRResponse. | 
|  | * | 
|  | * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either | 
|  | * `ActivatePendingOperationalKey` is called. It is destroyed if | 
|  | * `RevertPendingFabricData` is called before `CommitPendingFabricData`. | 
|  | *  If a pending keypair for the provided fabricIndex (if present) already existed, it is replaced by this call. | 
|  | * | 
|  | *  Only one pending operational keypair is supported at a time. | 
|  | * | 
|  | * @param fabricIndex - Existing FabricIndex for which a new keypair must be made available. If it | 
|  | *                      doesn't have a value, the key will be marked pending for the next available | 
|  | *                      fabric index that would apply for `AddNewFabric`. | 
|  | * @param outputCsr - Buffer to contain the CSR. Must be at least `kMIN_CSR_Buffer_Size` large. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outputCsr` buffer is too small | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is already a pending keypair for another `fabricIndex` value | 
|  | *                                         or if fabricIndex is an invalid value. | 
|  | * @retval other CHIP_ERROR value on internal errors | 
|  | */ | 
|  | CHIP_ERROR AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr); | 
|  |  | 
|  | /** | 
|  | * @brief Returns whether an operational key is pending (true if `AllocatePendingOperationalKey` was | 
|  | *        previously successfully called, false otherwise). | 
|  | * | 
|  | * @param outIsPendingKeyForUpdateNoc this is set to true if the `AllocatePendingOperationalKey` had an | 
|  | *                                    associated fabric index attached, indicating it's for UpdateNoc | 
|  | */ | 
|  | bool HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const; | 
|  |  | 
|  | /** | 
|  | * @brief Returns whether an operational key can be used to sign for given FabricIndex | 
|  | * | 
|  | * @param fabricIndex - Fabric index for which an operational key must be found | 
|  | * @return true if a pending fabric or committed fabric for fabricIndex has an operational key, false otherwise. | 
|  | */ | 
|  | bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const; | 
|  |  | 
|  | /** | 
|  | * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending. | 
|  | * | 
|  | * A newly-added fabric is pending if AddNOC has been previously called successfully but the | 
|  | * fabric is not yet fully committed by CommissioningComplete. | 
|  | * | 
|  | * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending. | 
|  | * | 
|  | * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending. | 
|  | */ | 
|  | FabricIndex GetPendingNewFabricIndex() const; | 
|  |  | 
|  | /** | 
|  | * @brief Returns the operational keystore. This is used for | 
|  | *        CASE and the only way the keystore should be used. | 
|  | * | 
|  | * @return The operational keystore, nullptr otherwise. | 
|  | */ | 
|  | const Crypto::OperationalKeystore * GetOperationalKeystore() { return mOperationalKeystore; } | 
|  |  | 
|  | /** | 
|  | * @brief Add a pending trusted root certificate for the next fabric created with `AddNewPendingFabric*` methods. | 
|  | * | 
|  | * The root only becomes actually pending when the `AddNewPendingFabric*` is called afterwards. It is reverted | 
|  | * if `RevertPendingFabricData` is called. | 
|  | * | 
|  | * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions, | 
|  | * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after | 
|  | * `UpdatePendingFabric` which would mean logical collision of an addition and an update. | 
|  | * | 
|  | * @param rcac - Root certificate in Matter Operational Certificate Encoding (TLV) format | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order | 
|  | * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the root pending | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if the RCAC is too large (further checks are done on `AddNewPendingFabric*`) | 
|  | * @retval other CHIP_ERROR on internal errors. | 
|  | */ | 
|  | CHIP_ERROR AddNewPendingTrustedRootCert(const ByteSpan & rcac); | 
|  |  | 
|  | /** | 
|  | * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric | 
|  | * | 
|  | * Operational key is assumed to be pending or committed in the associated mOperationalKeystore. | 
|  | * | 
|  | * The fabric becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc. | 
|  | * The new fabric becomes permanent/persisted on successful `CommitPendingFabricData`. It disappears | 
|  | * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`. | 
|  | * | 
|  | * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions, | 
|  | * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after | 
|  | * `UpdatePendingFabric*` which would mean logical collision of an addition and an update. | 
|  | * | 
|  | * If a pending key was present in the OperationalKeystore associated with this FabricTable, | 
|  | * it is activated on success. | 
|  | * | 
|  | * | 
|  | * @param noc - NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore. | 
|  | * @param icac - ICAC for the fabric. Can be empty if absent from the chain. | 
|  | * @param vendorId - VendorID to use for the new fabric | 
|  | * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success. | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order. | 
|  | * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending. | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds. | 
|  | * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present. | 
|  | * @retval other CHIP_ERROR_* on internal errors or certificate validation errors. | 
|  | */ | 
|  | CHIP_ERROR AddNewPendingFabricWithOperationalKeystore(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId, | 
|  | FabricIndex * outNewFabricIndex, | 
|  | AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes) | 
|  | { | 
|  | return AddNewPendingFabricCommon(noc, icac, vendorId, nullptr, false, advertiseIdentity, outNewFabricIndex); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Use an NOC and optional ICAC chaining back to the pending RCAC to activate a new fabric | 
|  | * | 
|  | * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or | 
|  | * owned externally if `isExistingOpKeyExternallyOwned` is true). | 
|  | * | 
|  | * WARNING: Copying keypairs is unsafe and not recommended. Consider using | 
|  | *          AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore | 
|  | *          or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe | 
|  | *          class derived from P256Keypair that avoids the true private key persisting in memory. | 
|  | * | 
|  | * For rest of semantics outside of operational key, @see AddNewPendingFabricWithOperationalKeystore | 
|  | * | 
|  | * @param noc - NOC for the fabric. Public key must match the `existingOpKey`'s public key | 
|  | * @param icac - ICAC for the fabric. Can be empty if absent from the chain. | 
|  | * @param vendorId - VendorID to use for the new fabric | 
|  | * @param existingOpKey - Existing operational key to ingest for use in the fabric. Cannot be nullptr. | 
|  | * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is | 
|  | *                                         copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo. | 
|  | * @param outNewFabricIndex - Pointer where the new fabric index for the fabric just added will be set. Cannot be nullptr. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success. | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order. | 
|  | * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to make the fabric pending. | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds. | 
|  | * @retval CHIP_ERROR_FABRIC_EXISTS if operational identity collides with one already present. | 
|  | * @retval other CHIP_ERROR_* on internal errors or certificate validation errors. | 
|  | */ | 
|  | CHIP_ERROR AddNewPendingFabricWithProvidedOpKey(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId, | 
|  | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, | 
|  | FabricIndex * outNewFabricIndex, | 
|  | AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes) | 
|  | { | 
|  | return AddNewPendingFabricCommon(noc, icac, vendorId, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity, | 
|  | outNewFabricIndex); | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Use an NOC and optional ICAC to update an existing fabric | 
|  | * | 
|  | * Operational key is assumed to be pending or committed in the associated mOperationalKeystore. | 
|  | * | 
|  | * The new NOC chain becomes temporarily active for purposes of `Fetch*` and `SignWithOpKeyPair`, etc. | 
|  | * The RCAC remains as before. For this method call to succeed, NOC chain must chain back to the existing RCAC. | 
|  | * The update fabric becomes permanent/persisted on successful `CommitPendingFabricData`. Changes revert | 
|  | * on `RevertPendingFabricData` or `RevertPendingOpCertsExceptRoot`. FabricId CANNOT be updated, but | 
|  | * CAT tags and Node ID in NOC can change between previous and new NOC for a given FabricId. | 
|  | * | 
|  | * This method with fail with CHIP_ERROR_INCORRECT_STATE in a variety of illogical/inconsistent conditions, | 
|  | * which always can be cleared with `RevertPendingFabricData`. Such a situation is calling this method after | 
|  | * `AddNewPending*` which would mean logical collision of an addition and an update. | 
|  | * | 
|  | * If a pending key was present in the OperationalKeystore associated with this FabricTable, | 
|  | * it is activated on success. | 
|  | * | 
|  | * @param fabricIndex - fabricIndex of the existing fabric to update | 
|  | * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore. | 
|  | * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order | 
|  | *                                    or if ICAC cannot be set due to VVSC presence (maps to INVALID_COMMAND). | 
|  | * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds. | 
|  | * @retval other CHIP_ERROR_* on internal errors or certificate validation errors. | 
|  | */ | 
|  | CHIP_ERROR UpdatePendingFabricWithOperationalKeystore(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, | 
|  | AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes) | 
|  | { | 
|  | return UpdatePendingFabricCommon(fabricIndex, noc, icac, nullptr, false, advertiseIdentity); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Use an NOC and optional ICAC to update an existing fabric | 
|  | * | 
|  | * Operational key is injected, and then owned by the fabric (!isExistingOpKeyExternallyOwned) or | 
|  | * owned externally if `isExistingOpKeyExternallyOwned` is true). | 
|  | * | 
|  | * WARNING: Copying keypairs is unsafe and not recommended. Consider using | 
|  | *          AddNewPendingFabricWithOperationalKeystore and an associated OperationalKeystore | 
|  | *          or always using `isExistingOpKeyExternallyOwned`, with `existingOpKey` being a safe | 
|  | *          class derived from P256Keypair that avoids the true private key persisting in memory. | 
|  | * | 
|  | * For rest of semantics outside of operational key, @see UpdatePendingFabricWithOperationalKeystore | 
|  | * | 
|  | * @param fabricIndex - fabricIndex of the existing fabric to update | 
|  | * @param noc - Updated NOC for the fabric. Must match an existing or pending operational keypair in the mOperationalKeystore. | 
|  | * @param icac - Update ICAC for the fabric. Can be empty if absent from the chain. | 
|  | * @param existingOpKey - Existing operational key to ingest for use in the fabric with new NOC. Cannot be nullptr. | 
|  | * @param isExistingOpKeyExternallyOwned - if true, operational key must outlive the fabric. If false, the key is | 
|  | *                                         copied using P256Keypair::Serialize/Deserialize and owned in heap of a FabricInfo. | 
|  | * | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if the `fabricIndex` is not an existing fabric | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if this is called in an inconsistent order | 
|  | *                                    or if ICAC cannot be set due to VVSC presence (maps to INVALID_COMMAND). | 
|  | * @retval CHIP_ERROR_NO_MEMORY if there is insufficient memory to store the pending updates | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if any of the arguments are invalid such as too large or out of bounds. | 
|  | * @retval other CHIP_ERROR_* on internal errors or certificate validation errors. | 
|  | */ | 
|  |  | 
|  | CHIP_ERROR UpdatePendingFabricWithProvidedOpKey(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, | 
|  | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, | 
|  | AdvertiseIdentity advertiseIdentity = AdvertiseIdentity::Yes) | 
|  | { | 
|  | return UpdatePendingFabricCommon(fabricIndex, noc, icac, existingOpKey, isExistingOpKeyExternallyOwned, advertiseIdentity); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Commit any pending temporary FabricTable state. This is used mostly for affecting | 
|  | *        CommissioningComplete. | 
|  | * | 
|  | * On success, any pending information is committed such that after a restart, it would | 
|  | * be found to be the same in persistent storage. | 
|  | * | 
|  | * If no changes were pending and state is internally consistent, this appears as a no-op and returns | 
|  | * CHIP_NO_ERROR. | 
|  | * | 
|  | * If there is any internally inconsistent state, this methods acts the same as RevertPendingFabricData(), | 
|  | * and all state is lost. | 
|  | * | 
|  | * In rare circumstances, and depending on the storage backend for opcerts and operational keys, | 
|  | * an inconsistent state could be left, such as if restarting during storage writes of | 
|  | * CommitPendingFabricData(). If this happens, the next FabricTable::Init() will attempt | 
|  | * to clean-up the pieces. | 
|  | * | 
|  | * @return CHIP_NO_ERROR on success or any other CHIP_ERROR value on internal errors | 
|  | */ | 
|  | CHIP_ERROR CommitPendingFabricData(); | 
|  |  | 
|  | /** | 
|  | * @brief Revert any pending state. | 
|  | * | 
|  | * This is used to handle fail-safe expiry of partially configured fabrics, or to recover | 
|  | * from situations where partial state was written and configuration cannot continue properly. | 
|  | * | 
|  | * All pending certificates and operational keys and pending fabric metadata are cleared. | 
|  | */ | 
|  | void RevertPendingFabricData(); | 
|  |  | 
|  | /** | 
|  | * @brief Revert only the pending NOC/ICAC and pending added fabric, not RCAC. Used for error handling | 
|  | *        during commissioning. | 
|  | */ | 
|  | void RevertPendingOpCertsExceptRoot(); | 
|  |  | 
|  | // Verifies credentials, using the root certificate of the provided fabric index. | 
|  | CHIP_ERROR VerifyCredentials(FabricIndex fabricIndex, ByteSpan noc, ByteSpan icac, Credentials::ValidationContext & context, | 
|  | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, NodeId & outNodeId, | 
|  | Crypto::P256PublicKey & outNocPubkey, Crypto::P256PublicKey * outRootPublicKey = nullptr) const; | 
|  |  | 
|  | // Verifies credentials, using the provided root certificate. | 
|  | static CHIP_ERROR VerifyCredentials(ByteSpan noc, ByteSpan icac, ByteSpan rcac, Credentials::ValidationContext & context, | 
|  | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, NodeId & outNodeId, | 
|  | Crypto::P256PublicKey & outNocPubkey, Crypto::P256PublicKey * outRootPublicKey = nullptr); | 
|  | /** | 
|  | * @brief Enables FabricInfo instances to collide and reference the same logical fabric (i.e Root Public Key + FabricId). | 
|  | * | 
|  | * *WARNING* This is ONLY to be used when creating multiple controllers on the same fabric OR for test. | 
|  | * | 
|  | */ | 
|  | void PermitCollidingFabrics() { mStateFlags.Set(StateFlags::kAreCollidingFabricsIgnored); } | 
|  |  | 
|  | // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will | 
|  | // get copied (directly) into the fabric table. | 
|  | CHIP_ERROR AddNewFabricForTest(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan opKeySpan, | 
|  | FabricIndex * outFabricIndex); | 
|  |  | 
|  | // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will | 
|  | // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending. | 
|  | CHIP_ERROR AddNewUncommittedFabricForTest(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan opKeySpan, | 
|  | FabricIndex * outFabricIndex); | 
|  |  | 
|  | // Same as AddNewFabricForTest, but ignore if we are colliding with same <Root Public Key, Fabric Id>, so | 
|  | // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form. | 
|  | CHIP_ERROR AddNewFabricForTestIgnoringCollisions(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan opKeySpan, | 
|  | FabricIndex * outFabricIndex) | 
|  | { | 
|  | PermitCollidingFabrics(); | 
|  | CHIP_ERROR err = AddNewFabricForTest(rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); | 
|  | mStateFlags.Clear(StateFlags::kAreCollidingFabricsIgnored); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | // For test only. See definition of `StateFlags::kAbortCommitForTest`. | 
|  | void SetForceAbortCommitForTest(bool abortCommitForTest) | 
|  | { | 
|  | (void) abortCommitForTest; | 
|  | #if CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
|  | if (abortCommitForTest) | 
|  | { | 
|  | mStateFlags.Set(StateFlags::kAbortCommitForTest); | 
|  | } | 
|  | else | 
|  | { | 
|  | mStateFlags.Clear(StateFlags::kAbortCommitForTest); | 
|  | } | 
|  | #endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Get the fabric index that will be used for the next fabric that will be | 
|  | * added.  Returns error if no more fabrics can be added, otherwise writes | 
|  | * the fabric index that will be used for the next addition into the | 
|  | * outparam. | 
|  | */ | 
|  | CHIP_ERROR PeekFabricIndexForNextAddition(FabricIndex & outIndex); | 
|  |  | 
|  | /** | 
|  | * Set the fabric index that will be used fo the next fabric added. | 
|  | * | 
|  | * Returns an error if the |fabricIndex| is already in use. | 
|  | */ | 
|  | CHIP_ERROR SetFabricIndexForNextAddition(FabricIndex fabricIndex); | 
|  |  | 
|  | /** | 
|  | * @brief Set the advertising behavior for the fabric identified by `fabricIndex`. | 
|  | * | 
|  | * It is the caller's responsibility to actually restart DNS-SD advertising | 
|  | * as needed after updating this state. | 
|  | * | 
|  | * @param fabricIndex - Fabric Index for which to set the label | 
|  | * @param advertiseIdentity - whether the identity for this fabric should be advertised. | 
|  | * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if fabricIndex does not refer to a fabric in the table | 
|  | */ | 
|  | CHIP_ERROR SetShouldAdvertiseIdentity(FabricIndex fabricIndex, AdvertiseIdentity advertiseIdentity); | 
|  |  | 
|  | /** | 
|  | * @brief Request a VID verification signature to be generated for a specific fabric in the FabricTable. | 
|  | * | 
|  | * @param[in] fabricIndex - Fabric Index for which to produce the response. | 
|  | * @param[in] clientChallenge - Client-provided challenge. | 
|  | * @param[in] attestationChallenge - Attestation challenge from the secure session. | 
|  | * @param[out] outResponse - Reference to a pre-allocated response object that will be populated on success. | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if the fabricIndex or clientChallenge is incorrectly formatted. | 
|  | */ | 
|  | CHIP_ERROR SignVIDVerificationRequest(FabricIndex fabricIndex, ByteSpan clientChallenge, ByteSpan attestationChallenge, | 
|  | SignVIDVerificationResponseData & outResponse); | 
|  |  | 
|  | /** | 
|  | * @brief Handle setting data related to Fabric Table VID Verification. | 
|  | * | 
|  | * This is on purpose structured to mirror the SetVIDVerificationStatement Operational Credentials Cluster command | 
|  | * | 
|  | * @param[in] fabricIndex - Fabric Index for which to produce the response. | 
|  | * @param[in] vendorID - New VendorID to set on the Fabric (ignored if missing) | 
|  | * @param[in] VIDVerificationStatement - VID Verification Statement to add/remove (ignored if missing) | 
|  | * @param[in] VVSC - VID Verification Signing Certificate to add/remove (ignored if missing) | 
|  | * @param[out] outFabricTableWasChanged - This is set to true if FabricTable saw a change from prior value, even if | 
|  | *                                        method returns an error (appies to VIDVerificationStatement and VendorID) | 
|  | * @retval CHIP_NO_ERROR on success | 
|  | * @retval CHIP_ERROR_INVALID_ARGUMENT if vendorID, VVSC or VIDVerificationStatement are not correct (maps to CONSTRAINT_ERROR) | 
|  | * @retval CHIP_ERROR_INCORRECT_STATE if VVSC cannot be set due to ICAC presence (maps to INVALID_COMMAND) | 
|  | */ | 
|  | CHIP_ERROR SetVIDVerificationStatementElements(FabricIndex fabricIndex, Optional<uint16_t> vendorId, | 
|  | Optional<ByteSpan> VIDVerificationStatement, Optional<ByteSpan> VVSC, | 
|  | bool & outFabricTableWasChanged); | 
|  |  | 
|  | private: | 
|  | enum class StateFlags : uint16_t | 
|  | { | 
|  | // If true, we are in the process of a fail-safe and there was at least one | 
|  | // operation that caused partial data in the fabric table. | 
|  | kIsPendingFabricDataPresent = (1u << 0), | 
|  | kIsTrustedRootPending       = (1u << 1), | 
|  | kIsUpdatePending            = (1u << 2), | 
|  | kIsAddPending               = (1u << 3), | 
|  |  | 
|  | // Only true when `AllocatePendingOperationalKey` has been called | 
|  | kIsOperationalKeyPending = (1u << 4), | 
|  | // True if `AllocatePendingOperationalKey` was for an existing fabric | 
|  | kIsPendingKeyForUpdateNoc = (1u << 5), | 
|  |  | 
|  | // True if we allow more than one fabric with same root and fabricId in the fabric table | 
|  | // for test purposes. This disables a collision check. | 
|  | kAreCollidingFabricsIgnored = (1u << 6), | 
|  |  | 
|  | // If set to true (only possible on test builds), will cause `CommitPendingFabricData()` to early | 
|  | // return during commit, skipping clean-ups, so that we can validate commit marker fabric removal. | 
|  | kAbortCommitForTest = (1u << 7), | 
|  | }; | 
|  |  | 
|  | // Stored to indicate a commit is in progress, so that it can be cleaned-up on next boot | 
|  | // if stopped in the middle. | 
|  | struct CommitMarker | 
|  | { | 
|  | CommitMarker() = default; | 
|  | CommitMarker(FabricIndex fabricIndex_, bool isAddition_) | 
|  | { | 
|  | this->fabricIndex = fabricIndex_; | 
|  | this->isAddition  = isAddition_; | 
|  | } | 
|  | FabricIndex fabricIndex = kUndefinedFabricIndex; | 
|  | bool isAddition         = false; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Get a mutable FabricInfo entry from the table by FabricIndex. | 
|  | * | 
|  | * NOTE: This is private for use within the FabricTable itself. All mutations have to go through the | 
|  | *       FabricTable public methods that take a FabricIndex so that there are no mutations about which | 
|  | *       the FabricTable is unaware, since this would break expectations regarding shadow/pending | 
|  | *       entries used during fail-safe. | 
|  | * | 
|  | * @param fabricIndex - fabric index for which to get a mutable FabricInfo entry | 
|  | * @return the FabricInfo entry for the fabricIndex if found, or nullptr if not found | 
|  | */ | 
|  | FabricInfo * GetMutableFabricByIndex(FabricIndex fabricIndex); | 
|  |  | 
|  | // Load a FabricInfo metatada item from storage for a given new fabric index. Returns internal error on failure. | 
|  | CHIP_ERROR LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex); | 
|  |  | 
|  | // Store a given fabric metadata directly/immediately. Used by internal operations. | 
|  | CHIP_ERROR StoreFabricMetadata(const FabricInfo * fabricInfo) const; | 
|  |  | 
|  | // Tries to set `mFabricIndexWithPendingState` and returns false if there's a clash. | 
|  | bool SetPendingDataFabricIndex(FabricIndex fabricIndex); | 
|  |  | 
|  | // Core validation logic for fabric additions/updates | 
|  | CHIP_ERROR AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey, | 
|  | bool isExistingOpKeyExternallyOwned, uint16_t vendorId, AdvertiseIdentity advertiseIdentity); | 
|  |  | 
|  | // Common code for fabric addition, for either OperationalKeystore or injected key scenarios. | 
|  | CHIP_ERROR AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId, | 
|  | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, | 
|  | AdvertiseIdentity advertiseIdentity, FabricIndex * outNewFabricIndex); | 
|  |  | 
|  | // Common code for fabric updates, for either OperationalKeystore or injected key scenarios. | 
|  | CHIP_ERROR UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac, | 
|  | Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned, | 
|  | AdvertiseIdentity advertiseIdentity); | 
|  |  | 
|  | // Common code for looking up a fabric given a root public key, a fabric ID and an optional node id scoped to that fabric. | 
|  | const FabricInfo * FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, | 
|  | NodeId nodeId = kUndefinedNodeId) const; | 
|  |  | 
|  | /** | 
|  | * UpdateNextAvailableFabricIndex should only be called when | 
|  | * mNextAvailableFabricIndex has a value and that value stops being | 
|  | * available.  It will set mNextAvailableFabricIndex to the next available | 
|  | * value, or no value if there is none available. | 
|  | */ | 
|  | void UpdateNextAvailableFabricIndex(); | 
|  |  | 
|  | /** | 
|  | * Ensure that we have a valid next available fabric index, if that's at all possible.  This covers | 
|  | * some FabricIndex allocation corner cases.  After this is called, the only way we can fail to have | 
|  | * a next available fabric index is if our fabric table is max-sized (254 entries) and full. | 
|  | */ | 
|  | void EnsureNextAvailableFabricIndexUpdated(); | 
|  |  | 
|  | /** | 
|  | * Store our current fabric index state: what our next available index is | 
|  | * and what indices we're using right now. | 
|  | */ | 
|  | CHIP_ERROR StoreFabricIndexInfo() const; | 
|  |  | 
|  | /** | 
|  | * @brief Delete all metadata from storage for the given fabric | 
|  | * | 
|  | * @param fabricIndex FabricIndex for which to delete the metadadata | 
|  | * @return CHIP_NO_ERROR on success or another CHIP_ERROR on failure | 
|  | */ | 
|  | CHIP_ERROR DeleteMetadataFromStorage(FabricIndex fabricIndex); | 
|  |  | 
|  | /** | 
|  | * @brief Determine if a collision (undesired on AddNOC, necessary on UpdateNOC) exists | 
|  | *        between the FabricID in the given noc, and the RCAC found for `currentFabricIndex` | 
|  | *        in the op cert store, against an existing fabric in the FabricTable (which could be pending) | 
|  | * | 
|  | * @param currentFabricIndex - pending fabricIndex for which we are trying to Add/Update a NOC | 
|  | * @param noc - NOC cert received that contains FabricID whose collision we care to validate | 
|  | * @param outMatchingFabricIndex - set to the FabricIndex matching the collision or kUndefinedFabricIndex on no collision found | 
|  | * @return CHIP_NO_ERROR on successful update of outMatchingFabricIndex or other CHIP_ERROR on internal errors | 
|  | */ | 
|  | CHIP_ERROR FindExistingFabricByNocChaining(FabricIndex currentFabricIndex, const ByteSpan & noc, | 
|  | FabricIndex & outMatchingFabricIndex) const; | 
|  |  | 
|  | /** | 
|  | * @brief Get the shadow FabricInfo entry that is pending for updates, if an | 
|  | *        update is in progress. | 
|  | * | 
|  | * @return a pointer to the shadow pending fabric or nullptr if none is active. | 
|  | */ | 
|  | const FabricInfo * GetShadowPendingFabricEntry() const { return HasPendingFabricUpdate() ? &mPendingFabric : nullptr; } | 
|  |  | 
|  | // Returns true if we have a shadow entry pending for a fabric update. | 
|  | bool HasPendingFabricUpdate() const | 
|  | { | 
|  | return mPendingFabric.IsInitialized() && | 
|  | mStateFlags.HasAll(StateFlags::kIsPendingFabricDataPresent, StateFlags::kIsUpdatePending); | 
|  | } | 
|  |  | 
|  | // Validate an NOC chain at time of adding/updating a fabric (uses VerifyCredentials with additional checks). | 
|  | // The `existingFabricId` is passed for UpdateNOC, and must match the Fabric, to make sure that we are | 
|  | // not trying to change FabricID with UpdateNOC. If set to kUndefinedFabricId, we are doing AddNOC and | 
|  | // we don't need to check match to pre-existing fabric. | 
|  | static CHIP_ERROR ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, | 
|  | FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy, | 
|  | CompressedFabricId & outCompressedFabricId, FabricId & outFabricId, | 
|  | NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey, | 
|  | Crypto::P256PublicKey & outRootPubkey); | 
|  |  | 
|  | /** | 
|  | * Read our fabric index info from the given TLV reader and set up the | 
|  | * fabric table accordingly. | 
|  | */ | 
|  | CHIP_ERROR ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader); | 
|  |  | 
|  | CHIP_ERROR NotifyFabricUpdated(FabricIndex fabricIndex); | 
|  | CHIP_ERROR NotifyFabricCommitted(FabricIndex fabricIndex); | 
|  |  | 
|  | // Commit management clean-up APIs | 
|  | CHIP_ERROR StoreCommitMarker(const CommitMarker & commitMarker); | 
|  | CHIP_ERROR GetCommitMarker(CommitMarker & outCommitMarker); | 
|  |  | 
|  | FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS]; | 
|  | // Used for UpdateNOC pending fabric updates | 
|  | FabricInfo mPendingFabric; | 
|  | PersistentStorageDelegate * mStorage                    = nullptr; | 
|  | Crypto::OperationalKeystore * mOperationalKeystore      = nullptr; | 
|  | Credentials::OperationalCertificateStore * mOpCertStore = nullptr; | 
|  |  | 
|  | // FabricTable::Delegate link to first node, since FabricTable::Delegate is a form | 
|  | // of intrusive linked-list item. | 
|  | FabricTable::Delegate * mDelegateListRoot = nullptr; | 
|  |  | 
|  | // When mStateFlags.Has(kIsPendingFabricDataPresent) is true, this holds the index of the fabric | 
|  | // for which there is currently pending data. | 
|  | FabricIndex mFabricIndexWithPendingState = kUndefinedFabricIndex; | 
|  |  | 
|  | // For when a revert occurs during init, so that more clean-up can be scheduled by caller. | 
|  | FabricIndex mDeletedFabricIndexFromInit = kUndefinedFabricIndex; | 
|  |  | 
|  | LastKnownGoodTime mLastKnownGoodTime; | 
|  |  | 
|  | // We may not have an mNextAvailableFabricIndex if our table is as large as | 
|  | // it can go and is full. | 
|  | Optional<FabricIndex> mNextAvailableFabricIndex; | 
|  | uint8_t mFabricCount = 0; | 
|  |  | 
|  | BitFlags<StateFlags> mStateFlags; | 
|  | }; | 
|  |  | 
|  | } // namespace chip |