/* * * Copyright (c) 2020-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. */ /** * @brief Defines state relevant for an active connection to a peer. */ #pragma once #include #include #include #include #include #include #include #include namespace chip { namespace Transport { class SecureSessionTable; class SecureSessionDeleter { public: static void Release(SecureSession * entry); }; /** * Defines state of a peer connection at a transport layer. * * Information contained within the state: * - SecureSessionType represents CASE or PASE session * - PeerAddress represents how to talk to the peer * - PeerNodeId is the unique ID of the peer * - PeerCATs represents CASE Authenticated Tags * - SendMessageIndex is an ever increasing index for sending messages * - LastActivityTime is a monotonic timestamp of when this connection was * last used. Inactive connections can expire. * - CryptoContext contains the encryption context of a connection */ class SecureSession : public Session, public ReferenceCounted { public: /** * @brief * Defines SecureSession Type. Currently supported types are PASE and CASE. */ enum class Type : uint8_t { kPASE = 1, kCASE = 2, }; // Test-only: inject a session in Active state. // TODO: Tests should allocate a pending session and then call Activate(), just like non-test code does. SecureSession(SecureSessionTable & table, Type secureSessionType, uint16_t localSessionId, NodeId localNodeId, NodeId peerNodeId, CATValues peerCATs, uint16_t peerSessionId, FabricIndex fabric, const ReliableMessageProtocolConfig & config) : mTable(table), mState(State::kEstablishing), mSecureSessionType(secureSessionType), mLocalNodeId(localNodeId), mPeerNodeId(peerNodeId), mPeerCATs(peerCATs), mLocalSessionId(localSessionId), mPeerSessionId(peerSessionId), mRemoteSessionParams(config) { MoveToState(State::kActive); Retain(); // Put the test session in Active state. This ref is released inside MarkForEviction SetFabricIndex(fabric); ChipLogDetail(Inet, "SecureSession[%p]: Allocated for Test Type:%d LSID:%d", this, to_underlying(mSecureSessionType), mLocalSessionId); } /** * @brief * Construct a secure session object to associate with a pending secure * session establishment attempt. The object for the pending session * receives a local session ID, but no other state. */ SecureSession(SecureSessionTable & table, Type secureSessionType, uint16_t localSessionId) : mTable(table), mState(State::kEstablishing), mSecureSessionType(secureSessionType), mLocalSessionId(localSessionId) { ChipLogDetail(Inet, "SecureSession[%p]: Allocated Type:%d LSID:%d", this, to_underlying(mSecureSessionType), mLocalSessionId); } /** * @brief * Activate a pending Secure Session that had been reserved during CASE or * PASE, setting internal state according to the parameters used and * discovered during session establishment. */ void Activate(const ScopedNodeId & localNode, const ScopedNodeId & peerNode, CATValues peerCATs, uint16_t peerSessionId, const SessionParameters & sessionParameters); ~SecureSession() override { ChipLogDetail(Inet, "SecureSession[%p]: Released - Type:%d LSID:%d", this, to_underlying(mSecureSessionType), mLocalSessionId); } SecureSession(SecureSession &&) = delete; SecureSession(const SecureSession &) = delete; SecureSession & operator=(const SecureSession &) = delete; SecureSession & operator=(SecureSession &&) = delete; void Retain() override; void Release() override; bool IsActiveSession() const override { return mState == State::kActive; } bool IsEstablishing() const { return mState == State::kEstablishing; } bool IsPendingEviction() const { return mState == State::kPendingEviction; } bool IsDefunct() const { return mState == State::kDefunct; } const char * GetStateStr() const { return StateToString(mState); } /* * This marks the session for eviction. It will first detach all SessionHolders attached to this * session by calling 'OnSessionReleased' on each of them. This will force them to release their reference * to the session. If there are no more references left, the session will then be de-allocated. * * Once marked for eviction, the session SHALL NOT ever become active again. * */ void MarkForEviction(); /* * This marks a previously active session as defunct to temporarily prevent it from being used with * new exchanges to send or receive messages on this session. This should be called when there is suspicion of * a loss-of-sync with the session state on the associated peer. This could arise if there is evidence * of transport failure. * * If messages are received thereafter on this session, the session SHALL be put back into the Active state. * * This SHALL only be callable on an active session. * This SHALL NOT detach any existing SessionHolders. * */ void MarkAsDefunct(); Session::SessionType GetSessionType() const override { return Session::SessionType::kSecure; } ScopedNodeId GetPeer() const override { return ScopedNodeId(mPeerNodeId, GetFabricIndex()); } ScopedNodeId GetLocalScopedNodeId() const override { return ScopedNodeId(mLocalNodeId, GetFabricIndex()); } Access::SubjectDescriptor GetSubjectDescriptor() const override; bool IsCommissioningSession() const override; bool AllowsMRP() const override { return ((GetPeerAddress().GetTransportType() == Transport::Type::kUdp) || (GetPeerAddress().GetTransportType() == Transport::Type::kWiFiPAF)); } bool AllowsLargePayload() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kTcp; } System::Clock::Milliseconds32 GetAckTimeout() const override { switch (mPeerAddress.GetTransportType()) { case Transport::Type::kUdp: { const ReliableMessageProtocolConfig & remoteMRPConfig = mRemoteSessionParams.GetMRPConfig(); return GetRetransmissionTimeout(remoteMRPConfig.mActiveRetransTimeout, remoteMRPConfig.mIdleRetransTimeout, GetLastPeerActivityTime(), remoteMRPConfig.mActiveThresholdTime); } case Transport::Type::kTcp: return System::Clock::Seconds16(30); case Transport::Type::kBle: return System::Clock::Milliseconds32(BTP_ACK_TIMEOUT_MS); default: break; } return System::Clock::Timeout(); } System::Clock::Milliseconds32 GetMessageReceiptTimeout(System::Clock::Timestamp ourLastActivity) const override { switch (mPeerAddress.GetTransportType()) { case Transport::Type::kUdp: { const auto & maybeLocalMRPConfig = GetLocalMRPConfig(); const auto & defaultMRRPConfig = GetDefaultMRPConfig(); const auto & localMRPConfig = maybeLocalMRPConfig.ValueOr(defaultMRRPConfig); return GetRetransmissionTimeout(localMRPConfig.mActiveRetransTimeout, localMRPConfig.mIdleRetransTimeout, ourLastActivity, localMRPConfig.mActiveThresholdTime); } case Transport::Type::kTcp: return System::Clock::Seconds16(30); case Transport::Type::kBle: return System::Clock::Milliseconds32(BTP_ACK_TIMEOUT_MS); default: break; } return System::Clock::Timeout(); } const PeerAddress & GetPeerAddress() const { return mPeerAddress; } void SetPeerAddress(const PeerAddress & address) { mPeerAddress = address; } Type GetSecureSessionType() const { return mSecureSessionType; } bool IsCASESession() const { return GetSecureSessionType() == Type::kCASE; } bool IsPASESession() const { return GetSecureSessionType() == Type::kPASE; } NodeId GetPeerNodeId() const { return mPeerNodeId; } NodeId GetLocalNodeId() const { return mLocalNodeId; } const CATValues & GetPeerCATs() const { return mPeerCATs; } void SetRemoteSessionParameters(const SessionParameters & sessionParams) { mRemoteSessionParams = sessionParams; } const SessionParameters & GetRemoteSessionParameters() const override { return mRemoteSessionParams; } uint16_t GetLocalSessionId() const { return mLocalSessionId; } uint16_t GetPeerSessionId() const { return mPeerSessionId; } // Called when AddNOC has gone through sufficient success that we need to switch the // session to reflect a new fabric if it was a PASE session CHIP_ERROR AdoptFabricIndex(FabricIndex fabricIndex) { // It's not legal to augment session type for non-PASE if (mSecureSessionType != Type::kPASE) { return CHIP_ERROR_INVALID_ARGUMENT; } SetFabricIndex(fabricIndex); return CHIP_NO_ERROR; } System::Clock::Timestamp GetLastActivityTime() const { return mLastActivityTime; } System::Clock::Timestamp GetLastPeerActivityTime() const { return mLastPeerActivityTime; } void MarkActive() { mLastActivityTime = System::SystemClock().GetMonotonicTimestamp(); } void MarkActiveRx() { mLastPeerActivityTime = System::SystemClock().GetMonotonicTimestamp(); MarkActive(); if (mState == State::kDefunct) { MoveToState(State::kActive); } } void SetCaseCommissioningSessionStatus(bool isCaseCommissioningSession) { VerifyOrDie(GetSecureSessionType() == Type::kCASE); mIsCaseCommissioningSession = isCaseCommissioningSession; } bool IsPeerActive() const { return ((System::SystemClock().GetMonotonicTimestamp() - GetLastPeerActivityTime()) < GetRemoteMRPConfig().mActiveThresholdTime); } System::Clock::Timestamp GetMRPBaseTimeout() const override { return IsPeerActive() ? GetRemoteMRPConfig().mActiveRetransTimeout : GetRemoteMRPConfig().mIdleRetransTimeout; } CryptoContext & GetCryptoContext() { return mCryptoContext; } const CryptoContext & GetCryptoContext() const { return mCryptoContext; } SessionMessageCounter & GetSessionMessageCounter() { return mSessionMessageCounter; } // This should be a private API, only meant to be called by SecureSessionTable // Session holders to this session may shift to the target session regarding SessionDelegate::GetNewSessionHandlingPolicy. // It requires that the target sessoin is also a CASE session, having the same peer and CATs as this session. void NewerSessionAvailable(const SessionHandle & session); private: enum class State : uint8_t { // // Denotes a secure session object that is internally // reserved by the stack before and during session establishment. // // Although the stack can tolerate eviction of these (releasing one // out from under the holder would exhibit as CHIP_ERROR_INCORRECT_STATE // during CASE or PASE), intent is that we should not and would leave // these untouched until CASE or PASE complete. // // In this state, the reference count is held by the PairingSession. // kEstablishing = 1, // // The session is active, ready for use. When transitioning to this state via Activate, the // reference count is incremented by 1, and will subsequently be decremented // by 1 when MarkForEviction is called. This ensures the session remains resident // and active for future use even if there currently are no references to it. // kActive = 2, // // The session is temporarily disabled due to suspicion of a loss of synchronization // with the session state on the peer (e.g transport failure). // In this state, no new outbound exchanges can be created. However, if we receive valid messages // again on this session, we CAN mark this session as being active again. // // Transitioning to this state does not detach any existing SessionHolders. // // In addition to any existing SessionHolders holding a reference to this session, the SessionManager // maintains a reference as well to the session that will only be relinquished when MarkForEviction is called. // kDefunct = 3, // // The session has been marked for eviction and is pending deallocation. All SessionHolders would have already // been detached in a previous call to MarkForEviction. Future SessionHolders will not be able to attach to // this session. // // When all SessionHandles go out of scope, the session will be released automatically. // kPendingEviction = 4, }; const char * StateToString(State state) const; void MoveToState(State targetState); friend class SecureSessionDeleter; friend class TestSecureSessionTable; SecureSessionTable & mTable; State mState; const Type mSecureSessionType; bool mIsCaseCommissioningSession = false; NodeId mLocalNodeId = kUndefinedNodeId; NodeId mPeerNodeId = kUndefinedNodeId; CATValues mPeerCATs = CATValues{}; const uint16_t mLocalSessionId; uint16_t mPeerSessionId = 0; PeerAddress mPeerAddress; /// Timestamp of last tx or rx. @see SessionTimestamp in the spec System::Clock::Timestamp mLastActivityTime = System::SystemClock().GetMonotonicTimestamp(); /// Timestamp of last rx. @see ActiveTimestamp in the spec System::Clock::Timestamp mLastPeerActivityTime = System::SystemClock().GetMonotonicTimestamp(); SessionParameters mRemoteSessionParams; CryptoContext mCryptoContext; SessionMessageCounter mSessionMessageCounter; }; } // namespace Transport } // namespace chip