/* * Copyright (c) 2021 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. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #if INET_CONFIG_ENABLE_TCP_ENDPOINT #include #endif // INET_CONFIG_ENABLE_TCP_ENDPOINT namespace chip { namespace Transport { class Session; } // namespace Transport /** @brief * Non-copyable session reference. All SessionHandles are created within SessionManager. It is not allowed to store SessionHandle * anywhere except for function arguments and return values. * * SessionHandle is reference counted such that it is never dangling, but there can be a gray period when the session is marked * as pending removal but not actually removed yet. During this period, the handle is functional, but the underlying session * won't be able to be grabbed by any SessionHolder. SessionHandle->IsActiveSession can be used to check if the session is * active. * * A `SessionHandle` exists to guarantee that the object it points to will not be released while allowing passing * the handle as a reference, to not incur extra reference-counted Retain/Release calls. * * Example code that breaks assumptions (hard to reason about/maintain): * * void Process(ReferenceCountedHandle &handle); * class Foo { * ReferenceCountedHandle mSession; * void ResetSession() { mSession = createNewSession(); } * void DoProcessing() { * Process(mSession); * } * * static Foo& GetInstance(); * }; * * void Process(ReferenceCountedHandle &handle) { * Foo::GetInstance()->ResetSession(); // this changes the passed in handle * // trying to use "&handle" here may point to something else altogether. * } * * Above would be fixed if we would pass in the handles by value, however that adds extra code * to call Retain/Release every time. We could also by design say that passed in references will * not change, however historically the codebase is complex enough that this could not be ensured. * * The end result is the existence of SessionHandle which is NOT allowed to be held and it serves * as a marker of "Retain has been called and stays valid". The code above becomes: * * void Process(SessionHandle &handle); * * .... * void Foo::DoProcessing() { * SessionHandle handle(mSession); // retains the session and mSession can be independently changed * Process(&handle); // reference is now safe to use. * } * * To meet the requirements of "you should not store this", the Handle has additional restrictions * preventing modification (no assignment or copy constructor) and allows only move. * NOTE: `move` should likely also not be allowed, however we need to have the ability to * return such objects from method calls, so it is currently allowed. * */ class SessionHandle { public: SessionHandle(Transport::Session & session) : mSession(session) {} ~SessionHandle() {} SessionHandle(const SessionHandle &) = delete; SessionHandle operator=(const SessionHandle &) = delete; SessionHandle(SessionHandle &&) = default; SessionHandle & operator=(SessionHandle &&) = delete; bool operator==(const SessionHandle & that) const { return &mSession.Get() == &that.mSession.Get(); } Transport::Session * operator->() const { return mSession.operator->(); } private: friend class SessionHolder; ReferenceCountedHandle mSession; }; /** @brief * Managed session reference. The object is used to store a session, the stored session will be automatically * released when the underlying session is released. One must verify it is available before use. The object can be * created using SessionHandle.Grab() */ class SessionHolder : public IntrusiveListNodeBase<> { public: SessionHolder() {} SessionHolder(const SessionHandle & handle) { Grab(handle); } virtual ~SessionHolder(); SessionHolder(const SessionHolder &); SessionHolder(SessionHolder && that); SessionHolder & operator=(const SessionHolder &); SessionHolder & operator=(SessionHolder && that); virtual void SessionReleased() { Release(); } virtual void ShiftToSession(const SessionHandle & session) { Release(); Grab(session); } bool Contains(const SessionHandle & session) const { return mSession.HasValue() && &mSession.Value().Get() == &session.mSession.Get(); } bool GrabPairingSession(const SessionHandle & session); // Should be only used inside CASE/PASE pairing. bool Grab(const SessionHandle & session); void Release(); explicit operator bool() const { return mSession.HasValue(); } Optional Get() const { // // We cannot return mSession directly even if Optional is internally composed of the same bits, // since they are not actually equivalent type-wise, and SessionHandle does not permit copy-construction. // // So, construct a new Optional from the underlying Transport::Session reference. // return mSession.HasValue() ? chip::MakeOptional(mSession.Value().Get()) : chip::Optional::Missing(); } Transport::Session * operator->() const { return &mSession.Value().Get(); } // There is no delegate, nothing to do here virtual void OnSessionHang() {} protected: // Helper for use by the Grab methods. void GrabUnchecked(const SessionHandle & session); Optional> mSession; }; /// @brief Extends SessionHolder to allow propagate SessionDelegate::* events to a given destination class SessionHolderWithDelegate : public SessionHolder { public: SessionHolderWithDelegate(SessionDelegate & delegate) : mDelegate(delegate) {} SessionHolderWithDelegate(const SessionHandle & handle, SessionDelegate & delegate) : SessionHolder(handle), mDelegate(delegate) {} operator bool() const { return SessionHolder::operator bool(); } void SessionReleased() override { Release(); // Note, the session is already cleared during mDelegate.OnSessionReleased mDelegate.OnSessionReleased(); } void ShiftToSession(const SessionHandle & session) override { if (mDelegate.GetNewSessionHandlingPolicy() == SessionDelegate::NewSessionHandlingPolicy::kShiftToNewSession) SessionHolder::ShiftToSession(session); } void OnSessionHang() override { mDelegate.OnSessionHang(); } private: SessionDelegate & mDelegate; }; namespace Transport { class SecureSession; class UnauthenticatedSession; class IncomingGroupSession; class OutgoingGroupSession; class Session { public: virtual ~Session() {} enum class SessionType : uint8_t { kUndefined = 0, kUnauthenticated = 1, kSecure = 2, kGroupIncoming = 3, kGroupOutgoing = 4, }; virtual SessionType GetSessionType() const = 0; void AddHolder(SessionHolder & holder) { assertChipStackLockedByCurrentThread(); VerifyOrDie(!holder.IsInList()); mHolders.PushBack(&holder); } void RemoveHolder(SessionHolder & holder) { assertChipStackLockedByCurrentThread(); VerifyOrDie(mHolders.Contains(&holder)); mHolders.Remove(&holder); } virtual void Retain() = 0; virtual void Release() = 0; virtual bool IsActiveSession() const = 0; virtual ScopedNodeId GetPeer() const = 0; virtual ScopedNodeId GetLocalScopedNodeId() const = 0; virtual Access::SubjectDescriptor GetSubjectDescriptor() const = 0; virtual bool AllowsMRP() const = 0; virtual bool AllowsLargePayload() const = 0; virtual const SessionParameters & GetRemoteSessionParameters() const = 0; virtual System::Clock::Timestamp GetMRPBaseTimeout() const = 0; // Returns true if `subjectDescriptor.IsCommissioning` (based on Core Specification // pseudocode in ACL Architecture chapter) should be true when computing a // subject descriptor for that session. This is only valid to call during // synchronous processing of a message received on the session. virtual bool IsCommissioningSession() const { return false; } // GetAckTimeout is the estimate for how long it could take for the other // side to receive our message (accounting for our MRP retransmits if it // gets lost) and send a response. virtual System::Clock::Milliseconds32 GetAckTimeout() const = 0; // GetReceiptTimeout is the estimate for how long it could take for us to // receive a message after the other side sends it, accounting for the MRP // retransmits the other side might do if the message gets lost. // // The caller is expected to provide an estimate for when the peer would // last have heard from us. The most likely values to pass are // System::SystemClock().GetMonotonicTimestamp() (to indicate "peer is // responding to a message it just received") and System::Clock::kZero (to // indicate "peer is reaching out to us, not in response to anything"). virtual System::Clock::Milliseconds32 GetMessageReceiptTimeout(System::Clock::Timestamp ourLastActivity) const = 0; const ReliableMessageProtocolConfig & GetRemoteMRPConfig() const { return GetRemoteSessionParameters().GetMRPConfig(); } // Returns a suggested timeout value based on the round-trip time it takes for the peer at the other end of the session to // receive a message, process it and send it back. This is computed based on the session type, the type of transport, sleepy // characteristics of the target and a caller-provided value for the time it takes to process a message at the upper layer on // the target For group sessions, this function will always return 0. System::Clock::Timeout ComputeRoundTripTimeout(System::Clock::Timeout upperlayerProcessingTimeout); FabricIndex GetFabricIndex() const { return mFabricIndex; } SecureSession * AsSecureSession(); UnauthenticatedSession * AsUnauthenticatedSession(); IncomingGroupSession * AsIncomingGroupSession(); OutgoingGroupSession * AsOutgoingGroupSession(); bool IsGroupSession() const { return GetSessionType() == SessionType::kGroupIncoming || GetSessionType() == SessionType::kGroupOutgoing; } bool IsSecureSession() const { return GetSessionType() == SessionType::kSecure; } bool IsUnauthenticatedSession() const { return GetSessionType() == SessionType::kUnauthenticated; } #if INET_CONFIG_ENABLE_TCP_ENDPOINT // This API is used to associate the connection with the session when the // latter is about to be marked active. It is also used to reset the // connection to a nullptr when the connection is lost and the session // is marked as Defunct. ActiveTCPConnectionState * GetTCPConnection() const { return mTCPConnection; } void SetTCPConnection(ActiveTCPConnectionState * conn) { mTCPConnection = conn; } #endif // INET_CONFIG_ENABLE_TCP_ENDPOINT void NotifySessionHang() { // Holders might remove themselves when notified. auto holder = mHolders.begin(); while (holder != mHolders.end()) { auto cur = holder; ++holder; cur->OnSessionHang(); } } // Return a session id that is usable for logging. This is the local session // id for secure unicast sessions, 0 for non-secure unicast sessions, and // the group id for group sessions. uint16_t SessionIdForLogging() const; protected: // This should be called by sub-classes at the very beginning of the destructor, before any data field is disposed, such that // the session is still functional during the callback. void NotifySessionReleased() { SessionHandle session(*this); while (!mHolders.Empty()) { mHolders.begin()->SessionReleased(); // SessionReleased must remove the item from the linked list } } void SetFabricIndex(FabricIndex index) { mFabricIndex = index; } const SecureSession * AsConstSecureSession() const; const IncomingGroupSession * AsConstIncomingGroupSession() const; const OutgoingGroupSession * AsConstOutgoingGroupSession() const; IntrusiveList mHolders; private: FabricIndex mFabricIndex = kUndefinedFabricIndex; #if INET_CONFIG_ENABLE_TCP_ENDPOINT // The underlying TCP connection object over which the session is // established. // The lifetime of this member connection pointer is, essentially, the same // as that of the underlying connection with the peer. // It would remain as a nullptr for all sessions that are not set up over // a TCP connection. ActiveTCPConnectionState * mTCPConnection = nullptr; #endif // INET_CONFIG_ENABLE_TCP_ENDPOINT }; // // Return a string representation of the underlying session. // // Always returns a non-null pointer. // const char * GetSessionTypeString(const SessionHandle & session); } // namespace Transport } // namespace chip