17
17
#include < app/codegen-data-model-provider/CodegenDataModelProvider.h>
18
18
19
19
#include < app-common/zap-generated/attribute-type.h>
20
+ #include < app/CommandHandlerInterface.h>
20
21
#include < app/CommandHandlerInterfaceRegistry.h>
22
+ #include < app/ConcreteClusterPath.h>
23
+ #include < app/ConcreteCommandPath.h>
21
24
#include < app/RequiredPrivilege.h>
25
+ #include < app/data-model-provider/MetadataTypes.h>
22
26
#include < app/util/IMClusterCommandHandler.h>
23
27
#include < app/util/attribute-storage.h>
24
28
#include < app/util/endpoint-config-api.h>
29
+ #include < lib/core/CHIPError.h>
25
30
#include < lib/core/DataModelTypes.h>
26
31
27
32
#include < optional>
@@ -31,6 +36,113 @@ namespace chip {
31
36
namespace app {
32
37
namespace {
33
38
39
+ // / Handles going through callback-based enumeration of generated/accepted commands
40
+ // / for CommandHandlerInterface based items.
41
+ // /
42
+ // / Offers the ability to focus on some operation for finding a given
43
+ // / command id:
44
+ // / - FindFirst will return the first found element
45
+ // / - FindExact finds the element with the given id
46
+ // / - FindNext finds the element following the given id
47
+ class EnumeratorCommandFinder
48
+ {
49
+ public:
50
+ using HandlerCallbackFunction = CHIP_ERROR (CommandHandlerInterface::*)(const ConcreteClusterPath &,
51
+ CommandHandlerInterface::CommandIdCallback, void *);
52
+
53
+ enum class Operation
54
+ {
55
+ kFindFirst , // Find the first value in the list
56
+ kFindExact , // Find the given value
57
+ kFindNext // Find the value AFTER this value
58
+ };
59
+
60
+ EnumeratorCommandFinder (HandlerCallbackFunction callback) :
61
+ mCallback (callback), mOperation (Operation::kFindFirst ), mTarget (kInvalidCommandId )
62
+ {}
63
+
64
+ // / Find the given command ID that matches the given operation/path.
65
+ // /
66
+ // / If operation is kFindFirst, then path commandID is ignored. Otherwise it is used as a key to
67
+ // / kFindExact or kFindNext.
68
+ // /
69
+ // / Returns:
70
+ // / - std::nullopt if no command found using the command handler interface
71
+ // / - kInvalidCommandId if the find failed (but command handler interface does provide a list)
72
+ // / - valid id if command handler interface usage succeeds
73
+ std::optional<CommandId> FindCommandId (Operation operation, const ConcreteCommandPath & path);
74
+
75
+ // / Uses FindCommandId to find the given command and loads the command entry data
76
+ std::optional<DataModel::CommandEntry> FindCommandEntry (Operation operation, const ConcreteCommandPath & path);
77
+
78
+ private:
79
+ HandlerCallbackFunction mCallback ;
80
+ Operation mOperation ;
81
+ CommandId mTarget ;
82
+ std::optional<CommandId> mFound = std::nullopt;
83
+
84
+ Loop HandlerCallback (CommandId id)
85
+ {
86
+ switch (mOperation )
87
+ {
88
+ case Operation::kFindFirst :
89
+ mFound = id;
90
+ return Loop::Break;
91
+ case Operation::kFindExact :
92
+ if (mTarget == id)
93
+ {
94
+ mFound = id; // found it
95
+ return Loop::Break;
96
+ }
97
+ break ;
98
+ case Operation::kFindNext :
99
+ if (mTarget == id)
100
+ {
101
+ // Once we found the ID, get the first
102
+ mOperation = Operation::kFindFirst ;
103
+ }
104
+ break ;
105
+ }
106
+ return Loop::Continue; // keep searching
107
+ }
108
+
109
+ static Loop HandlerCallbackFn (CommandId id, void * context)
110
+ {
111
+ auto self = static_cast <EnumeratorCommandFinder *>(context);
112
+ return self->HandlerCallback (id);
113
+ }
114
+ };
115
+
116
+ std::optional<CommandId> EnumeratorCommandFinder::FindCommandId (Operation operation, const ConcreteCommandPath & path)
117
+ {
118
+ mOperation = operation;
119
+ mTarget = path.mCommandId ;
120
+
121
+ CommandHandlerInterface * interface =
122
+ CommandHandlerInterfaceRegistry::Instance ().GetCommandHandler (path.mEndpointId , path.mClusterId );
123
+
124
+ if (interface == nullptr )
125
+ {
126
+ return std::nullopt; // no data: no interface
127
+ }
128
+
129
+ CHIP_ERROR err = (interface->*mCallback )(path, HandlerCallbackFn, this );
130
+ if (err == CHIP_ERROR_NOT_IMPLEMENTED)
131
+ {
132
+ return std::nullopt; // no data provided by the interface
133
+ }
134
+
135
+ if (err != CHIP_NO_ERROR)
136
+ {
137
+ // Report the error here since we lose actual error. This generally should NOT be possible as CommandHandlerInterface
138
+ // usually returns unimplemented or should just work for our use case (our callback never fails)
139
+ ChipLogError (DataManagement, " Enumerate error: %" CHIP_ERROR_FORMAT, err.Format ());
140
+ return kInvalidCommandId ;
141
+ }
142
+
143
+ return mFound .value_or (kInvalidCommandId );
144
+ }
145
+
34
146
// / Load the cluster information into the specified destination
35
147
std::variant<CHIP_ERROR, DataModel::ClusterInfo> LoadClusterInfo (const ConcreteClusterPath & path, const EmberAfCluster & cluster)
36
148
{
@@ -160,6 +272,20 @@ DataModel::CommandEntry CommandEntryFrom(const ConcreteClusterPath & clusterPath
160
272
return entry;
161
273
}
162
274
275
+ std::optional<DataModel::CommandEntry> EnumeratorCommandFinder::FindCommandEntry (Operation operation,
276
+ const ConcreteCommandPath & path)
277
+ {
278
+
279
+ std::optional<CommandId> id = FindCommandId (operation, path);
280
+
281
+ if (!id.has_value ())
282
+ {
283
+ return std::nullopt;
284
+ }
285
+
286
+ return (*id == kInvalidCommandId ) ? DataModel::CommandEntry::kInvalid : CommandEntryFrom (path, *id);
287
+ }
288
+
163
289
const ConcreteCommandPath kInvalidCommandPath (kInvalidEndpointId , kInvalidClusterId , kInvalidCommandId );
164
290
165
291
} // namespace
@@ -492,6 +618,15 @@ std::optional<DataModel::AttributeInfo> CodegenDataModelProvider::GetAttributeIn
492
618
493
619
DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand (const ConcreteClusterPath & path)
494
620
{
621
+ auto handlerInterfaceValue = EnumeratorCommandFinder (&CommandHandlerInterface::EnumerateAcceptedCommands)
622
+ .FindCommandEntry (EnumeratorCommandFinder::Operation::kFindFirst ,
623
+ ConcreteCommandPath (path.mEndpointId , path.mClusterId , kInvalidCommandId ));
624
+
625
+ if (handlerInterfaceValue.has_value ())
626
+ {
627
+ return *handlerInterfaceValue;
628
+ }
629
+
495
630
const EmberAfCluster * cluster = FindServerCluster (path);
496
631
497
632
VerifyOrReturnValue (cluster != nullptr , DataModel::CommandEntry::kInvalid );
@@ -504,6 +639,16 @@ DataModel::CommandEntry CodegenDataModelProvider::FirstAcceptedCommand(const Con
504
639
505
640
DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand (const ConcreteCommandPath & before)
506
641
{
642
+ // TODO: `Next` redirecting to a callback is slow O(n^2).
643
+ // see https://github.com/project-chip/connectedhomeip/issues/35790
644
+ auto handlerInterfaceValue = EnumeratorCommandFinder (&CommandHandlerInterface::EnumerateAcceptedCommands)
645
+ .FindCommandEntry (EnumeratorCommandFinder::Operation::kFindNext , before);
646
+
647
+ if (handlerInterfaceValue.has_value ())
648
+ {
649
+ return *handlerInterfaceValue;
650
+ }
651
+
507
652
const EmberAfCluster * cluster = FindServerCluster (before);
508
653
509
654
VerifyOrReturnValue (cluster != nullptr , DataModel::CommandEntry::kInvalid );
@@ -516,6 +661,14 @@ DataModel::CommandEntry CodegenDataModelProvider::NextAcceptedCommand(const Conc
516
661
517
662
std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedCommandInfo (const ConcreteCommandPath & path)
518
663
{
664
+ auto handlerInterfaceValue = EnumeratorCommandFinder (&CommandHandlerInterface::EnumerateAcceptedCommands)
665
+ .FindCommandEntry (EnumeratorCommandFinder::Operation::kFindExact , path);
666
+
667
+ if (handlerInterfaceValue.has_value ())
668
+ {
669
+ return handlerInterfaceValue->IsValid () ? std::make_optional (handlerInterfaceValue->info ) : std::nullopt;
670
+ }
671
+
519
672
const EmberAfCluster * cluster = FindServerCluster (path);
520
673
521
674
VerifyOrReturnValue (cluster != nullptr , std::nullopt);
@@ -526,17 +679,37 @@ std::optional<DataModel::CommandInfo> CodegenDataModelProvider::GetAcceptedComma
526
679
527
680
ConcreteCommandPath CodegenDataModelProvider::FirstGeneratedCommand (const ConcreteClusterPath & path)
528
681
{
529
- const EmberAfCluster * cluster = FindServerCluster (path);
682
+ std::optional<CommandId> commandId =
683
+ EnumeratorCommandFinder (&CommandHandlerInterface::EnumerateGeneratedCommands)
684
+ .FindCommandId (EnumeratorCommandFinder::Operation::kFindFirst ,
685
+ ConcreteCommandPath (path.mEndpointId , path.mClusterId , kInvalidCommandId ));
686
+ if (commandId.has_value ())
687
+ {
688
+ return *commandId == kInvalidCommandId ? kInvalidCommandPath
689
+ : ConcreteCommandPath (path.mEndpointId , path.mClusterId , *commandId);
690
+ }
530
691
692
+ const EmberAfCluster * cluster = FindServerCluster (path);
531
693
VerifyOrReturnValue (cluster != nullptr , kInvalidCommandPath );
532
694
533
- std::optional<CommandId> commandId = mGeneratedCommandsIterator .First (cluster->generatedCommandList );
695
+ commandId = mGeneratedCommandsIterator .First (cluster->generatedCommandList );
534
696
VerifyOrReturnValue (commandId.has_value (), kInvalidCommandPath );
535
697
return ConcreteCommandPath (path.mEndpointId , path.mClusterId , *commandId);
536
698
}
537
699
538
700
ConcreteCommandPath CodegenDataModelProvider::NextGeneratedCommand (const ConcreteCommandPath & before)
539
701
{
702
+ // TODO: `Next` redirecting to a callback is slow O(n^2).
703
+ // see https://github.com/project-chip/connectedhomeip/issues/35790
704
+ auto nextId = EnumeratorCommandFinder (&CommandHandlerInterface::EnumerateGeneratedCommands)
705
+ .FindCommandId (EnumeratorCommandFinder::Operation::kFindNext , before);
706
+
707
+ if (nextId.has_value ())
708
+ {
709
+ return (*nextId == kInvalidCommandId ) ? kInvalidCommandPath
710
+ : ConcreteCommandPath (before.mEndpointId , before.mClusterId , *nextId);
711
+ }
712
+
540
713
const EmberAfCluster * cluster = FindServerCluster (before);
541
714
542
715
VerifyOrReturnValue (cluster != nullptr , kInvalidCommandPath );
0 commit comments