blob: 0f5a72c72eb714fee2f8a383a4f752e8ee2b5f4b [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file defines read client for a CHIP Interaction Data model
*
*/
#pragma once
#include "system/SystemClock.h"
#include <app/AttributePathParams.h>
#include <app/ConcreteAttributePath.h>
#include <app/EventHeader.h>
#include <app/EventPathParams.h>
#include <app/MessageDef/ReadRequestMessage.h>
#include <app/MessageDef/StatusIB.h>
#include <app/MessageDef/StatusResponseMessage.h>
#include <app/MessageDef/SubscribeRequestMessage.h>
#include <app/MessageDef/SubscribeResponseMessage.h>
#include <app/OperationalSessionSetup.h>
#include <app/ReadPrepareParams.h>
#include <app/data-model/Decode.h>
#include <lib/core/CHIPCallback.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLVDebug.hpp>
#include <lib/support/CodeUtils.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeHolder.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
#include <system/SystemPacketBuffer.h>
namespace chip {
namespace app {
class InteractionModelEngine;
/**
* @class ReadClient
*
* @brief The read client represents the initiator side of a Read Or Subscribe Interaction (depending on the APIs invoked).
*
* When used to manage subscriptions, the client provides functionality to automatically re-subscribe as needed,
* including re-establishing CASE under certain conditions (see Callback::OnResubscriptionNeeded for more info).
* This is the default behavior. A consumer can completely opt-out of this behavior by over-riding
* Callback::OnResubscriptionNeeded and providing an alternative implementation.
*
*/
class ReadClient : public Messaging::ExchangeDelegate
{
public:
class Callback
{
public:
virtual ~Callback() = default;
/**
* Used to signal the commencement of processing of the first attribute or event report received in a given exchange.
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
* Once OnReportBegin has been called, either OnReportEnd or OnError will be called before OnDone.
*
*/
virtual void OnReportBegin() {}
/**
* Used to signal the completion of processing of the last attribute or event report in a given exchange.
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
*/
virtual void OnReportEnd() {}
/**
* Used to deliver event data received through the Read and Subscribe interactions
*
* Only one of the apData and apStatus can be non-null.
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
* @param[in] aEventHeader The event header in report response.
* @param[in] apData A TLVReader positioned right on the payload of the event.
* @param[in] apStatus Event-specific status, containing an InteractionModel::Status code as well as an optional
* cluster-specific status code.
*/
virtual void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) {}
/**
* Used to deliver attribute data received through the Read and Subscribe interactions.
*
* This callback will be called when:
* - Receiving attribute data as response of Read interactions
* - Receiving attribute data as reports of subscriptions
* - Receiving attribute data as initial reports of subscriptions
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
* @param[in] aPath The attribute path field in report response.
* @param[in] apData The attribute data of the given path, will be a nullptr if status is not Success.
* @param[in] aStatus Attribute-specific status, containing an InteractionModel::Status code as well as an
* optional cluster-specific status code.
*/
virtual void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) {}
/**
* OnSubscriptionEstablished will be called when a subscription is established for the given subscription transaction.
* If using auto resubscription, OnSubscriptionEstablished will be called whenever resubscription is established.
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
* @param[in] aSubscriptionId The identifier of the subscription that was established.
*/
virtual void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) {}
/**
* OnResubscriptionNeeded will be called when a subscription that was started with SendAutoResubscribeRequest has terminated
* and re-subscription is needed. The termination cause is provided to help inform subsequent re-subscription logic.
*
* The base implementation automatically re-subscribes at appropriate intervals taking the termination cause into account
* (see ReadClient::DefaultResubscribePolicy for more details). If the default implementation doesn't suffice, the logic of
* ReadClient::DefaultResubscribePolicy is broken down into its constituent methods that are publicly available for
* applications to call and sequence.
*
* If the method is over-ridden, it's the application's responsibility to take the appropriate steps needed to eventually
* call-back into the ReadClient object to schedule a re-subscription (by invoking ReadClient::ScheduleResubscription).
*
* If the application DOES NOT want re-subscription to happen on a particular invocation of this method, returning anything
* other than CHIP_NO_ERROR will terminate the interaction and result in OnError, OnDeallocatePaths and OnDone being called
* in that sequence.
*
* This object MUST continue to exist after this call is completed. The
* application shall wait until it receives an OnDone call to destroy the object.
*
* @param[in] aTerminationCause The cause of failure of the subscription that just terminated.
*/
virtual CHIP_ERROR OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause)
{
return apReadClient->DefaultResubscribePolicy(aTerminationCause);
}
/**
* OnError will be called when an error occurs *after* a successful call to SendRequest(). The following
* errors will be delivered through this call in the aError field:
*
* - CHIP_ERROR_TIMEOUT: A response was not received within the expected response timeout.
* - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from the server.
* - CHIP_ERROR encapsulating a StatusIB: If we got a non-path-specific
* status response from the server. In that case,
* StatusIB::InitFromChipError can be used to extract the status.
* - CHIP_ERROR*: All other cases.
*
* This object MUST continue to exist after this call is completed. The application shall wait until it
* receives an OnDone call to destroy the object.
*
* @param[in] aError A system error code that conveys the overall error code.
*/
virtual void OnError(CHIP_ERROR aError) {}
/**
* OnDone will be called when ReadClient has finished all work and it is
* safe to destroy and free the allocated ReadClient object and any
* other objects associated with the Read or Subscribe interaction.
*
* This function will:
* - Always be called exactly *once* for a given ReadClient instance.
* - Be called even in error circumstances.
* - Only be called after a successful call to SendRequest has been
* made, when the read completes or the subscription is shut down.
*
* @param[in] apReadClient the ReadClient for the completed interaction.
*/
virtual void OnDone(ReadClient * apReadClient) = 0;
/**
* This function is invoked when using SendAutoResubscribeRequest, where the ReadClient was configured to auto re-subscribe
* and the ReadPrepareParams was moved into this client for management. This will have to be free'ed appropriately by the
* application. If SendAutoResubscribeRequest fails, this function will be called before it returns the failure. If
* SendAutoResubscribeRequest succeeds, this function will be called immediately before calling OnDone. If
* SendAutoResubscribeRequest is not called, this function will not be called.
*/
virtual void OnDeallocatePaths(ReadPrepareParams && aReadPrepareParams) {}
/**
* This function is invoked when constructing a read/subscribeRequest that does not have data
* version filters specified, to give the callback a chance to provide some.
*
* This function is expected to encode as many complete data version filters as will fit into
* the buffer, rolling back any partially-encoded filters if it runs out of space, and set the
* aEncodedDataVersionList boolean to true if it has successfully encoded at least one data version filter.
*
* Otherwise aEncodedDataVersionList will be set to false.
*/
virtual CHIP_ERROR OnUpdateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
const Span<AttributePathParams> & aAttributePaths,
bool & aEncodedDataVersionList)
{
aEncodedDataVersionList = false;
return CHIP_NO_ERROR;
}
/*
* Get highest received event number.
* If the application does not want to filter events by event number, it should call ClearValue() on aEventNumber
* and return CHIP_NO_ERROR. An error return from this function will fail the entire read client interaction.
*/
virtual CHIP_ERROR GetHighestReceivedEventNumber(Optional<EventNumber> & aEventNumber)
{
aEventNumber.ClearValue();
return CHIP_NO_ERROR;
}
};
enum class InteractionType : uint8_t
{
Read,
Subscribe,
};
/**
*
* Constructor.
*
* The callback passed in has to outlive this ReadClient object.
*
* This object can outlive the InteractionModelEngine passed in. However, upon shutdown of the engine,
* this object will cease to function correctly since it depends on the engine for a number of critical functions.
*
* @param[in] apImEngine A valid pointer to the IM engine.
* @param[in] apExchangeMgr A pointer to the ExchangeManager object.
* @param[in] apCallback Callback set by application.
* @param[in] aInteractionType Type of interaction (read or subscribe)
*
* @retval #CHIP_ERROR_INCORRECT_STATE incorrect state if it is already initialized
* @retval #CHIP_NO_ERROR On success.
*
*/
ReadClient(InteractionModelEngine * apImEngine, Messaging::ExchangeManager * apExchangeMgr, Callback & apCallback,
InteractionType aInteractionType);
/**
* Destructor.
*
* Will abort the exchange context if a valid one still exists. It will also cancel any
* liveness timers that may be active.
*
* OnDone() will not be called.
*/
~ReadClient() override;
/*
* This forcibly closes the exchange context if a valid one is pointed to. Such a situation does
* not arise during normal message processing flows that all normally call Close() above. This can only
* arise due to application-initiated destruction of the object when this object is handling receiving/sending
* message payloads. Abort() should be called first before the object is destroyed.
*/
void Abort();
/**
* Send a request. There can be one request outstanding on a given ReadClient.
* If SendRequest returns success, no more SendRequest calls can happen on this ReadClient
* until the corresponding OnDone call has happened.
*
* This will send either a Read Request or a Subscribe Request depending on
* the InteractionType this read client was initialized with.
*
* @retval #others fail to send read request
* @retval #CHIP_NO_ERROR On success.
*/
CHIP_ERROR SendRequest(ReadPrepareParams & aReadPrepareParams);
void OnUnsolicitedReportData(Messaging::ExchangeContext * apExchangeContext, System::PacketBufferHandle && aPayload);
auto GetSubscriptionId() const
{
using returnType = Optional<decltype(mSubscriptionId)>;
return mInteractionType == InteractionType::Subscribe ? returnType(mSubscriptionId) : returnType::Missing();
}
FabricIndex GetFabricIndex() const { return mPeer.GetFabricIndex(); }
NodeId GetPeerNodeId() const { return mPeer.GetNodeId(); }
bool IsReadType() { return mInteractionType == InteractionType::Read; }
bool IsSubscriptionType() const { return mInteractionType == InteractionType::Subscribe; };
/*
* Retrieve the reporting intervals associated with an active subscription. This should only be called if we're of subscription
* interaction type and after a subscription has been established.
*/
CHIP_ERROR GetReportingIntervals(uint16_t & aMinIntervalFloorSeconds, uint16_t & aMaxIntervalCeilingSeconds) const
{
VerifyOrReturnError(IsSubscriptionType(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsSubscriptionActive(), CHIP_ERROR_INCORRECT_STATE);
aMinIntervalFloorSeconds = mMinIntervalFloorSeconds;
aMaxIntervalCeilingSeconds = mMaxInterval;
return CHIP_NO_ERROR;
}
ReadClient * GetNextClient() { return mpNext; }
void SetNextClient(ReadClient * apClient) { mpNext = apClient; }
/**
* Like SendSubscribeRequest, but the ReadClient will automatically attempt to re-establish the subscription if
* we decide that the subscription has dropped. The exact behavior of the re-establishment can be controlled
* by setting mResubscribePolicy in the ReadPrepareParams. If not set, a default behavior with exponential backoff will be
* used.
*
* The application has to know to
* a) allocate a ReadPrepareParams object that will have fields mpEventPathParamsList and mpAttributePathParamsList and
* mpDataVersionFilterList with lifetimes as long as the ReadClient itself and b) free those up later in the call to
* OnDeallocatePaths. Note: At a given time in the system, you can either have a single subscription with re-sub enabled that
* has mKeepSubscriptions = false, OR, multiple subs with re-sub enabled with mKeepSubscriptions = true. You shall not
* have a mix of both simultaneously. If SendAutoResubscribeRequest is called at all, it guarantees that it will call
* OnDeallocatePaths when OnDone is called. SendAutoResubscribeRequest is the only case that calls OnDeallocatePaths, since
* that's the only case when the consumer moved a ReadParams into the client.
*
*/
CHIP_ERROR SendAutoResubscribeRequest(ReadPrepareParams && aReadPrepareParams);
/**
* This provides a standard re-subscription policy implementation that given a termination cause, does the following:
* - Calculates the time till next subscription with fibonacci back-off (implemented by ComputeTimeTillNextSubscription()).
* - Schedules the next subscription attempt at the computed interval from the previous step. Operational discovery and
* CASE establishment will be attempted if aTerminationCause was CHIP_ERROR_TIMEOUT. In all other cases, it will attempt
* to re-use a previously established session.
*/
CHIP_ERROR DefaultResubscribePolicy(CHIP_ERROR aTerminationCause);
/**
* Computes the time, in milliseconds, until the next re-subscription over
* an ever increasing window following a fibonacci sequence with the current retry count
* used as input to the fibonacci algorithm.
*
* CHIP_RESUBSCRIBE_MAX_FIBONACCI_STEP_INDEX is the maximum value the retry count can tick up to.
*
*/
uint32_t ComputeTimeTillNextSubscription();
/**
* Schedules a re-subscription aTimeTillNextResubscriptionMs into the future.
*
* If an application wants to set up CASE on their own, they should call ComputeTimeTillNextSubscription() to compute the next
* interval at which they should attempt CASE and attempt CASE at that time. On successful CASE establishment, this method
* should be called with the new SessionHandle provided through 'aNewSessionHandle', 'aTimeTillNextResubscriptionMs' set to 0
* (i.e async, but as soon as possible) and 'aReestablishCASE' set to false.
*
* Otherwise, if aReestablishCASE is true, operational discovery and CASE will be attempted at that time before
* the actual IM interaction is initiated.
*
* aReestablishCASE SHALL NOT be set to true if a valid SessionHandle is provided through newSessionHandle.
*/
CHIP_ERROR ScheduleResubscription(uint32_t aTimeTillNextResubscriptionMs, Optional<SessionHandle> aNewSessionHandle,
bool aReestablishCASE);
// Like SendSubscribeRequest, but allows sending certain forms of invalid
// subscribe requests that servers are expected to reject, for testing
// purposes. Should only be called from tests.
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
CHIP_ERROR SendSubscribeRequestWithoutValidation(const ReadPrepareParams & aReadPrepareParams)
{
return SendSubscribeRequestImpl(aReadPrepareParams);
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
/**
* Override the interval at which liveness of the subscription is assessed.
* By default, this is set set to the max interval of the subscription + ACK timeout of the underlying session.
*
* This can be only be called once a subscription has been established and is active. Once called, this will cancel any existing
* liveness timers and schedule a new one.
*
* This can be called from the Callback::OnSubscriptionEstablished callback.
*
*/
void OverrideLivenessTimeout(System::Clock::Timeout aLivenessTimeout);
private:
friend class TestReadInteraction;
friend class InteractionModelEngine;
enum class ClientState : uint8_t
{
Idle, ///< The client has been initialized and is ready for a SendRequest
AwaitingInitialReport, ///< The client is waiting for initial report
AwaitingSubscribeResponse, ///< The client is waiting for subscribe response
SubscriptionActive, ///< The client is maintaining subscription
};
bool IsMatchingSubscriptionId(SubscriptionId aSubscriptionId)
{
return aSubscriptionId == mSubscriptionId && mInteractionType == InteractionType::Subscribe;
}
CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * apExchangeContext, const PayloadHeader & aPayloadHeader,
System::PacketBufferHandle && aPayload) override;
void OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext) override;
/**
* Check if current read client is being used
*
*/
bool IsIdle() const { return mState == ClientState::Idle; }
bool IsSubscriptionActive() const { return mState == ClientState::SubscriptionActive; }
bool IsAwaitingInitialReport() const { return mState == ClientState::AwaitingInitialReport; }
bool IsAwaitingSubscribeResponse() const { return mState == ClientState::AwaitingSubscribeResponse; }
CHIP_ERROR GenerateEventPaths(EventPathIBs::Builder & aEventPathsBuilder, const Span<EventPathParams> & aEventPaths);
CHIP_ERROR GenerateAttributePaths(AttributePathIBs::Builder & aAttributePathIBsBuilder,
const Span<AttributePathParams> & aAttributePaths);
CHIP_ERROR GenerateDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
const Span<AttributePathParams> & aAttributePaths,
const Span<DataVersionFilter> & aDataVersionFilters, bool & aEncodedDataVersionList);
CHIP_ERROR BuildDataVersionFilterList(DataVersionFilterIBs::Builder & aDataVersionFilterIBsBuilder,
const Span<AttributePathParams> & aAttributePaths,
const Span<DataVersionFilter> & aDataVersionFilters, bool & aEncodedDataVersionList);
CHIP_ERROR ProcessAttributeReportIBs(TLV::TLVReader & aAttributeDataIBsReader);
CHIP_ERROR ProcessEventReportIBs(TLV::TLVReader & aEventReportIBsReader);
static void OnLivenessTimeoutCallback(System::Layer * apSystemLayer, void * apAppState);
CHIP_ERROR ProcessSubscribeResponse(System::PacketBufferHandle && aPayload);
CHIP_ERROR RefreshLivenessCheckTimer();
void CancelLivenessCheckTimer();
void CancelResubscribeTimer();
void MoveToState(const ClientState aTargetState);
CHIP_ERROR ProcessAttributePath(AttributePathIB::Parser & aAttributePath, ConcreteDataAttributePath & aClusterInfo);
CHIP_ERROR ProcessReportData(System::PacketBufferHandle && aPayload);
const char * GetStateStr() const;
/*
* Checks if we should re-subscribe based on the specified re-subscription policy. If we should, re-subscription is scheduled
* aNextResubscribeIntervalMsec is updated accordingly, and true is returned.
*
* If we should not resubscribe, false is returned.
*
* @param[out] aNextResubscribeIntervalMsec How long we will wait before trying to auto-resubscribe.
*/
bool ResubscribeIfNeeded(uint32_t & aNextResubscribeIntervalMsec);
// Specialized request-sending functions.
CHIP_ERROR SendReadRequest(ReadPrepareParams & aReadPrepareParams);
// SendSubscribeRequest performs som validation on aSubscribePrepareParams
// and then calls SendSubscribeRequestImpl.
CHIP_ERROR SendSubscribeRequest(const ReadPrepareParams & aSubscribePrepareParams);
CHIP_ERROR SendSubscribeRequestImpl(const ReadPrepareParams & aSubscribePrepareParams);
void UpdateDataVersionFilters(const ConcreteDataAttributePath & aPath);
static void OnResubscribeTimerCallback(System::Layer * apSystemLayer, void * apAppState);
// Called to ensure OnReportBegin is called before calling OnEventData or OnAttributeData
void NoteReportingData();
/*
* Called internally to signal the completion of all work on this object, gracefully close the
* exchange and finally, signal to the application that it's
* safe to release this object.
*
* If aError != CHIP_NO_ERROR, this will trigger re-subscriptions if allowResubscription is true
* AND if this ReadClient instance is tracking a subscription AND the applications decides to do so
* in their implementation of Callback::OnResubscriptionNeeded().
*
*/
void Close(CHIP_ERROR aError, bool allowResubscription = true);
void StopResubscription();
void ClearActiveSubscriptionState();
static void HandleDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle);
static void HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error);
CHIP_ERROR GetMinEventNumber(const ReadPrepareParams & aReadPrepareParams, Optional<EventNumber> & aEventMin);
Messaging::ExchangeManager * mpExchangeMgr = nullptr;
Messaging::ExchangeHolder mExchange;
Callback & mpCallback;
ClientState mState = ClientState::Idle;
bool mIsReporting = false;
bool mIsInitialReport = true;
// boolean to check if client is waiting for the first priming report
bool mWaitingForFirstPrimingReport = true;
bool mPendingMoreChunks = false;
uint16_t mMinIntervalFloorSeconds = 0;
uint16_t mMaxInterval = 0;
SubscriptionId mSubscriptionId = 0;
ScopedNodeId mPeer;
InteractionType mInteractionType = InteractionType::Read;
Timestamp mEventTimestamp;
bool mDoCaseOnNextResub = true;
chip::Callback::Callback<OnDeviceConnected> mOnConnectedCallback;
chip::Callback::Callback<OnDeviceConnectionFailure> mOnConnectionFailureCallback;
ReadClient * mpNext = nullptr;
InteractionModelEngine * mpImEngine = nullptr;
//
// This stores the params associated with the interaction in a specific set of cases:
// 1. Stores all parameters when used with subscriptions initiated using SendAutoResubscribeRequest.
// 2. Stores just the SessionHolder when used with any subscriptions.
//
ReadPrepareParams mReadPrepareParams;
uint32_t mNumRetries = 0;
System::Clock::Timeout mLivenessTimeoutOverride = System::Clock::kZero;
// End Of Container (0x18) uses one byte.
static constexpr uint16_t kReservedSizeForEndOfContainer = 1;
// Reserved size for the uint8_t InteractionModelRevision flag, which takes up 1 byte for the control tag and 1 byte for the
// context tag, 1 byte for value
static constexpr uint16_t kReservedSizeForIMRevision = 1 + 1 + 1;
// Reserved buffer for TLV level overhead (the overhead for data version filter IBs EndOfContainer, IM reversion end
// of RequestMessage (another end of container)).
static constexpr uint16_t kReservedSizeForTLVEncodingOverhead =
kReservedSizeForEndOfContainer + kReservedSizeForIMRevision + kReservedSizeForEndOfContainer;
};
}; // namespace app
}; // namespace chip