| /* |
| * |
| * 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 |