Matter SDK Coverage Report
Current view: top level - app - OperationalSessionSetup.h (source / functions) Coverage Total Hit
Test: SHA:b879ecb8e99e175eea0a293a888bda853da2b19c Lines: 2.8 % 36 1
Test Date: 2025-01-17 19:00:11 Functions: 6.7 % 15 1

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2020-2021 Project CHIP Authors
       4              :  *    All rights reserved.
       5              :  *
       6              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       7              :  *    you may not use this file except in compliance with the License.
       8              :  *    You may obtain a copy of the License at
       9              :  *
      10              :  *        http://www.apache.org/licenses/LICENSE-2.0
      11              :  *
      12              :  *    Unless required by applicable law or agreed to in writing, software
      13              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      14              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      15              :  *    See the License for the specific language governing permissions and
      16              :  *    limitations under the License.
      17              :  */
      18              : 
      19              : /**
      20              :  *  @file
      21              :  *    This file contains definitions for Device class. The objects of this
      22              :  *    class will be used by Controller applications to interact with CHIP
      23              :  *    devices. The class provides mechanism to construct, send and receive
      24              :  *    messages to and from the corresponding CHIP devices.
      25              :  */
      26              : 
      27              : #pragma once
      28              : 
      29              : #include <app/AppConfig.h>
      30              : #include <app/CASEClient.h>
      31              : #include <app/CASEClientPool.h>
      32              : #include <app/DeviceProxy.h>
      33              : #include <app/util/basic-types.h>
      34              : #include <credentials/GroupDataProvider.h>
      35              : #include <lib/address_resolve/AddressResolve.h>
      36              : #include <lib/core/GroupedCallbackList.h>
      37              : #include <messaging/ExchangeContext.h>
      38              : #include <messaging/ExchangeDelegate.h>
      39              : #include <messaging/ExchangeMgr.h>
      40              : #include <messaging/Flags.h>
      41              : #include <messaging/ReliableMessageProtocolConfig.h>
      42              : #include <platform/CHIPDeviceConfig.h>
      43              : #include <protocols/secure_channel/CASESession.h>
      44              : #include <system/SystemClock.h>
      45              : #include <system/SystemLayer.h>
      46              : #include <transport/SessionManager.h>
      47              : #include <transport/TransportMgr.h>
      48              : #include <transport/raw/MessageHeader.h>
      49              : #include <transport/raw/UDP.h>
      50              : 
      51              : namespace chip {
      52              : 
      53              : class OperationalSessionSetup;
      54              : 
      55              : /**
      56              :  * @brief Delegate provided when creating OperationalSessionSetup.
      57              :  *
      58              :  * Once OperationalSessionSetup establishes a connection (or errors out) and has notified all
      59              :  * registered application callbacks via OnDeviceConnected/OnDeviceConnectionFailure, this delegate
      60              :  * is used to deallocate the OperationalSessionSetup.
      61              :  */
      62              : class OperationalSessionReleaseDelegate
      63              : {
      64              : public:
      65            1 :     virtual ~OperationalSessionReleaseDelegate()                        = default;
      66              :     virtual void ReleaseSession(OperationalSessionSetup * sessionSetup) = 0;
      67              : };
      68              : 
      69              : /**
      70              :  * @brief Minimal implementation of DeviceProxy that encapsulates a SessionHolder to track a CASE session.
      71              :  *
      72              :  * Deprecated - Avoid using this object.
      73              :  *
      74              :  * OperationalDeviceProxy is a minimal implementation of DeviceProxy. It is meant to provide a transition
      75              :  * for existing consumers of OperationalDeviceProxy that were delivered a reference to that object in
      76              :  * their respective OnDeviceConnected callback, but were incorrectly holding onto that object past
      77              :  * the function call. OperationalDeviceProxy can be held on for as long as is desired, while still
      78              :  * minimizing the code changes needed to transition to a more final solution by virtue of
      79              :  * implementing DeviceProxy.
      80              :  */
      81              : class OperationalDeviceProxy : public DeviceProxy
      82              : {
      83              : public:
      84            0 :     OperationalDeviceProxy(Messaging::ExchangeManager * exchangeMgr, const SessionHandle & sessionHandle) :
      85            0 :         mExchangeMgr(exchangeMgr), mSecureSession(sessionHandle), mPeerScopedNodeId(sessionHandle->GetPeer())
      86            0 :     {}
      87            0 :     OperationalDeviceProxy() {}
      88              : 
      89            0 :     void Disconnect() override
      90              :     {
      91            0 :         if (IsSecureConnected())
      92              :         {
      93            0 :             GetSecureSession().Value()->AsSecureSession()->MarkAsDefunct();
      94              :         }
      95            0 :         mSecureSession.Release();
      96            0 :         mExchangeMgr      = nullptr;
      97            0 :         mPeerScopedNodeId = ScopedNodeId();
      98            0 :     }
      99            0 :     Messaging::ExchangeManager * GetExchangeManager() const override { return mExchangeMgr; }
     100            0 :     chip::Optional<SessionHandle> GetSecureSession() const override { return mSecureSession.Get(); }
     101            0 :     NodeId GetDeviceId() const override { return mPeerScopedNodeId.GetNodeId(); }
     102              :     ScopedNodeId GetPeerScopedNodeId() const { return mPeerScopedNodeId; }
     103              : 
     104              :     bool ConnectionReady() const { return (mExchangeMgr != nullptr && IsSecureConnected()); }
     105              : 
     106              : private:
     107            0 :     bool IsSecureConnected() const override { return static_cast<bool>(mSecureSession); }
     108              : 
     109              :     Messaging::ExchangeManager * mExchangeMgr = nullptr;
     110              :     SessionHolder mSecureSession;
     111              :     ScopedNodeId mPeerScopedNodeId;
     112              : };
     113              : 
     114              : /**
     115              :  * @brief Callback prototype when secure session is established.
     116              :  *
     117              :  * Callback implementations are not supposed to store the exchangeMgr or the sessionHandle. Older
     118              :  * application code does incorrectly hold onto this information so do not follow those incorrect
     119              :  * implementations as an example.
     120              :  */
     121              : typedef void (*OnDeviceConnected)(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle);
     122              : 
     123              : /**
     124              :  * Callback prototype when secure session establishment fails.
     125              :  */
     126              : typedef void (*OnDeviceConnectionFailure)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error);
     127              : 
     128              : /**
     129              :  * Callback prototype when secure session establishement has failed and will be
     130              :  * retried.  retryTimeout indicates how much time will pass before we know
     131              :  * whether the retry has timed out waiting for a response to our Sigma1 message.
     132              :  */
     133              : typedef void (*OnDeviceConnectionRetry)(void * context, const ScopedNodeId & peerId, CHIP_ERROR error,
     134              :                                         System::Clock::Seconds16 retryTimeout);
     135              : 
     136              : /**
     137              :  * Object used to either establish a connection to peer or performing address lookup to a peer.
     138              :  *
     139              :  * OperationalSessionSetup is capable of either:
     140              :  *    1. Establishing a CASE session connection to a peer. Upon success or failure, the OnDeviceConnected or
     141              :  *       OnDeviceConnectionFailure callback will be called to notify the caller the results of trying to
     142              :  *       estblish a CASE session. Some additional details about the steps to establish a connection are:
     143              :  *          - Discover the device using DNSSD (find out what IP address to use and what
     144              :  *            communication parameters are appropriate for it)
     145              :  *          - Establish a secure channel to it via CASE
     146              :  *          - Expose to consumers the secure session for talking to the device via OnDeviceConnected
     147              :  *            callback.
     148              :  *    2. Performing an address lookup for given a scoped nodeid. On success, it will call into
     149              :  *       SessionManager to update the addresses for all matching sessions in the session table.
     150              :  *
     151              :  * OperationalSessionSetup has a very limited lifetime. Once it has completed its purpose outlined above,
     152              :  * it will use `releaseDelegate` to release itself.
     153              :  *
     154              :  * It is possible to determine which of the two purposes the OperationalSessionSetup is for by calling
     155              :  * IsForAddressUpdate().
     156              :  */
     157              : class DLL_EXPORT OperationalSessionSetup : public SessionEstablishmentDelegate, public AddressResolve::NodeListener
     158              : {
     159              : public:
     160              :     struct ConnectionFailureInfo
     161              :     {
     162              :         const ScopedNodeId peerId;
     163              :         CHIP_ERROR error;
     164              :         SessionEstablishmentStage sessionStage;
     165              : 
     166              :         // When the response was BUSY, error will be CHIP_ERROR_BUSY and
     167              :         // requestedBusyDelay will be set, if handling of BUSY responses is
     168              :         // enabled.
     169              : #if CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
     170              :         Optional<System::Clock::Milliseconds16> requestedBusyDelay;
     171              : #endif // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
     172              : 
     173            0 :         ConnectionFailureInfo(const ScopedNodeId & peer, CHIP_ERROR err, SessionEstablishmentStage stage) :
     174            0 :             peerId(peer), error(err), sessionStage(stage)
     175            0 :         {}
     176              :     };
     177              : 
     178              :     using OnSetupFailure = void (*)(void * context, const ConnectionFailureInfo & failureInfo);
     179              : 
     180              :     ~OperationalSessionSetup() override;
     181              : 
     182            0 :     OperationalSessionSetup(const CASEClientInitParams & params, CASEClientPoolDelegate * clientPool, ScopedNodeId peerId,
     183              :                             OperationalSessionReleaseDelegate * releaseDelegate)
     184            0 :     {
     185            0 :         mInitParams = params;
     186            0 :         if (params.Validate() != CHIP_NO_ERROR || clientPool == nullptr || releaseDelegate == nullptr)
     187              :         {
     188            0 :             mState = State::Uninitialized;
     189            0 :             return;
     190              :         }
     191              : 
     192            0 :         mClientPool      = clientPool;
     193            0 :         mPeerId          = peerId;
     194            0 :         mReleaseDelegate = releaseDelegate;
     195            0 :         mState           = State::NeedsAddress;
     196            0 :         mAddressLookupHandle.SetListener(this);
     197              :     }
     198              : 
     199              :     /*
     200              :      * This function can be called to establish a secure session with the device.
     201              :      *
     202              :      * The device is expected to have been commissioned, A CASE session
     203              :      * setup will be triggered.
     204              :      *
     205              :      * If session setup succeeds, the callback function `onConnection` will be called.
     206              :      * If session setup fails, `onFailure` will be called.
     207              :      *
     208              :      * If the session already exists, `onConnection` will be called immediately,
     209              :      * before the Connect call returns.
     210              :      *
     211              :      * `onFailure` may be called before the Connect call returns, for error
     212              :      * cases that are detected synchronously (e.g. inability to start an address
     213              :      * lookup).
     214              :      *
     215              :      * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
     216              :      * over a transport that allows large payloads to be transferred, e.g., TCP.
     217              :      */
     218              :     void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
     219              :                  TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
     220              : 
     221              :     /*
     222              :      * This function can be called to establish a secure session with the device.
     223              :      *
     224              :      * The device is expected to have been commissioned, A CASE session
     225              :      * setup will be triggered.
     226              :      *
     227              :      * If session setup succeeds, the callback function `onConnection` will be called.
     228              :      * If session setup fails, `onSetupFailure` will be called.
     229              :      *
     230              :      * If the session already exists, `onConnection` will be called immediately,
     231              :      * before the Connect call returns.
     232              :      *
     233              :      * `onSetupFailure` may be called before the Connect call returns, for error cases that are detected synchronously
     234              :      * (e.g. inability to start an address lookup).
     235              :      *
     236              :      * `transportPayloadCapability` is set to kLargePayload when the session needs to be established
     237              :      * over a transport that allows large payloads to be transferred, e.g., TCP.
     238              :      */
     239              :     void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnSetupFailure> * onSetupFailure,
     240              :                  TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
     241              : 
     242            0 :     bool IsForAddressUpdate() const { return mPerformingAddressUpdate; }
     243              : 
     244              :     //////////// SessionEstablishmentDelegate Implementation ///////////////
     245              :     void OnSessionEstablished(const SessionHandle & session) override;
     246              :     void OnSessionEstablishmentError(CHIP_ERROR error, SessionEstablishmentStage stage) override;
     247              :     void OnResponderBusy(System::Clock::Milliseconds16 requestedDelay) override;
     248              : 
     249            0 :     ScopedNodeId GetPeerId() const { return mPeerId; }
     250              : 
     251              :     static Transport::PeerAddress ToPeerAddress(const Dnssd::ResolvedNodeData & nodeData)
     252              :     {
     253              :         Inet::InterfaceId interfaceId = Inet::InterfaceId::Null();
     254              : 
     255              :         // TODO - Revisit usage of InterfaceID only for addresses that are IPv6 LLA
     256              :         // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA.
     257              :         // For all other addresses, we should rely on the device's routing table to route messages sent.
     258              :         // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread,
     259              :         // mDNS advertisements are not usually received on the same interface the peer is reachable on.
     260              :         if (nodeData.resolutionData.ipAddress[0].IsIPv6LinkLocal())
     261              :         {
     262              :             interfaceId = nodeData.resolutionData.interfaceId;
     263              :         }
     264              : 
     265              :         return Transport::PeerAddress::UDP(nodeData.resolutionData.ipAddress[0], nodeData.resolutionData.port, interfaceId);
     266              :     }
     267              : 
     268              :     /**
     269              :      * @brief Get the fabricIndex
     270              :      */
     271            0 :     FabricIndex GetFabricIndex() const { return mPeerId.GetFabricIndex(); }
     272              : 
     273              :     void PerformAddressUpdate();
     274              : 
     275              :     // AddressResolve::NodeListener - notifications when dnssd finds a node IP address
     276              :     void OnNodeAddressResolved(const PeerId & peerId, const AddressResolve::ResolveResult & result) override;
     277              :     void OnNodeAddressResolutionFailed(const PeerId & peerId, CHIP_ERROR reason) override;
     278              : 
     279              : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     280              :     // Update our remaining attempt count to be at least the given value.
     281              :     void UpdateAttemptCount(uint8_t attemptCount);
     282              : 
     283              :     // Add a retry handler for this session setup.
     284              :     void AddRetryHandler(Callback::Callback<OnDeviceConnectionRetry> * onRetry);
     285              : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     286              : 
     287              : private:
     288              :     enum class State : uint8_t
     289              :     {
     290              :         Uninitialized,    // Error state: OperationalSessionSetup is useless
     291              :         NeedsAddress,     // No address known, lookup not started yet.
     292              :         ResolvingAddress, // Address lookup in progress.
     293              :         HasAddress,       // Have an address, CASE handshake not started yet.
     294              :         Connecting,       // CASE handshake in progress.
     295              :         SecureConnected,  // CASE session established.
     296              :         WaitingForRetry,  // No address known, but a retry is pending.  Added at
     297              :                           // end to make logs easier to understand.
     298              :     };
     299              : 
     300              :     CASEClientInitParams mInitParams;
     301              :     CASEClientPoolDelegate * mClientPool = nullptr;
     302              : 
     303              :     // mCASEClient is only non-null if we are in State::Connecting or just
     304              :     // allocated it as part of an attempt to enter State::Connecting.
     305              :     CASEClient * mCASEClient = nullptr;
     306              : 
     307              :     ScopedNodeId mPeerId;
     308              : 
     309              :     Transport::PeerAddress mDeviceAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any);
     310              : 
     311              :     SessionHolder mSecureSession;
     312              : 
     313              :     typedef Callback::GroupedCallbackList<OnDeviceConnected, OnDeviceConnectionFailure, OnSetupFailure> SuccessFailureCallbackList;
     314              :     SuccessFailureCallbackList mCallbacks;
     315              : 
     316              :     OperationalSessionReleaseDelegate * mReleaseDelegate;
     317              : 
     318              :     /// This is used when a node address is required.
     319              :     chip::AddressResolve::NodeLookupHandle mAddressLookupHandle;
     320              : 
     321              :     State mState = State::Uninitialized;
     322              : 
     323              :     bool mPerformingAddressUpdate = false;
     324              : 
     325              : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
     326              :     System::Clock::Milliseconds16 mRequestedBusyDelay = System::Clock::kZero;
     327              : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES || CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP
     328              : 
     329              :     TransportPayloadCapability mTransportPayloadCapability = TransportPayloadCapability::kMRPPayload;
     330              : 
     331              : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     332              :     // When we TryNextResult on the resolver, it will synchronously call back
     333              :     // into our OnNodeAddressResolved when it succeeds.  We need to track
     334              :     // whether the OnNodeAddressResolved is coming from handling a session
     335              :     // establishment error or whether it's happening because we didn't even
     336              :     // manage to start a session establishment at all.  Use this member to keep
     337              :     // track of that.
     338              :     bool mTryingNextResultDueToSessionEstablishmentError = false;
     339              : 
     340              :     uint8_t mRemainingAttempts = 0;
     341              :     uint8_t mAttemptsDone      = 0;
     342              : 
     343              :     uint8_t mResolveAttemptsAllowed = 0;
     344              : 
     345              :     Callback::CallbackDeque mConnectionRetry;
     346              : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     347              : 
     348              :     void MoveToState(State aTargetState);
     349              : 
     350              :     CHIP_ERROR EstablishConnection(const AddressResolve::ResolveResult & result);
     351              : 
     352              :     /*
     353              :      * This checks to see if an existing CASE session exists to the peer within the SessionManager
     354              :      * and if one exists, to load that into mSecureSession.
     355              :      *
     356              :      * Returns true if a valid session was found, false otherwise.
     357              :      *
     358              :      */
     359              :     bool AttachToExistingSecureSession();
     360              : 
     361              :     void CleanupCASEClient();
     362              : 
     363              :     void Connect(Callback::Callback<OnDeviceConnected> * onConnection, Callback::Callback<OnDeviceConnectionFailure> * onFailure,
     364              :                  Callback::Callback<OnSetupFailure> * onSetupFailure,
     365              :                  TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload);
     366              : 
     367              :     void EnqueueConnectionCallbacks(Callback::Callback<OnDeviceConnected> * onConnection,
     368              :                                     Callback::Callback<OnDeviceConnectionFailure> * onFailure,
     369              :                                     Callback::Callback<OnSetupFailure> * onSetupFailure);
     370              : 
     371              :     enum class ReleaseBehavior
     372              :     {
     373              :         Release,
     374              :         DoNotRelease
     375              :     };
     376              : 
     377              :     /*
     378              :      * This dequeues all failure and success callbacks and appropriately invokes either set depending
     379              :      * on the value of error.
     380              :      *
     381              :      * If error == CHIP_NO_ERROR, only success callbacks are invoked. Otherwise, only failure callbacks are invoked.
     382              :      *
     383              :      * The state offers additional context regarding the failure, indicating the specific state in which
     384              :      * the error occurs. It is only relayed through failure callbacks when the error is not equal to CHIP_NO_ERROR.
     385              :      *
     386              :      * If releaseBehavior is Release, this uses mReleaseDelegate to release
     387              :      * ourselves (aka `this`). As a result any caller should return right away
     388              :      * without touching `this`.
     389              :      *
     390              :      * Setting releaseBehavior to DoNotRelease is meant for use from the destructor
     391              :      */
     392              :     void DequeueConnectionCallbacks(CHIP_ERROR error, SessionEstablishmentStage stage,
     393              :                                     ReleaseBehavior releaseBehavior = ReleaseBehavior::Release);
     394              : 
     395            0 :     void DequeueConnectionCallbacks(CHIP_ERROR error, ReleaseBehavior releaseBehavior = ReleaseBehavior::Release)
     396              :     {
     397            0 :         this->DequeueConnectionCallbacks(error, SessionEstablishmentStage::kNotInKeyExchange, releaseBehavior);
     398            0 :     }
     399              : 
     400              :     /**
     401              :      * Helper for DequeueConnectionCallbacks that handles the actual callback
     402              :      * notifications. This happens after the object has been released, if it's
     403              :      * being released.
     404              :      */
     405              :     static void NotifyConnectionCallbacks(SuccessFailureCallbackList & ready, CHIP_ERROR error, SessionEstablishmentStage stage,
     406              :                                           const ScopedNodeId & peerId, Messaging::ExchangeManager * exchangeMgr,
     407              :                                           const Optional<SessionHandle> & optionalSessionHandle,
     408              :                                           // requestedBusyDelay will be 0 if not
     409              :                                           // CHIP_CONFIG_ENABLE_BUSY_HANDLING_FOR_OPERATIONAL_SESSION_SETUP,
     410              :                                           // and only has a meaningful value
     411              :                                           // when the error is CHIP_ERROR_BUSY.
     412              :                                           System::Clock::Milliseconds16 requestedBusyDelay);
     413              : 
     414              :     /**
     415              :      * Triggers a DNSSD lookup to find a usable peer address.
     416              :      */
     417              :     CHIP_ERROR LookupPeerAddress();
     418              : 
     419              :     /**
     420              :      * This function will set new IP address, port and MRP retransmission intervals of the device.
     421              :      */
     422              :     void UpdateDeviceData(const AddressResolve::ResolveResult & result);
     423              : 
     424              : #if CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     425              :     /**
     426              :      * Schedule a setup reattempt, if possible.  The outparam indicates how long
     427              :      * it will be before the reattempt happens.
     428              :      */
     429              :     CHIP_ERROR ScheduleSessionSetupReattempt(System::Clock::Seconds16 & timerDelay);
     430              : 
     431              :     /**
     432              :      * Cancel a scheduled setup reattempt, if we can (i.e. if we still have
     433              :      * access to the SystemLayer).
     434              :      */
     435              :     void CancelSessionSetupReattempt();
     436              : 
     437              :     /**
     438              :      * Helper for our backoff retry timer.
     439              :      */
     440              :     static void TrySetupAgain(System::Layer * systemLayer, void * state);
     441              : 
     442              :     /**
     443              :      * Helper to notify our retry callbacks that a setup error occurred and we
     444              :      * will retry.
     445              :      */
     446              :     void NotifyRetryHandlers(CHIP_ERROR error, const ReliableMessageProtocolConfig & remoteMrpConfig,
     447              :                              System::Clock::Seconds16 retryDelay);
     448              : 
     449              :     /**
     450              :      * A version of NotifyRetryHandlers that passes in a retry timeout estimate
     451              :      * directly.
     452              :      */
     453              :     void NotifyRetryHandlers(CHIP_ERROR error, System::Clock::Seconds16 timeoutEstimate);
     454              : #endif // CHIP_DEVICE_CONFIG_ENABLE_AUTOMATIC_CASE_RETRIES
     455              : };
     456              : 
     457              : } // namespace chip
        

Generated by: LCOV version 2.0-1