blob: 933b23d0ee5012b4b3a82792b139bcb502153599 [file]
/*
* 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.
*/
/**
* @file
* This file defines the Matter Group message counters of remote nodes for groups.
*
*/
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <limits>
#include <transport/GroupPeerMessageCounter.h>
#include <crypto/RandUtils.h>
#include <utility>
namespace chip {
namespace Transport {
namespace {
/// Shift elements right and insert newEntry at front (MRU position).
///
/// All elements [0..oldIndex) are shifted one slot to the right; newEntry
/// becomes list[0] as the most-recently-used entry.
void ShiftAndInsert(GroupSender * list, uint32_t oldIndex, GroupSender && newEntry)
{
for (uint32_t j = oldIndex; j > 0; j--)
{
list[j] = std::move(list[j - 1]);
}
list[0] = std::move(newEntry);
}
/// Find peer by nodeId (move to MRU) or add it; return counter.
///
/// `list` is a list of `maxLimit` items, out of which the first `peerCount` elements are valid.
/// `nodeId` is searched for and added if not found:
/// - if found, it gets moved to `list[0]` as the most recently used element
/// - if not found, it will be inserted at index 0, shifting everything else to the right.
/// peerCount may increase if space exists, otherwise the LRU entry (at maxLimit - 1) will be evicted
/// `peerType` is a label used only in eviction log messages.
///
/// `outCounter` is an OUTPUT value, pointing to the PeerMessageCounter of the GroupSender found or inserted.
CHIP_ERROR FindOrAddPeerFabricFound(GroupSender * list, uint32_t maxLimit, uint8_t & peerCount, NodeId nodeId,
chip::Transport::PeerMessageCounter *& outCounter, const char * peerType)
{
static uint32_t kInvalidIndex = std::numeric_limits<uint32_t>::max();
GroupSender temp;
temp.mNodeId = nodeId;
uint32_t insertPos = kInvalidIndex;
// Search for peer
for (uint32_t i = 0; i < peerCount; i++)
{
if (list[i].mNodeId == nodeId)
{
insertPos = i;
temp = std::move(list[i]);
break;
}
}
if (insertPos == kInvalidIndex)
{
// GroupSender was not found, must add a new one for this node id.
if (peerCount < maxLimit)
{
peerCount++;
}
else
{
// Evict LRU
ChipLogProgress(SecureChannel, "GroupPeerTable: Evicting %s peer " ChipLogFormatX64 " due to table being full",
peerType, ChipLogValueX64(list[maxLimit - 1].mNodeId));
}
insertPos = peerCount - 1;
}
ShiftAndInsert(list, insertPos, std::move(temp));
outCounter = &(list[0].msgCounter);
return CHIP_NO_ERROR;
}
} // anonymous namespace
CHIP_ERROR GroupPeerTable::FindOrAddPeer(FabricIndex fabricIndex, NodeId nodeId, bool isControl,
chip::Transport::PeerMessageCounter *& counter)
{
if (fabricIndex == kUndefinedFabricIndex || nodeId == kUndefinedNodeId)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
for (auto & groupFabric : mGroupFabrics)
{
if (groupFabric.mFabricIndex == kUndefinedFabricIndex)
{
// Already iterated through all known fabricIndex
// Add the new peer to save some processing time
groupFabric.mFabricIndex = fabricIndex;
if (isControl)
{
groupFabric.mControlGroupSenders[0].mNodeId = nodeId;
counter = &(groupFabric.mControlGroupSenders[0].msgCounter);
groupFabric.mControlPeerCount++;
}
else
{
groupFabric.mDataGroupSenders[0].mNodeId = nodeId;
counter = &(groupFabric.mDataGroupSenders[0].msgCounter);
groupFabric.mDataPeerCount++;
}
return CHIP_NO_ERROR;
}
if (fabricIndex == groupFabric.mFabricIndex)
{
if (isControl)
{
return FindOrAddPeerFabricFound(groupFabric.mControlGroupSenders, CHIP_CONFIG_MAX_GROUP_CONTROL_PEERS,
groupFabric.mControlPeerCount, nodeId, counter, "control");
}
return FindOrAddPeerFabricFound(groupFabric.mDataGroupSenders, CHIP_CONFIG_MAX_GROUP_DATA_PEERS,
groupFabric.mDataPeerCount, nodeId, counter, "data");
}
}
// Exceeded the Max number of Group fabrics
return CHIP_ERROR_NO_MEMORY;
}
// Used in case of MCSP failure
CHIP_ERROR GroupPeerTable::RemovePeer(FabricIndex fabricIndex, NodeId nodeId, bool isControl)
{
CHIP_ERROR err = CHIP_ERROR_NOT_FOUND;
uint32_t fabricIt = CHIP_CONFIG_MAX_FABRICS;
if (fabricIndex == kUndefinedFabricIndex || nodeId == kUndefinedNodeId)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
for (uint32_t it = 0; it < CHIP_CONFIG_MAX_FABRICS; it++)
{
if (fabricIndex == mGroupFabrics[it].mFabricIndex)
{
if (isControl)
{
if (RemoveSpecificPeer(mGroupFabrics[it].mControlGroupSenders, nodeId, CHIP_CONFIG_MAX_GROUP_CONTROL_PEERS))
{
fabricIt = it;
mGroupFabrics[it].mControlPeerCount--;
err = CHIP_NO_ERROR;
}
}
else
{
if (RemoveSpecificPeer(mGroupFabrics[it].mDataGroupSenders, nodeId, CHIP_CONFIG_MAX_GROUP_DATA_PEERS))
{
fabricIt = it;
mGroupFabrics[it].mDataPeerCount--;
err = CHIP_NO_ERROR;
}
}
break;
}
}
// Remove Fabric entry from PeerTable if empty
if (fabricIt < CHIP_CONFIG_MAX_FABRICS)
{
if (mGroupFabrics[fabricIt].mDataPeerCount == 0 && mGroupFabrics[fabricIt].mControlPeerCount == 0)
{
RemoveAndCompactFabric(fabricIt);
}
}
// Cannot find Peer to remove
return err;
}
CHIP_ERROR GroupPeerTable::FabricRemoved(FabricIndex fabricIndex)
{
CHIP_ERROR err = CHIP_ERROR_NOT_FOUND;
if (fabricIndex == kUndefinedFabricIndex)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
for (uint32_t it = 0; it < CHIP_CONFIG_MAX_FABRICS; it++)
{
if (fabricIndex == mGroupFabrics[it].mFabricIndex)
{
RemoveAndCompactFabric(it);
return CHIP_NO_ERROR;
}
}
// Cannot find Fabric to remove
return err;
}
bool GroupPeerTable::RemoveSpecificPeer(GroupSender * list, NodeId nodeId, uint32_t size)
{
bool removed = false;
for (uint32_t nodeIt = 0; nodeIt < size; nodeIt++)
{
if (list[nodeIt].mNodeId == nodeId)
{
list[nodeIt].mNodeId = kUndefinedNodeId;
list[nodeIt].msgCounter.Reset();
removed = true;
break;
}
}
if (removed)
{
CompactPeers(list, size);
}
return removed;
}
void GroupPeerTable::CompactPeers(GroupSender * list, uint32_t size)
{
if (list == nullptr || size == 0)
{
return;
}
uint32_t writeIndex = 0;
for (uint32_t readIndex = 0; readIndex < size; readIndex++)
{
// readIndex and writeIndex will iterate together, until a point
// is found where the node id at the read index is kUndefinedNodeId. This
// means the GroupSender entry at this index has been removed (likely by
// RemoveSpecificPeer()). When this removed entry index is found, the read
// index will be 1 ahead of the write index, and then all entries
// that follow will be shifted down the list (moving 1 to the left).
if (list[readIndex].mNodeId != kUndefinedNodeId)
{
if (readIndex != writeIndex)
{
list[writeIndex] = std::move(list[readIndex]);
}
writeIndex++;
}
}
// Cleanup old entries at the end of the list. These were entries that were
// moved further up in the list, and the entries from the writeIndex onwards
// are in an unspecified state.
for (uint32_t i = writeIndex; i < size; i++)
{
list[i] = GroupSender();
}
}
void GroupPeerTable::RemoveAndCompactFabric(uint32_t tableIndex)
{
if (tableIndex >= CHIP_CONFIG_MAX_FABRICS)
{
return;
}
mGroupFabrics[tableIndex].mFabricIndex = kUndefinedFabricIndex;
new (&mGroupFabrics[tableIndex]) GroupFabric();
// To maintain logic integrity Fabric array cannot have empty slot in between data
// Find the last non empty element
for (uint32_t i = CHIP_CONFIG_MAX_FABRICS - 1; i > tableIndex; i--)
{
if (mGroupFabrics[i].mFabricIndex != kUndefinedFabricIndex)
{
// Logic works since all buffer are static
// move it up front
new (&mGroupFabrics[tableIndex]) GroupFabric(mGroupFabrics[i]);
new (&mGroupFabrics[i]) GroupFabric();
break;
}
}
}
GroupOutgoingCounters::GroupOutgoingCounters(chip::PersistentStorageDelegate * storage_delegate)
{
TEMPORARY_RETURN_IGNORED Init(storage_delegate);
}
CHIP_ERROR GroupOutgoingCounters::Init(chip::PersistentStorageDelegate * storage_delegate)
{
if (storage_delegate == nullptr)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Spec 4.5.1.3
mStorage = storage_delegate;
uint16_t size = static_cast<uint16_t>(sizeof(uint32_t));
uint32_t temp;
CHIP_ERROR err;
err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::GroupControlCounter().KeyName(), &temp, size);
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// First time retrieving the counter
mGroupControlCounter = (chip::Crypto::GetRandU32() & kMessageCounterRandomInitMask) + 1;
}
else if (err != CHIP_NO_ERROR)
{
return err;
}
else
{
mGroupControlCounter = temp;
}
err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::GroupDataCounter().KeyName(), &temp, size);
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// First time retrieving the counter
mGroupDataCounter = (chip::Crypto::GetRandU32() & kMessageCounterRandomInitMask) + 1;
}
else if (err != CHIP_NO_ERROR)
{
return err;
}
else
{
mGroupDataCounter = temp;
}
temp = mGroupControlCounter + GROUP_MSG_COUNTER_MIN_INCREMENT;
size = static_cast<uint16_t>(sizeof(temp));
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::GroupControlCounter().KeyName(), &temp, size));
temp = mGroupDataCounter + GROUP_MSG_COUNTER_MIN_INCREMENT;
return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::GroupDataCounter().KeyName(), &temp, size);
}
uint32_t GroupOutgoingCounters::GetCounter(bool isControl)
{
return (isControl) ? mGroupControlCounter : mGroupDataCounter;
}
CHIP_ERROR GroupOutgoingCounters::IncrementCounter(bool isControl)
{
uint32_t temp = 0;
uint16_t size = static_cast<uint16_t>(sizeof(uint32_t));
uint32_t value = 0;
StorageKeyName key = StorageKeyName::Uninitialized();
if (isControl)
{
mGroupControlCounter++;
key = DefaultStorageKeyAllocator::GroupControlCounter();
value = mGroupControlCounter;
}
else
{
mGroupDataCounter++;
key = DefaultStorageKeyAllocator::GroupDataCounter();
value = mGroupDataCounter;
}
if (mStorage == nullptr)
{
return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND;
}
ReturnErrorOnFailure(mStorage->SyncGetKeyValue(key.KeyName(), &temp, size));
if (temp == value)
{
temp = value + GROUP_MSG_COUNTER_MIN_INCREMENT;
return mStorage->SyncSetKeyValue(key.KeyName(), &temp, sizeof(uint32_t));
}
return CHIP_NO_ERROR;
}
} // namespace Transport
} // namespace chip