blob: b49d461acb744ccb0a760783bca4d32b2ae71d52 [file] [log] [blame]
/*
* 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 <access/SubjectDescriptor.h>
#include <credentials/FabricTable.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/Optional.h>
#include <lib/core/PeerId.h>
#include <lib/core/ScopedNodeId.h>
#include <lib/support/IntrusiveList.h>
#include <lib/support/ReferenceCountedHandle.h>
#include <messaging/ReliableMessageProtocolConfig.h>
#include <messaging/SessionParameters.h>
#include <platform/LockTracker.h>
#include <transport/SessionDelegate.h>
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
#include <transport/raw/TCP.h>
#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<Session> &handle);
* class Foo {
* ReferenceCountedHandle<Session> mSession;
* void ResetSession() { mSession = createNewSession(); }
* void DoProcessing() {
* Process(mSession);
* }
*
* static Foo& GetInstance();
* };
*
* void Process(ReferenceCountedHandle<Session> &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<Transport::Session> 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<SessionHandle> Get() const
{
//
// We cannot return mSession directly even if Optional<SessionHandle> 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<SessionHandle> from the underlying Transport::Session reference.
//
return mSession.HasValue() ? chip::MakeOptional<SessionHandle>(mSession.Value().Get())
: chip::Optional<SessionHandle>::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<ReferenceCountedHandle<Transport::Session>> 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<SessionHolder> 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