Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make use of DataModel::Provider device type resolution in the access control #35862

Merged
merged 12 commits into from
Oct 4, 2024
74 changes: 74 additions & 0 deletions src/app/codegen-data-model-provider/CodegenDataModelProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,21 @@ std::optional<DataModel::CommandEntry> EnumeratorCommandFinder::FindCommandEntry
return (*id == kInvalidCommandId) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom(path, *id);
}

DataModel::DeviceTypeEntry DeviceTypeEntryFromEmber(const EmberAfDeviceType & other)
{
DataModel::DeviceTypeEntry entry;

entry.deviceTypeId = other.deviceId;
entry.deviceTypeVersion = other.deviceVersion;

return entry;
}

bool IsSameDeviceTypeEntry(const DataModel::DeviceTypeEntry & a, const EmberAfDeviceType & b)
{
return (a.deviceTypeId == b.deviceId) && (a.deviceTypeVersion == b.deviceVersion);
}

const ConcreteCommandPath kInvalidCommandPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId);

} // namespace
Expand Down Expand Up @@ -726,6 +741,65 @@ ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand(const Concret
return ConcreteCommandPath(before.mEndpointId, before.mClusterId, *commandId);
}

std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::FirstDeviceType(EndpointId endpoint)
{
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}

CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);

if (deviceTypes.empty())
{
return std::nullopt;
}

// we start at the beginning
mDeviceTypeIterationHint = 0;
return DeviceTypeEntryFromEmber(deviceTypes[0]);
}

std::optional<DataModel::DeviceTypeEntry> CodegenDataModelProvider::NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous)
{
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpoint);
if (!endpoint_index.has_value())
{
return std::nullopt;
}

CHIP_ERROR err = CHIP_NO_ERROR;
Span<const EmberAfDeviceType> deviceTypes = emberAfDeviceTypeListFromEndpointIndex(*endpoint_index, err);

unsigned idx = 0;
if ((mDeviceTypeIterationHint < deviceTypes.size()) && IsSameDeviceTypeEntry(previous, deviceTypes[mDeviceTypeIterationHint]))
{
idx = mDeviceTypeIterationHint + 1; // target the NEXT device
}
else
{
while (idx < deviceTypes.size())
{
idx++;
if (IsSameDeviceTypeEntry(previous, deviceTypes[idx - 1]))
{
break;
}
}
}

if (idx >= deviceTypes.size())
{
return std::nullopt;
}

mDeviceTypeIterationHint = idx;
return DeviceTypeEntryFromEmber(deviceTypes[idx]);
}

bool CodegenDataModelProvider::EventPathIncludesAccessibleConcretePath(const EventPathParams & path,
const Access::SubjectDescriptor & descriptor)
{
Expand Down
11 changes: 8 additions & 3 deletions src/app/codegen-data-model-provider/CodegenDataModelProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider
EndpointId FirstEndpoint() override;
EndpointId NextEndpoint(EndpointId before) override;

std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous) override;

DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;
Expand All @@ -115,9 +119,10 @@ class CodegenDataModelProvider : public chip::app::DataModel::Provider
private:
// Iteration is often done in a tight loop going through all values.
// To avoid N^2 iterations, cache a hint of where something is positioned
uint16_t mEndpointIterationHint = 0;
unsigned mClusterIterationHint = 0;
unsigned mAttributeIterationHint = 0;
uint16_t mEndpointIterationHint = 0;
unsigned mClusterIterationHint = 0;
unsigned mAttributeIterationHint = 0;
unsigned mDeviceTypeIterationHint = 0;
EmberCommandListIterator mAcceptedCommandsIterator;
EmberCommandListIterator mGeneratedCommandsIterator;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include <lib/support/Span.h>
#include <protocols/interaction_model/StatusCode.h>

#include <optional>
#include <vector>

using namespace chip;
Expand All @@ -86,6 +87,15 @@ constexpr EndpointId kEndpointIdThatIsMissing = kMockEndpointMin - 1;

constexpr AttributeId kReadOnlyAttributeId = 0x5001;

constexpr DeviceTypeId kDeviceTypeId1 = 123;
constexpr uint8_t kDeviceTypeId1Version = 10;

constexpr DeviceTypeId kDeviceTypeId2 = 1122;
constexpr uint8_t kDeviceTypeId2Version = 11;

constexpr DeviceTypeId kDeviceTypeId3 = 3;
constexpr uint8_t kDeviceTypeId3Version = 33;

static_assert(kEndpointIdThatIsMissing != kInvalidEndpointId);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint1);
static_assert(kEndpointIdThatIsMissing != kMockEndpoint2);
Expand Down Expand Up @@ -270,6 +280,10 @@ const MockNodeConfig gTestNodeConfig({
MockClusterConfig(MockClusterId(2), {
ClusterRevision::Id, FeatureMap::Id, MockAttributeId(1),
}),
}, {
{ kDeviceTypeId1, kDeviceTypeId1Version},
{ kDeviceTypeId2, kDeviceTypeId2Version},
{ kDeviceTypeId3, kDeviceTypeId3Version},
}),
MockEndpointConfig(kMockEndpoint2, {
MockClusterConfig(MockClusterId(1), {
Expand All @@ -296,6 +310,8 @@ const MockNodeConfig gTestNodeConfig({
{11}, /* acceptedCommands */
{4, 6} /* generatedCommands */
),
}, {
{ kDeviceTypeId2, kDeviceTypeId2Version},
}),
MockEndpointConfig(kMockEndpoint3, {
MockClusterConfig(MockClusterId(1), {
Expand Down Expand Up @@ -2580,3 +2596,48 @@ TEST(TestCodegenModelViaMocks, EmberWriteInvalidDataType)
ASSERT_EQ(model.WriteAttribute(test.GetRequest(), decoder), Status::Failure);
ASSERT_TRUE(model.ChangeListener().DirtyList().empty());
}

TEST(TestCodegenModelViaMocks, DeviceTypeIteration)
{
UseMockNodeConfig config(gTestNodeConfig);
CodegenDataModelProviderWithContext model;

// Mock endpoint 1 has 3 device types
std::optional<DeviceTypeEntry> entry = model.FirstDeviceType(kMockEndpoint1);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_FALSE(entry.has_value());

// Mock endpoint 2 has 1 device types
entry = model.FirstDeviceType(kMockEndpoint2);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version }));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access): Assert above that this is not none
entry = model.NextDeviceType(kMockEndpoint2, *entry);
ASSERT_FALSE(entry.has_value());

// out of order query works
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId2, .deviceTypeVersion = kDeviceTypeId2Version });
entry = model.NextDeviceType(kMockEndpoint1, *entry);
ASSERT_EQ(entry,
std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId3, .deviceTypeVersion = kDeviceTypeId3Version }));

// invalid query fails
entry = std::make_optional(DeviceTypeEntry{ .deviceTypeId = kDeviceTypeId1, .deviceTypeVersion = kDeviceTypeId1Version });
entry = model.NextDeviceType(kMockEndpoint2, *entry);
ASSERT_FALSE(entry.has_value());

// empty endpoint works
entry = model.FirstDeviceType(kMockEndpoint3);
ASSERT_FALSE(entry.has_value());
}
17 changes: 17 additions & 0 deletions src/app/data-model-provider/MetadataTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ struct CommandEntry
static const CommandEntry kInvalid;
};

/// Represents a device type that resides on an endpoint
struct DeviceTypeEntry
{
DeviceTypeId deviceTypeId;
uint8_t deviceTypeVersion;

bool operator==(const DeviceTypeEntry & other) const
{
return (deviceTypeId == other.deviceTypeId) && (deviceTypeVersion == other.deviceTypeVersion);
}
};

/// Provides metadata information for a data model
///
/// The data model can be viewed as a tree of endpoint/cluster/(attribute+commands+events)
Expand All @@ -129,6 +141,11 @@ class ProviderMetadataTree
virtual EndpointId FirstEndpoint() = 0;
virtual EndpointId NextEndpoint(EndpointId before) = 0;

// This iteration describes device types registered on an endpoint
virtual std::optional<DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) = 0;
virtual std::optional<DeviceTypeEntry> NextDeviceType(EndpointId endpoint, const DeviceTypeEntry & previous) = 0;

// This iteration will list all clusters on a given endpoint
virtual ClusterEntry FirstCluster(EndpointId endpoint) = 0;
virtual ClusterEntry NextCluster(const ConcreteClusterPath & before) = 0;
virtual std::optional<ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) = 0;
Expand Down
30 changes: 30 additions & 0 deletions src/app/server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

#include <access/examples/ExampleAccessControlDelegate.h>

#include <app/AppConfig.h>
#include <app/EventManagement.h>
#include <app/InteractionModelEngine.h>
#include <app/data-model-provider/Provider.h>
#include <app/server/Dnssd.h>
#include <app/server/EchoHandler.h>
#include <app/util/DataModelHandler.h>
Expand Down Expand Up @@ -81,6 +83,33 @@ using chip::Transport::TcpListenParameters;

namespace {

#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
class DeviceTypeResolver : public chip::Access::AccessControl::DeviceTypeResolver
{
public:
bool IsDeviceTypeOnEndpoint(chip::DeviceTypeId deviceType, chip::EndpointId endpoint) override
{
chip::app::DataModel::Provider * model = chip::app::InteractionModelEngine::GetInstance()->GetDataModelProvider();

for (auto type = model->FirstDeviceType(endpoint); type.has_value(); type = model->NextDeviceType(endpoint, *type))
{
if (type->deviceTypeId == deviceType)
{
#if CHIP_CONFIG_USE_EMBER_DATA_MODEL
VerifyOrDie(chip::app::IsDeviceTypeOnEndpoint(deviceType, endpoint));
#endif // CHIP_CONFIG_USE_EMBER_DATA_MODEL
return true;
}
}
#if CHIP_CONFIG_USE_EMBER_DATA_MODEL
VerifyOrDie(!chip::app::IsDeviceTypeOnEndpoint(deviceType, endpoint));
#endif // CHIP_CONFIG_USE_EMBER_DATA_MODEL
return false;
}
} sDeviceTypeResolver;
#else // CHIP_CONFIG_USE_DATA_MODEL_INTERFACE

// Ember implementation of the device type resolver
class DeviceTypeResolver : public chip::Access::AccessControl::DeviceTypeResolver
{
public:
Expand All @@ -89,6 +118,7 @@ class DeviceTypeResolver : public chip::Access::AccessControl::DeviceTypeResolve
return chip::app::IsDeviceTypeOnEndpoint(deviceType, endpoint);
}
} sDeviceTypeResolver;
#endif

} // namespace

Expand Down
11 changes: 11 additions & 0 deletions src/app/tests/test-interaction-model-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ EndpointId TestImCustomDataModel::NextEndpoint(EndpointId before)
return CodegenDataModelProviderInstance()->NextEndpoint(before);
}

std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::FirstDeviceType(EndpointId endpoint)
{
return std::nullopt;
}

std::optional<DataModel::DeviceTypeEntry> TestImCustomDataModel::NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous)
{
return std::nullopt;
}

ClusterEntry TestImCustomDataModel::FirstCluster(EndpointId endpoint)
{
return CodegenDataModelProviderInstance()->FirstCluster(endpoint);
Expand Down
3 changes: 3 additions & 0 deletions src/app/tests/test-interaction-model-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class TestImCustomDataModel : public DataModel::Provider

EndpointId FirstEndpoint() override;
EndpointId NextEndpoint(EndpointId before) override;
std::optional<DataModel::DeviceTypeEntry> FirstDeviceType(EndpointId endpoint) override;
std::optional<DataModel::DeviceTypeEntry> NextDeviceType(EndpointId endpoint,
const DataModel::DeviceTypeEntry & previous) override;
DataModel::ClusterEntry FirstCluster(EndpointId endpoint) override;
DataModel::ClusterEntry NextCluster(const ConcreteClusterPath & before) override;
std::optional<DataModel::ClusterInfo> GetClusterInfo(const ConcreteClusterPath & path) override;
Expand Down
8 changes: 5 additions & 3 deletions src/app/util/attribute-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,13 +1007,15 @@ uint8_t emberAfGetClusterCountForEndpoint(EndpointId endpoint)

Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(EndpointId endpoint, CHIP_ERROR & err)
{
uint16_t endpointIndex = emberAfIndexFromEndpoint(endpoint);
Span<const EmberAfDeviceType> ret;
return emberAfDeviceTypeListFromEndpointIndex(emberAfIndexFromEndpoint(endpoint), err);
}

chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err)
{
if (endpointIndex == 0xFFFF)
{
err = CHIP_ERROR_INVALID_ARGUMENT;
return ret;
return Span<const EmberAfDeviceType>();
}

err = CHIP_NO_ERROR;
Expand Down
1 change: 1 addition & 0 deletions src/app/util/attribute-storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ const EmberAfCluster * emberAfGetNthCluster(chip::EndpointId endpoint, uint8_t n
// Retrieve the device type list associated with a specific endpoint.
//
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpoint(chip::EndpointId endpoint, CHIP_ERROR & err);
chip::Span<const EmberAfDeviceType> emberAfDeviceTypeListFromEndpointIndex(unsigned endpointIndex, CHIP_ERROR & err);

//
// Override the device type list current associated with an endpoint with a user-provided list. The buffers backing
Expand Down
11 changes: 8 additions & 3 deletions src/app/util/mock/MockNodeConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
* limitations under the License.
*/

#include "app/util/af-types.h"
#include <app/util/mock/MockNodeConfig.h>

#include <app/util/att-storage.h>
#include <app/util/attribute-storage.h>
#include <initializer_list>
#include <lib/support/CodeUtils.h>

#include <utility>
Expand Down Expand Up @@ -174,8 +176,10 @@ const MockAttributeConfig * MockClusterConfig::attributeById(AttributeId attribu
return findById(attributes, attributeId, outIndex);
}

MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters) :
id(aId), clusters(aClusters), mEmberEndpoint{}
MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<MockClusterConfig> aClusters,
std::initializer_list<EmberAfDeviceType> aDeviceTypes) :
id(aId),
clusters(aClusters), mDeviceTypes(aDeviceTypes), mEmberEndpoint{}
{
VerifyOrDie(aClusters.size() < UINT8_MAX);

Expand All @@ -189,7 +193,8 @@ MockEndpointConfig::MockEndpointConfig(EndpointId aId, std::initializer_list<Moc
}

MockEndpointConfig::MockEndpointConfig(const MockEndpointConfig & other) :
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mEmberEndpoint(other.mEmberEndpoint)
id(other.id), clusters(other.clusters), mEmberClusters(other.mEmberClusters), mDeviceTypes(other.mDeviceTypes),
mEmberEndpoint(other.mEmberEndpoint)
{
// fix self-referencing pointers
mEmberEndpoint.cluster = mEmberClusters.data();
Expand Down
Loading
Loading