blob: fe6b2bff612072a5ca11883b4aff18f38caa7ad9 [file] [log] [blame]
/*
*
* Copyright (c) 2020 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 <core/CHIPError.h>
#include <support/CodeUtils.h>
#include <system/TimeSource.h>
#include <transport/AdminPairingTable.h>
#include <transport/PeerConnectionState.h>
namespace chip {
namespace Transport {
// TODO; use 0xffff to match any key id, this is a temporary solution for
// InteractionModel, where key id is not obtainable. This will be removed when
// InteractionModel is migrated to messaging layer
constexpr const uint16_t kAnyKeyId = 0xffff;
/**
* Handles a set of peer connection states.
*
* Intended for:
* - handle connection active time and expiration
* - allocate and free space for connection states.
*/
template <size_t kMaxConnectionCount, Time::Source kTimeSource = Time::Source::kSystem>
class PeerConnections
{
public:
/**
* Allocates a new peer connection state state object out of the internal resource pool.
*
* @param address represents the connection state address
* @param state [out] will contain the connection state if one was available. May be null if no return value is desired.
*
* @note the newly created state will have an 'active' time set based on the current time source.
*
* @returns CHIP_NO_ERROR if state could be initialized. May fail if maximum connection count
* has been reached (with CHIP_ERROR_NO_MEMORY).
*/
CHECK_RETURN_VALUE
CHIP_ERROR CreateNewPeerConnectionState(const PeerAddress & address, PeerConnectionState ** state)
{
CHIP_ERROR err = CHIP_ERROR_NO_MEMORY;
if (state)
{
*state = nullptr;
}
for (size_t i = 0; i < kMaxConnectionCount; i++)
{
if (!mStates[i].IsInitialized())
{
mStates[i] = PeerConnectionState(address);
mStates[i].SetLastActivityTimeMs(mTimeSource.GetCurrentMonotonicTimeMs());
if (state)
{
*state = &mStates[i];
}
err = CHIP_NO_ERROR;
break;
}
}
return err;
}
/**
* Allocates a new peer connection state state object out of the internal resource pool.
*
* @param peerNode represents optional peer Node's ID
* @param peerKeyId represents the encryption key ID assigned by peer node
* @param localKeyId represents the encryption key ID assigned by local node
* @param state [out] will contain the connection state if one was available. May be null if no return value is desired.
*
* @note the newly created state will have an 'active' time set based on the current time source.
*
* @returns CHIP_NO_ERROR if state could be initialized. May fail if maximum connection count
* has been reached (with CHIP_ERROR_NO_MEMORY).
*/
CHECK_RETURN_VALUE
CHIP_ERROR CreateNewPeerConnectionState(const Optional<NodeId> & peerNode, uint16_t peerKeyId, uint16_t localKeyId,
PeerConnectionState ** state)
{
CHIP_ERROR err = CHIP_ERROR_NO_MEMORY;
if (state)
{
*state = nullptr;
}
for (size_t i = 0; i < kMaxConnectionCount; i++)
{
if (!mStates[i].IsInitialized())
{
mStates[i] = PeerConnectionState();
mStates[i].SetPeerKeyID(peerKeyId);
mStates[i].SetLocalKeyID(localKeyId);
mStates[i].SetLastActivityTimeMs(mTimeSource.GetCurrentMonotonicTimeMs());
if (peerNode.ValueOr(kUndefinedNodeId) != kUndefinedNodeId)
{
mStates[i].SetPeerNodeId(peerNode.Value());
}
if (state)
{
*state = &mStates[i];
}
err = CHIP_NO_ERROR;
break;
}
}
return err;
}
/**
* Get a peer connection state given a Peer address.
*
* @param address is the connection to find (based on address)
* @param begin If a member of the pool, will start search from the next item. Can be nullptr to search from start.
*
* @return the state found, nullptr if not found
*/
CHECK_RETURN_VALUE
PeerConnectionState * FindPeerConnectionState(const PeerAddress & address, PeerConnectionState * begin)
{
PeerConnectionState * state = nullptr;
PeerConnectionState * iter = &mStates[0];
if (begin >= iter && begin < &mStates[kMaxConnectionCount])
{
iter = begin + 1;
}
for (; iter < &mStates[kMaxConnectionCount]; iter++)
{
if (iter->GetPeerAddress() == address)
{
state = iter;
break;
}
}
return state;
}
/**
* Get a peer connection state given a Node Id.
*
* @param nodeId is the connection to find (based on nodeId). Note that initial connections
* do not have a node id set. Use this if you know the node id should be set.
* @param begin If a member of the pool, will start search from the next item. Can be nullptr to search from start.
*
* @return the state found, nullptr if not found
*/
CHECK_RETURN_VALUE
PeerConnectionState * FindPeerConnectionState(NodeId nodeId, PeerConnectionState * begin)
{
PeerConnectionState * state = nullptr;
PeerConnectionState * iter = &mStates[0];
if (begin >= iter && begin < &mStates[kMaxConnectionCount])
{
iter = begin + 1;
}
for (; iter < &mStates[kMaxConnectionCount]; iter++)
{
if (!iter->IsInitialized())
{
continue;
}
if (iter->GetPeerNodeId() == nodeId)
{
state = iter;
break;
}
}
return state;
}
/**
* Get a peer connection state given a Node Id and Peer's Encryption Key Id.
*
* @param nodeId is the connection to find (based on nodeId). Note that initial connections
* do not have a node id set. Use this if you know the node id should be set.
* @param peerKeyId Encryption key ID used by the peer node.
* @param begin If a member of the pool, will start search from the next item. Can be nullptr to search from start.
*
* @return the state found, nullptr if not found
*/
CHECK_RETURN_VALUE
PeerConnectionState * FindPeerConnectionState(Optional<NodeId> nodeId, uint16_t peerKeyId, PeerConnectionState * begin)
{
PeerConnectionState * state = nullptr;
PeerConnectionState * iter = &mStates[0];
if (begin >= iter && begin < &mStates[kMaxConnectionCount])
{
iter = begin + 1;
}
for (; iter < &mStates[kMaxConnectionCount]; iter++)
{
if (!iter->IsInitialized())
{
continue;
}
if (peerKeyId == kAnyKeyId || iter->GetPeerKeyID() == peerKeyId)
{
if (nodeId.ValueOr(kUndefinedNodeId) == kUndefinedNodeId || iter->GetPeerNodeId() == kUndefinedNodeId ||
iter->GetPeerNodeId() == nodeId.Value())
{
state = iter;
break;
}
}
}
return state;
}
/**
* Get a peer connection state given the local Encryption Key Id.
*
* @param keyId Encryption key ID assigned by the local node.
* @param begin If a member of the pool, will start search from the next item. Can be nullptr to search from start.
*
* @return the state found, nullptr if not found
*/
CHECK_RETURN_VALUE
PeerConnectionState * FindPeerConnectionState(uint16_t keyId, PeerConnectionState * begin)
{
PeerConnectionState * state = nullptr;
PeerConnectionState * iter = &mStates[0];
assert(begin == nullptr || (begin >= iter && begin < &mStates[kMaxConnectionCount]));
if (begin != nullptr)
{
iter = begin + 1;
}
for (; iter < &mStates[kMaxConnectionCount]; iter++)
{
if (!iter->IsInitialized())
{
continue;
}
if (iter->GetLocalKeyID() == keyId)
{
state = iter;
break;
}
}
return state;
}
/**
* Get a peer connection state given a Node Id and Peer's Encryption Key Id.
*
* @param nodeId is the connection to find (based on peer nodeId). Note that initial connections
* do not have a node id set. Use this if you know the node id should be set.
* @param localKeyId Encryption key ID used by the local node.
* @param begin If a member of the pool, will start search from the next item. Can be nullptr to search from start.
*
* @return the state found, nullptr if not found
*/
CHECK_RETURN_VALUE
PeerConnectionState * FindPeerConnectionStateByLocalKey(Optional<NodeId> nodeId, uint16_t localKeyId,
PeerConnectionState * begin)
{
PeerConnectionState * state = nullptr;
PeerConnectionState * iter = &mStates[0];
if (begin >= iter && begin < &mStates[kMaxConnectionCount])
{
iter = begin + 1;
}
for (; iter < &mStates[kMaxConnectionCount]; iter++)
{
if (!iter->IsInitialized())
{
continue;
}
if (iter->GetLocalKeyID() == localKeyId)
{
if (nodeId.ValueOr(kUndefinedNodeId) == kUndefinedNodeId || iter->GetPeerNodeId() == kUndefinedNodeId ||
iter->GetPeerNodeId() == nodeId.Value())
{
state = iter;
break;
}
}
}
return state;
}
/// Convenience method to mark a peer connection state as active
void MarkConnectionActive(PeerConnectionState * state)
{
state->SetLastActivityTimeMs(mTimeSource.GetCurrentMonotonicTimeMs());
}
/// Convenience method to expired a peer connection state and fired the related callback
template <typename Callback>
void MarkConnectionExpired(PeerConnectionState * state, Callback callback)
{
callback(*state);
*state = PeerConnectionState(PeerAddress::Uninitialized());
}
/**
* Iterates through all active connections and expires any connection with an idle time
* larger than the given amount.
*
* Expiring a connection involves callback execution and then clearing the internal state.
*/
template <typename Callback>
void ExpireInactiveConnections(uint64_t maxIdleTimeMs, Callback callback)
{
const uint64_t currentTime = mTimeSource.GetCurrentMonotonicTimeMs();
for (size_t i = 0; i < kMaxConnectionCount; i++)
{
if (!mStates[i].GetPeerAddress().IsInitialized())
{
continue; // not an active connection
}
uint64_t connectionActiveTime = mStates[i].GetLastActivityTimeMs();
if (connectionActiveTime + maxIdleTimeMs >= currentTime)
{
continue; // not expired
}
MarkConnectionExpired(&mStates[i], callback);
}
}
/// Allows access to the underlying time source used for keeping track of connection active time
Time::TimeSource<kTimeSource> & GetTimeSource() { return mTimeSource; }
private:
Time::TimeSource<kTimeSource> mTimeSource;
PeerConnectionState mStates[kMaxConnectionCount];
};
} // namespace Transport
} // namespace chip