Skip to content

Commit

Permalink
Add ZAP gen for Matter access required privileges
Browse files Browse the repository at this point in the history
ZAPT file iterates over access definitions for app server clusters, for
attributes/commands/events, to generate parallel arrays of custom
privileges for read/write attribute, invoke command, and read event.

These are generated and built per-app, and linked in by the library's
RequiredPrivilege module. Weak implementations provide an empty default
(needed for testing).

Fixes project-chip#14419
  • Loading branch information
mlepage-google committed Mar 16, 2022
1 parent 0c43841 commit e00ab9b
Show file tree
Hide file tree
Showing 29 changed files with 3,837 additions and 117 deletions.
116 changes: 13 additions & 103 deletions src/app/RequiredPrivilege.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,120 +18,30 @@

#include "RequiredPrivilege.h"

#include <app-common/zap-generated/cluster-objects.h>

namespace {

using namespace chip;
using namespace chip::app;
using namespace chip::Access;

// Privilege override entries are stored in a table per operation (read attribute,
// write attribute, invoke command, read entry). Cluster cannot be invalid, but
// endpoint and field can be invalid, which means wildcard. For each cluster,
// more specific entries should be before less specific entries, so they take effect.
struct PrivilegeOverride
{
ClusterId mCluster;
EndpointId mEndpoint;
Privilege mPrivilege; // NOTE: here so packing is tighter
FieldId mField;

constexpr PrivilegeOverride(ClusterId cluster, EndpointId endpoint, FieldId field, Privilege privilege) :
mCluster(cluster), mEndpoint(endpoint), mPrivilege(privilege), mField(field)
{}

static_assert(sizeof(FieldId) >= sizeof(AttributeId), "FieldId must be able to hold AttributeId");
static_assert(sizeof(FieldId) >= sizeof(CommandId), "FieldId must be able to hold CommandId");
static_assert(sizeof(FieldId) >= sizeof(EventId), "FieldId must be able to hold EventId");
};

// WARNING: for each cluster, put more specific entries before less specific entries
constexpr PrivilegeOverride kPrivilegeOverrideForReadAttribute[] = {
PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister),
};

// WARNING: for each cluster, put more specific entries before less specific entries
constexpr PrivilegeOverride kPrivilegeOverrideForWriteAttribute[] = {
PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister),
};

// WARNING: for each cluster, put more specific entries before less specific entries
constexpr PrivilegeOverride kPrivilegeOverrideForInvokeCommand[] = {
PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister),
};

// WARNING: for each cluster, put more specific entries before less specific entries
constexpr PrivilegeOverride kPrivilegeOverrideForReadEvent[] = {
PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister),
};

enum class Operation
{
kReadAttribute = 0,
kWriteAttribute = 1,
kInvokeCommand = 2,
kReadEvent = 3
};

constexpr Privilege kDefaultPrivilege[] = {
Privilege::kView, // for read attribute
Privilege::kOperate, // for write attribute
Privilege::kOperate, // for invoke command
Privilege::kView // for read event
};

const PrivilegeOverride * const kPrivilegeOverride[] = { kPrivilegeOverrideForReadAttribute, kPrivilegeOverrideForWriteAttribute,
kPrivilegeOverrideForInvokeCommand, kPrivilegeOverrideForReadEvent };

constexpr size_t kNumPrivilegeOverride[] = { ArraySize(kPrivilegeOverrideForReadAttribute),
ArraySize(kPrivilegeOverrideForWriteAttribute),
ArraySize(kPrivilegeOverrideForInvokeCommand),
ArraySize(kPrivilegeOverrideForReadEvent) };

Privilege GetRequiredPrivilege(Operation operation, ClusterId cluster, EndpointId endpoint, FieldId field)
{
VerifyOrDie(cluster != kInvalidClusterId && endpoint != kInvalidEndpointId && field != kInvalidFieldId);

const auto * const pStart = kPrivilegeOverride[static_cast<int>(operation)];
const auto * const pEnd = pStart + kNumPrivilegeOverride[static_cast<int>(operation)];

for (const auto * p = pStart; p < pEnd; ++p)
{
if (p->mCluster == cluster && (p->mEndpoint == endpoint || p->mEndpoint == kInvalidEndpointId) &&
(p->mField == field || p->mField == kInvalidFieldId))
{
return p->mPrivilege;
}
}

return kDefaultPrivilege[static_cast<int>(operation)];
}

} // namespace

namespace chip {
namespace app {

Privilege RequiredPrivilege::ForReadAttribute(const ConcreteAttributePath & path)
constexpr Access::Privilege RequiredPrivilege::kPrivilegeMapper[];

} // namespace app
} // namespace chip

int __attribute__((weak)) MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute)
{
return GetRequiredPrivilege(Operation::kReadAttribute, path.mClusterId, path.mEndpointId, path.mAttributeId);
return kMatterAccessPrivilegeView;
}

Privilege RequiredPrivilege::ForWriteAttribute(const ConcreteAttributePath & path)
int __attribute__((weak)) MatterGetAccessPrivilegeForWriteAttribute(chip::ClusterId cluster, chip::AttributeId attribute)
{
return GetRequiredPrivilege(Operation::kWriteAttribute, path.mClusterId, path.mEndpointId, path.mAttributeId);
return kMatterAccessPrivilegeOperate;
}

Privilege RequiredPrivilege::ForInvokeCommand(const ConcreteCommandPath & path)
int __attribute__((weak)) MatterGetAccessPrivilegeForInvokeCommand(chip::ClusterId cluster, chip::CommandId command)
{
return GetRequiredPrivilege(Operation::kInvokeCommand, path.mClusterId, path.mEndpointId, path.mCommandId);
return kMatterAccessPrivilegeOperate;
}

Privilege RequiredPrivilege::ForReadEvent(const ConcreteEventPath & path)
int __attribute__((weak)) MatterGetAccessPrivilegeForReadEvent(chip::ClusterId cluster, chip::EventId event)
{
return GetRequiredPrivilege(Operation::kReadEvent, path.mClusterId, path.mEndpointId, path.mEventId);
return kMatterAccessPrivilegeView;
}

} // namespace app
} // namespace chip
44 changes: 38 additions & 6 deletions src/app/RequiredPrivilege.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "ConcreteCommandPath.h"
#include "ConcreteEventPath.h"

#include <app/util/privilege-storage.h>

#include <access/Privilege.h>

#include <lib/core/CHIPCore.h>
Expand All @@ -30,17 +32,47 @@
namespace chip {
namespace app {

// This functionality is intended to come from Ember, but until Ember supports it,
// this class will provide a workable alternative.
class RequiredPrivilege
{
using Privilege = Access::Privilege;

static constexpr Privilege kPrivilegeMapper[] = { Privilege::kView, Privilege::kOperate, Privilege::kManage,
Privilege::kAdminister };

static_assert(ArraySize(kPrivilegeMapper) > kMatterAccessPrivilegeView &&
kPrivilegeMapper[kMatterAccessPrivilegeView] == Privilege::kView,
"Must map privilege correctly");
static_assert(ArraySize(kPrivilegeMapper) > kMatterAccessPrivilegeOperate &&
kPrivilegeMapper[kMatterAccessPrivilegeOperate] == Privilege::kOperate,
"Must map privilege correctly");
static_assert(ArraySize(kPrivilegeMapper) > kMatterAccessPrivilegeManage &&
kPrivilegeMapper[kMatterAccessPrivilegeManage] == Privilege::kManage,
"Must map privilege correctly");
static_assert(ArraySize(kPrivilegeMapper) > kMatterAccessPrivilegeAdminister &&
kPrivilegeMapper[kMatterAccessPrivilegeAdminister] == Privilege::kAdminister,
"Must map privilege correctly");
static_assert(ArraySize(kPrivilegeMapper) > kMatterAccessPrivilegeMaxValue, "Must map all privileges");

public:
static Privilege ForReadAttribute(const ConcreteAttributePath & path);
static Privilege ForWriteAttribute(const ConcreteAttributePath & path);
static Privilege ForInvokeCommand(const ConcreteCommandPath & path);
static Privilege ForReadEvent(const ConcreteEventPath & path);
static Privilege ForReadAttribute(const ConcreteAttributePath & path)
{
return kPrivilegeMapper[MatterGetAccessPrivilegeForReadAttribute(path.mClusterId, path.mAttributeId)];
}

static Privilege ForWriteAttribute(const ConcreteAttributePath & path)
{
return kPrivilegeMapper[MatterGetAccessPrivilegeForWriteAttribute(path.mClusterId, path.mAttributeId)];
}

static Privilege ForInvokeCommand(const ConcreteCommandPath & path)
{
return kPrivilegeMapper[MatterGetAccessPrivilegeForInvokeCommand(path.mClusterId, path.mCommandId)];
}

static Privilege ForReadEvent(const ConcreteEventPath & path)
{
return kPrivilegeMapper[MatterGetAccessPrivilegeForReadEvent(path.mClusterId, path.mEventId)];
}
};

} // namespace app
Expand Down
1 change: 1 addition & 0 deletions src/app/chip_data_model.gni
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ template("chip_data_model") {
"${_app_root}/util/ember-print.cpp",
"${_app_root}/util/error-mapping.cpp",
"${_app_root}/util/message.cpp",
"${_app_root}/util/privilege-storage.cpp",
"${_app_root}/util/util.cpp",
"${chip_root}/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp",
]
Expand Down
142 changes: 142 additions & 0 deletions src/app/util/privilege-storage.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
*
* Copyright (c) 2022 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 "privilege-storage.h"

#include <zap-generated/access.h>

#include <lib/support/CodeUtils.h>

#include <cstdint>

using chip::AttributeId;
using chip::ClusterId;
using chip::CommandId;
using chip::EventId;

namespace {

static_assert(GENERATED_ACCESS_PRIVILEGE__VIEW == kMatterAccessPrivilegeView,
"Generated privilege value must match privilege value in code");
static_assert(GENERATED_ACCESS_PRIVILEGE__OPERATE == kMatterAccessPrivilegeOperate,
"Generated privilege value must match privilege value in code");
static_assert(GENERATED_ACCESS_PRIVILEGE__MANAGE == kMatterAccessPrivilegeManage,
"Generated privilege value must match privilege value in code");
static_assert(GENERATED_ACCESS_PRIVILEGE__ADMINISTER == kMatterAccessPrivilegeAdminister,
"Generated privilege value must match privilege value in code");

#ifdef GENERATED_ACCESS_READ_ATTRIBUTE__CLUSTER
namespace GeneratedAccessReadAttribute {
constexpr ClusterId kCluster[] = GENERATED_ACCESS_READ_ATTRIBUTE__CLUSTER;
constexpr AttributeId kAttribute[] = GENERATED_ACCESS_READ_ATTRIBUTE__ATTRIBUTE;
constexpr uint8_t kPrivilege[] = GENERATED_ACCESS_READ_ATTRIBUTE__PRIVILEGE;
static_assert(ArraySize(kCluster) == ArraySize(kAttribute) && ArraySize(kAttribute) == ArraySize(kPrivilege),
"Generated parallel arrays must be same size");
} // namespace GeneratedAccessReadAttribute
#else
#error "Undefined generated access for read attribute"
#endif

#ifdef GENERATED_ACCESS_WRITE_ATTRIBUTE__CLUSTER
namespace GeneratedAccessWriteAttribute {
constexpr ClusterId kCluster[] = GENERATED_ACCESS_READ_ATTRIBUTE__CLUSTER;
constexpr AttributeId kAttribute[] = GENERATED_ACCESS_READ_ATTRIBUTE__ATTRIBUTE;
constexpr uint8_t kPrivilege[] = GENERATED_ACCESS_READ_ATTRIBUTE__PRIVILEGE;
static_assert(ArraySize(kCluster) == ArraySize(kAttribute) && ArraySize(kAttribute) == ArraySize(kPrivilege),
"Generated parallel arrays must be same size");
} // namespace GeneratedAccessWriteAttribute
#else
#error "Undefined generated access for read attribute"
#endif

#ifdef GENERATED_ACCESS_INVOKE_COMMAND__CLUSTER
namespace GeneratedAccessInvokeCommand {
constexpr ClusterId kCluster[] = GENERATED_ACCESS_INVOKE_COMMAND__CLUSTER;
constexpr CommandId kCommand[] = GENERATED_ACCESS_INVOKE_COMMAND__COMMAND;
constexpr uint8_t kPrivilege[] = GENERATED_ACCESS_INVOKE_COMMAND__PRIVILEGE;
static_assert(ArraySize(kCluster) == ArraySize(kCommand) && ArraySize(kCommand) == ArraySize(kPrivilege),
"Generated parallel arrays must be same size");
} // namespace GeneratedAccessInvokeCommand
#else
#error "Undefined generated access for read attribute"
#endif

#ifdef GENERATED_ACCESS_READ_EVENT__CLUSTER
namespace GeneratedAccessReadEvent {
constexpr ClusterId kCluster[] = GENERATED_ACCESS_READ_EVENT__CLUSTER;
constexpr EventId kEvent[] = GENERATED_ACCESS_READ_EVENT__EVENT;
constexpr uint8_t kPrivilege[] = GENERATED_ACCESS_READ_EVENT__PRIVILEGE;
static_assert(ArraySize(kCluster) == ArraySize(kEvent) && ArraySize(kEvent) == ArraySize(kPrivilege),
"Generated parallel arrays must be same size");
} // namespace GeneratedAccessReadEvent
#else
#error "Undefined generated access for read attribute"
#endif

} // anonymous namespace

int MatterGetAccessPrivilegeForReadAttribute(ClusterId cluster, AttributeId attribute)
{
using namespace GeneratedAccessReadAttribute;
for (size_t i = 0; i < ArraySize(kCluster); ++i)
{
if (kCluster[i] == cluster && kAttribute[i] == attribute)
{
return kPrivilege[i];
}
}
return GENERATED_ACCESS_PRIVILEGE__VIEW;
}

int MatterGetAccessPrivilegeForWriteAttribute(ClusterId cluster, AttributeId attribute)
{
using namespace GeneratedAccessWriteAttribute;
for (size_t i = 0; i < ArraySize(kCluster); ++i)
{
if (kCluster[i] == cluster && kAttribute[i] == attribute)
{
return kPrivilege[i];
}
}
return GENERATED_ACCESS_PRIVILEGE__OPERATE;
}

int MatterGetAccessPrivilegeForInvokeCommand(ClusterId cluster, CommandId command)
{
using namespace GeneratedAccessInvokeCommand;
for (size_t i = 0; i < ArraySize(kCluster); ++i)
{
if (kCluster[i] == cluster && kCommand[i] == command)
{
return kPrivilege[i];
}
}
return GENERATED_ACCESS_PRIVILEGE__OPERATE;
}

int MatterGetAccessPrivilegeForReadEvent(ClusterId cluster, EventId event)
{
using namespace GeneratedAccessReadEvent;
for (size_t i = 0; i < ArraySize(kCluster); ++i)
{
if (kCluster[i] == cluster && kEvent[i] == event)
{
return kPrivilege[i];
}
}
return GENERATED_ACCESS_PRIVILEGE__VIEW;
}
30 changes: 30 additions & 0 deletions src/app/util/privilege-storage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
*
* Copyright (c) 2022 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 <lib/core/DataModelTypes.h>

constexpr int kMatterAccessPrivilegeView = 0;
constexpr int kMatterAccessPrivilegeOperate = 1;
constexpr int kMatterAccessPrivilegeManage = 2;
constexpr int kMatterAccessPrivilegeAdminister = 3;
constexpr int kMatterAccessPrivilegeMaxValue = kMatterAccessPrivilegeAdminister;

int MatterGetAccessPrivilegeForReadAttribute(chip::ClusterId cluster, chip::AttributeId attribute);
int MatterGetAccessPrivilegeForWriteAttribute(chip::ClusterId cluster, chip::AttributeId attribute);
int MatterGetAccessPrivilegeForInvokeCommand(chip::ClusterId cluster, chip::CommandId command);
int MatterGetAccessPrivilegeForReadEvent(chip::ClusterId cluster, chip::EventId event);
5 changes: 5 additions & 0 deletions src/app/zap-templates/app-templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@
"path": "templates/app/MatterIDL.zapt",
"name": "Human-readable Matter IDL",
"output": "Clusters.matter"
},
{
"path": "templates/app/access.zapt",
"name": "Matter access definitions",
"output": "access.h"
}
]
}
Loading

0 comments on commit e00ab9b

Please sign in to comment.