blob: 267cbc4607e7a711d85c99f731c7bbbee88fcb69 [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.
*/
#include <credentials/GroupDataProvider.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/Pool.h>
#include <stdlib.h>
#include <string.h>
namespace chip {
namespace Credentials {
namespace {
static constexpr size_t kNumFabrics = CHIP_CONFIG_MAX_DEVICE_ADMINS;
static constexpr size_t kEndpointEntriesMax = CHIP_CONFIG_MAX_GROUP_ENDPOINTS_PER_FABRIC;
static constexpr size_t kStateEntriesMax = CHIP_CONFIG_MAX_GROUPS_PER_FABRIC;
static constexpr size_t kKeyEntriesMax = CHIP_CONFIG_MAX_GROUPS_PER_FABRIC;
static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_GROUP_CONCURRENT_ITERATORS;
class StaticGroupsProvider : public GroupDataProvider
{
protected:
struct EndpointEntry : public GroupMapping
{
bool in_use = false;
void Clear()
{
endpoint = 0;
group = 0;
in_use = false;
}
};
struct KeysEntry : public KeySet
{
bool in_use = false;
void Clear()
{
memset(epoch_keys, 0x00, sizeof(epoch_keys));
key_set_index = 0;
num_keys_used = 0;
in_use = false;
}
};
struct Fabric
{
// Whether entry is allocated (true), or free to allocate (false)
bool in_use = false;
// Node-wide fabric index associated with the entry
chip::FabricIndex fabric_index;
// Group to Endpoint mapping for fabric
EndpointEntry endpoints[kEndpointEntriesMax];
// Number of Group states for this fabric
size_t states_count;
// Group key sets for fabric
KeysEntry keys[kKeyEntriesMax];
void Clear()
{
in_use = false;
fabric_index = 0;
states_count = 0;
for (size_t i = 0; i < kEndpointEntriesMax; i++)
{
endpoints[i].Clear();
}
for (size_t i = 0; i < kKeyEntriesMax; i++)
{
keys[i].Clear();
}
}
};
struct StateEntry : public GroupState
{
Fabric * fabric = nullptr;
bool in_use = false;
void Clear()
{
in_use = false;
fabric_index = 0;
group = 0;
key_set_index = 0;
fabric = nullptr;
}
};
class EndpointIterator : public GroupMappingIterator
{
public:
EndpointIterator(StaticGroupsProvider & provider, Fabric * fabric, chip::EndpointId endpoint) :
mProvider(provider), mFabric(fabric), mEndpoint(endpoint)
{}
size_t Count() override
{
size_t count = 0;
for (size_t i = 0; this->mFabric && i < kEndpointEntriesMax; ++i)
{
const EndpointEntry & entry = this->mFabric->endpoints[i];
if (entry.in_use && entry.endpoint == this->mEndpoint)
{
count++;
}
}
return count;
}
bool Next(GroupId & outGroup) override
{
while ((this->mFabric != nullptr) && (this->mIndex < kEndpointEntriesMax))
{
const EndpointEntry & entry = this->mFabric->endpoints[this->mIndex++];
if (entry.in_use && (entry.endpoint == this->mEndpoint))
{
outGroup = entry.group;
return true;
}
}
return false;
}
void Release() override { mProvider.Release(this); }
private:
StaticGroupsProvider & mProvider;
Fabric * mFabric = nullptr;
chip::EndpointId mEndpoint = 0;
uint16_t mIndex = 0;
};
class FabricGroupStateIterator : public GroupStateIterator
{
public:
FabricGroupStateIterator(StaticGroupsProvider & provider, Fabric * fabric) : mProvider(provider), mFabric(fabric) {}
size_t Count() override
{
size_t count = 0;
for (size_t i = 0; i < kMaxNumGroupStates && this->mProvider.mGroupStatesCount; ++i)
{
const StateEntry & entry = this->mProvider.mGroupStates[i];
if (entry.in_use && entry.fabric == mFabric)
{
count++;
}
}
return count;
}
bool Next(GroupState & outEntry) override
{
while ((this->mFabric != nullptr) && (mIndex < kMaxNumGroupStates) && (mIndex < this->mProvider.mGroupStatesCount))
{
const StateEntry & entry = this->mProvider.mGroupStates[mIndex++];
if (entry.in_use && entry.fabric == mFabric)
{
// Iterator has data available, copy the contents of the entry to the output
outEntry = entry;
return true;
}
}
return false;
}
void Release() override { mProvider.Release(this); }
private:
StaticGroupsProvider & mProvider;
Fabric * mFabric = nullptr;
uint16_t mIndex = 0;
};
class AllGroupStateIterator : public GroupStateIterator
{
public:
AllGroupStateIterator(StaticGroupsProvider & provider) : mProvider(provider) {}
size_t Count() override
{
size_t count = 0;
for (size_t i = 0; i < kMaxNumGroupStates && this->mProvider.mGroupStatesCount; ++i)
{
const StateEntry & entry = this->mProvider.mGroupStates[i];
if (entry.in_use)
{
count++;
}
}
return count;
}
bool Next(GroupState & outEntry) override
{
while ((mIndex < kMaxNumGroupStates) && (mIndex < this->mProvider.mGroupStatesCount))
{
const StateEntry & entry = this->mProvider.mGroupStates[mIndex++];
if (entry.in_use)
{
// Iterator has data available, copy the contents of the entry to the output
outEntry = entry;
return true;
}
}
return false;
}
void Release() override { mProvider.Release(this); }
private:
StaticGroupsProvider & mProvider;
uint16_t mIndex = 0;
};
class KeysIterator : public KeySetIterator
{
public:
KeysIterator(StaticGroupsProvider & provider, Fabric * fabric) : mProvider(provider), mFabric(fabric) {}
size_t Count() override
{
size_t count = 0;
for (size_t i = 0; this->mFabric && i < kKeyEntriesMax; ++i)
{
const KeysEntry & entry = this->mFabric->keys[i];
if (entry.in_use)
{
count++;
}
}
return count;
}
bool Next(KeySet & outSet) override
{
while ((this->mFabric != nullptr) && (this->mIndex < kKeyEntriesMax))
{
const KeysEntry & entry = this->mFabric->keys[this->mIndex++];
if (entry.in_use)
{
outSet.key_set_index = entry.key_set_index;
outSet.policy = entry.policy;
outSet.num_keys_used = entry.num_keys_used;
memcpy(outSet.epoch_keys, entry.epoch_keys, sizeof(outSet.epoch_keys));
return true;
}
}
return false;
}
void Release() override { mProvider.Release(this); }
private:
StaticGroupsProvider & mProvider;
Fabric * mFabric = nullptr;
uint16_t mIndex = 0;
};
// Get the fabric-scoped dataset slot for the given `fabric_index`.
// If `allow_allocate` is true, a fabric index not found will mark a slot as allocated
// to that given fabric_index, otherwise only lookup of existing fabric slots is possible.
// If no slot is found matching the `fabric_index`, nullptr is returned.
Fabric * GetFabric(chip::FabricIndex fabric_index, bool allow_allocate)
{
Fabric * fabric = nullptr;
Fabric * unused = nullptr;
for (chip::FabricIndex fabric_slot_idx = 0; fabric_slot_idx < kNumFabrics; fabric_slot_idx++)
{
fabric = &mFabrics[fabric_slot_idx];
if (fabric->in_use)
{
if (fabric->fabric_index == fabric_index)
{
// Fabric in use
return fabric;
}
}
else if (nullptr == unused)
{
// Remember the first unused entry
unused = fabric;
}
}
if (unused && allow_allocate)
{
// Use the first available entry
unused->fabric_index = fabric_index;
unused->in_use = true;
return unused;
}
// Fabric not found, and not allowed to allocate or out of entry
return nullptr;
}
Fabric * GetExistingFabric(chip::FabricIndex fabric_index) { return GetFabric(fabric_index, /* allow_allocate= */ false); }
Fabric * GetExistingFabricOrAllocateNew(chip::FabricIndex fabric_index)
{
return GetFabric(fabric_index, /* allow_allocate= */ true);
}
public:
CHIP_ERROR Init() override
{
// Clear-out all fabric entries
for (size_t i = 0; i < kNumFabrics; ++i)
{
mFabrics[i].Clear();
}
// Clear-out all entries of index mapping.
mGroupStatesCount = 0;
for (size_t i = 0; i < kMaxNumGroupStates; ++i)
{
mGroupStates[i].Clear();
}
mInitialized = true;
return CHIP_NO_ERROR;
}
void Finish() override { mInitialized = false; }
// Endpoints
bool GroupMappingExists(chip::FabricIndex fabric_index, const GroupMapping & mapping) override
{
VerifyOrReturnError(mInitialized, false);
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(nullptr != fabric, false);
for (uint16_t i = 0; fabric && i < kEndpointEntriesMax; ++i)
{
EndpointEntry & entry = fabric->endpoints[i];
if (entry.in_use && (entry == mapping))
{
return true;
}
}
return false;
}
CHIP_ERROR AddGroupMapping(chip::FabricIndex fabric_index, const GroupMapping & mapping, const char * name) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
(void) name; // Unused!
Fabric * fabric = GetExistingFabricOrAllocateNew(fabric_index);
VerifyOrReturnError(nullptr != fabric, CHIP_ERROR_NO_MEMORY);
// Search for existing mapping
for (uint16_t i = 0; i < kEndpointEntriesMax; ++i)
{
EndpointEntry & entry = fabric->endpoints[i];
if (entry.in_use && (entry == mapping))
{
// Duplicated
return CHIP_NO_ERROR;
}
}
// New mapping
for (uint16_t i = 0; i < kEndpointEntriesMax; ++i)
{
EndpointEntry & entry = fabric->endpoints[i];
if (!entry.in_use)
{
entry.group = mapping.group;
entry.endpoint = mapping.endpoint;
entry.in_use = true;
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_NO_MEMORY;
}
CHIP_ERROR RemoveGroupMapping(chip::FabricIndex fabric_index, const GroupMapping & mapping) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Search for existing mapping
for (uint16_t i = 0; fabric && i < kEndpointEntriesMax; ++i)
{
EndpointEntry & entry = fabric->endpoints[i];
if (entry.in_use && (entry == mapping))
{
// Found
entry.in_use = false;
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR RemoveAllGroupMappings(chip::FabricIndex fabric_index) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Remove all mappings from fabric
for (uint16_t i = 0; fabric && i < kEndpointEntriesMax; ++i)
{
fabric->endpoints[i].in_use = false;
}
return CHIP_NO_ERROR;
}
GroupMappingIterator * IterateGroupMappings(chip::FabricIndex fabric_index, EndpointId endpoint) override
{
VerifyOrReturnError(mInitialized, nullptr);
return mEndpointIterators.CreateObject(*this, GetExistingFabric(fabric_index), endpoint);
}
void Release(EndpointIterator * iterator) { mEndpointIterators.ReleaseObject(iterator); }
// States
CHIP_ERROR SetGroupState(size_t state_index, const GroupState & state) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
// Append is "add to one past the end". Further than that is not supported.
VerifyOrReturnError(static_cast<size_t>(state_index) <= mGroupStatesCount, CHIP_ERROR_INVALID_ARGUMENT);
Fabric * fabric = GetExistingFabricOrAllocateNew(state.fabric_index);
VerifyOrReturnError(nullptr != fabric, CHIP_ERROR_NO_MEMORY);
StateEntry & entry = mGroupStates[state_index];
bool appending = static_cast<size_t>(state_index) == mGroupStatesCount;
if (appending)
{
// New entry, append at the end, limiting the number of entries per fabric
VerifyOrReturnError(fabric->states_count < kStateEntriesMax, CHIP_ERROR_NO_MEMORY);
mGroupStatesCount++;
fabric->states_count++;
}
else
{
// Existing entry, avoid overwrite another fabric's entry
VerifyOrReturnError(mGroupStates[state_index].fabric_index == state.fabric_index, CHIP_ERROR_ACCESS_DENIED);
}
GroupState old_entry = entry;
entry.fabric_index = state.fabric_index;
entry.group = state.group;
entry.key_set_index = state.key_set_index;
entry.fabric = fabric;
entry.in_use = true;
if (mListener)
{
mListener->OnGroupStateChanged((appending ? nullptr : &old_entry), &entry);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR GetGroupState(size_t state_index, GroupState & state) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(static_cast<size_t>(state_index) < mGroupStatesCount, CHIP_ERROR_KEY_NOT_FOUND);
StateEntry & entry = mGroupStates[state_index];
// Should not happen that mapped fabric is not allocated!
VerifyOrReturnError(entry.fabric && entry.fabric->in_use && entry.in_use, CHIP_ERROR_INTERNAL);
// Update output mapping entry
state = mGroupStates[state_index];
return CHIP_NO_ERROR;
}
CHIP_ERROR RemoveGroupState(size_t state_index) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(static_cast<size_t>(state_index) < mGroupStatesCount, CHIP_ERROR_KEY_NOT_FOUND);
StateEntry & entry = mGroupStates[state_index];
// Should not happen that mapped fabric is not allocated!
VerifyOrReturnError(entry.fabric && entry.fabric->in_use && entry.fabric->states_count > 0 && entry.in_use,
CHIP_ERROR_INTERNAL);
GroupState old_entry = entry;
// Shift rest of the list up
// Mark entry unused in fabric
entry.fabric->states_count--;
entry.Clear();
--mGroupStatesCount;
size_t num_entries_to_shift = mGroupStatesCount - state_index;
if (num_entries_to_shift > 0)
{
memmove(&mGroupStates[state_index], &mGroupStates[state_index + 1], num_entries_to_shift * sizeof(mGroupStates[0]));
// Invalidate entry one past the end of whole used space of table after shift, if any shift occured.
// This is because the shift left free space at the end of the table which is a copy of the
// very last entry previously there, and this would break free-space checking assumptions if not
// fixed-up that all unused entries at the end are invalid
mGroupStates[mGroupStatesCount].Clear();
}
if (mListener)
{
mListener->OnGroupStateRemoved(&old_entry);
}
return CHIP_NO_ERROR;
}
GroupStateIterator * IterateGroupStates() override
{
VerifyOrReturnError(mInitialized, nullptr);
return mAllStateIterators.CreateObject(*this);
}
GroupStateIterator * IterateGroupStates(chip::FabricIndex fabric_index) override
{
VerifyOrReturnError(mInitialized, nullptr);
return mFabricStateIterators.CreateObject(*this, GetExistingFabric(fabric_index));
}
void Release(FabricGroupStateIterator * iterator) { mFabricStateIterators.ReleaseObject(iterator); }
void Release(AllGroupStateIterator * iterator) { mAllStateIterators.ReleaseObject(iterator); }
// Keys
CHIP_ERROR SetKeySet(chip::FabricIndex fabric_index, uint16_t key_set_index, const KeySet & keys) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
Fabric * fabric = GetExistingFabricOrAllocateNew(fabric_index);
KeysEntry * entry = nullptr;
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Search for existing, or unused entry
for (uint16_t i = 0; fabric && i < kKeyEntriesMax; ++i)
{
if (fabric->keys[i].in_use)
{
if (fabric->keys[i].key_set_index == key_set_index)
{
// Reuse existing entry
entry = &fabric->keys[i];
break;
}
}
else if (!entry)
{
// Unused entry
entry = &fabric->keys[i];
}
}
if (entry)
{
entry->key_set_index = key_set_index;
entry->policy = keys.policy;
entry->num_keys_used = keys.num_keys_used;
memcpy(entry->epoch_keys, keys.epoch_keys, sizeof(keys.epoch_keys[0]) * keys.num_keys_used);
entry->in_use = true;
return CHIP_NO_ERROR;
}
return CHIP_ERROR_NO_MEMORY;
}
CHIP_ERROR GetKeySet(chip::FabricIndex fabric_index, uint16_t key_set_index, KeySet & keys) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Search for existing keys
for (uint16_t i = 0; fabric && i < kKeyEntriesMax; ++i)
{
KeysEntry & entry = fabric->keys[i];
if (entry.in_use && entry.key_set_index == key_set_index)
{
// Found
keys.policy = entry.policy;
keys.num_keys_used = entry.num_keys_used;
memcpy(keys.epoch_keys, entry.epoch_keys, sizeof(keys.epoch_keys[0]) * keys.num_keys_used);
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR RemoveKeySet(chip::FabricIndex fabric_index, uint16_t key_set_index) override
{
VerifyOrReturnError(mInitialized, CHIP_ERROR_INTERNAL);
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Search for existing keys
for (uint16_t i = 0; fabric && i < kKeyEntriesMax; ++i)
{
KeysEntry & entry = fabric->keys[i];
if (entry.in_use && entry.key_set_index == key_set_index)
{
// Found
entry.in_use = false;
return CHIP_NO_ERROR;
}
}
return CHIP_ERROR_KEY_NOT_FOUND;
}
KeySetIterator * IterateKeySets(chip::FabricIndex fabric_index) override
{
VerifyOrReturnError(mInitialized, nullptr);
return mKeyIterators.CreateObject(*this, GetExistingFabric(fabric_index));
}
void Release(KeysIterator * iterator) { return mKeyIterators.ReleaseObject(iterator); }
CHIP_ERROR RemoveFabric(chip::FabricIndex fabric_index) override
{
Fabric * fabric = GetExistingFabric(fabric_index);
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INVALID_FABRIC_ID);
// Remove group states
for (size_t i = 0; i < kMaxNumGroupStates; ++i)
{
if (mGroupStates[i].fabric == fabric)
{
RemoveGroupState(i);
}
}
// Release other resources associated with the fabric entry, such as mapped
// endpoints (i.e. through the Groups cluster) and group key sets, both of which
// live in the `Fabric` data structure of this implementation.
fabric->Clear();
return CHIP_NO_ERROR;
}
CHIP_ERROR Decrypt(PacketHeader packetHeader, PayloadHeader & payloadHeader, System::PacketBufferHandle && msg) override
{
// TODO
return CHIP_NO_ERROR;
}
private:
bool mInitialized = false;
Fabric mFabrics[kNumFabrics];
BitMapObjectPool<KeysIterator, kIteratorsMax> mKeyIterators;
BitMapObjectPool<EndpointIterator, kIteratorsMax> mEndpointIterators;
BitMapObjectPool<FabricGroupStateIterator, kIteratorsMax> mFabricStateIterators;
BitMapObjectPool<AllGroupStateIterator, kIteratorsMax> mAllStateIterators;
static constexpr size_t kMaxNumGroupStates = kNumFabrics * kStateEntriesMax;
StateEntry mGroupStates[kMaxNumGroupStates];
size_t mGroupStatesCount = 0;
};
StaticGroupsProvider gDefaultGroupsProvider;
GroupDataProvider * gGroupsProvider = &gDefaultGroupsProvider;
} // namespace
/**
* Instance getter for the global GroupDataProvider.
*
* Callers have to externally synchronize usage of this function.
*
* @return The global device attestation credentials provider. Assume never null.
*/
GroupDataProvider * GetGroupDataProvider()
{
return gGroupsProvider;
}
/**
* Instance setter for the global GroupDataProvider.
*
* Callers have to externally synchronize usage of this function.
*
* If the `provider` is nullptr, no change is done.
*
* @param[in] provider the GroupDataProvider to start returning with the getter
*/
void SetGroupDataProvider(GroupDataProvider * provider)
{
if (provider)
{
gGroupsProvider = provider;
}
}
} // namespace Credentials
} // namespace chip