rs_matter/dm/clusters/
groups.rs1use core::num::NonZeroU8;
21
22use crate::dm::{Cluster, Dataver, InvokeContext, ReadContext};
23use crate::error::{Error, ErrorCode};
24use crate::fabric::FabricPersist;
25use crate::im::encoding::IMStatusCode;
26use crate::tlv::{Nullable, TLVBuilderParent};
27use crate::{with, MatterState};
28
29pub use crate::dm::clusters::decl::groups::*;
30
31#[derive(Debug, Clone)]
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36pub struct GroupsHandler {
37 dataver: Dataver,
38}
39
40impl GroupsHandler {
41 pub const fn new(dataver: Dataver) -> Self {
46 Self { dataver }
47 }
48
49 pub const fn adapt(self) -> HandlerAdaptor<Self> {
51 HandlerAdaptor(self)
52 }
53
54 fn has_group_material(
56 state: &mut MatterState,
57 fab_idx: NonZeroU8,
58 group_id: u16,
59 ) -> Result<bool, Error> {
60 let fabric = state.fabrics.fabric(fab_idx)?;
61
62 let result = fabric
63 .groups()
64 .key_map_iter()
65 .any(|entry| entry.group_id == group_id);
66
67 Ok(result)
68 }
69}
70
71impl ClusterHandler for GroupsHandler {
72 const CLUSTER: Cluster<'static> = FULL_CLUSTER
73 .with_features(Feature::GROUP_NAMES.bits())
74 .with_attrs(with!(required));
75
76 fn dataver(&self) -> u32 {
77 self.dataver.get()
78 }
79
80 fn dataver_changed(&self) {
81 self.dataver.changed();
82 }
83
84 fn name_support(&self, _ctx: impl ReadContext) -> Result<NameSupportBitmap, Error> {
85 Ok(NameSupportBitmap::GROUP_NAMES)
87 }
88
89 fn handle_add_group<P: TLVBuilderParent>(
90 &self,
91 ctx: impl InvokeContext,
92 request: AddGroupRequest<'_>,
93 response: AddGroupResponseBuilder<P>,
94 ) -> Result<P, Error> {
95 let fab_idx = ctx.exchange().accessor()?.fab_idx()?;
96 let group_id = request.group_id()?;
97 let group_name: &str = request.group_name()?;
98
99 if (group_id == 0) || (group_name.len() > 16) {
101 return response
102 .status(IMStatusCode::ConstraintError as u8)?
103 .group_id(group_id)?
104 .end();
105 }
106
107 let mut persist = FabricPersist::new(ctx.kv());
108
109 let status = ctx.exchange().with_state(|state| {
110 if !Self::has_group_material(state, fab_idx, group_id)? {
112 return Ok(IMStatusCode::UnsupportedAccess);
113 }
114
115 let endpoint_id = ctx.cmd().endpoint_id;
117 let fabric = state.fabrics.fabric_mut(fab_idx)?;
118
119 match fabric.groups_mut().add(endpoint_id, group_id, group_name) {
120 Ok(_) => {
121 if !state.failsafe.is_armed_for(fab_idx.get()) {
125 persist.store(fabric)?;
126 }
127
128 ctx.exchange().matter().transport().notify_groups_changed();
129
130 Ok(IMStatusCode::Success)
131 }
132 Err(e) if e.code() == ErrorCode::ResourceExhausted => {
133 Ok(IMStatusCode::ResourceExhausted)
134 }
135 Err(e) => Err(e)?,
136 }
137 })?;
138
139 persist.run()?;
140
141 response.status(status as u8)?.group_id(group_id)?.end()
142 }
143
144 fn handle_view_group<P: TLVBuilderParent>(
145 &self,
146 ctx: impl InvokeContext,
147 request: ViewGroupRequest<'_>,
148 response: ViewGroupResponseBuilder<P>,
149 ) -> Result<P, Error> {
150 let fab_idx = ctx.exchange().accessor()?.fab_idx()?;
151 let group_id = request.group_id()?;
152
153 if group_id == 0 {
155 return response
156 .status(IMStatusCode::ConstraintError as u8)?
157 .group_id(group_id)?
158 .group_name("")?
159 .end();
160 }
161
162 ctx.exchange().with_state(|state| {
163 let fabric = state.fabrics.fabric(fab_idx)?;
165
166 let endpoint_id = ctx.cmd().endpoint_id;
167 if let Some(entry) = fabric.groups().get(group_id) {
168 if entry.endpoints.contains(&endpoint_id) {
169 return response
170 .status(IMStatusCode::Success as u8)?
171 .group_id(group_id)?
172 .group_name(entry.group_name.as_str())?
173 .end();
174 }
175 }
176
177 response
178 .status(IMStatusCode::NotFound as u8)?
179 .group_id(group_id)?
180 .group_name("")?
181 .end()
182 })
183 }
184
185 fn handle_get_group_membership<P: TLVBuilderParent>(
186 &self,
187 ctx: impl InvokeContext,
188 request: GetGroupMembershipRequest<'_>,
189 response: GetGroupMembershipResponseBuilder<P>,
190 ) -> Result<P, Error> {
191 let fab_idx = ctx.exchange().accessor()?.fab_idx()?;
192 let request_group_list = request.group_list()?;
193
194 ctx.exchange().with_state(|state| {
195 let fabric = state.fabrics.fabric(fab_idx)?;
196
197 let capacity = Nullable::<u8>::none();
199
200 let endpoint_id = ctx.cmd().endpoint_id;
201 let mut group_list = response.capacity(capacity)?.group_list()?;
202
203 if request_group_list.iter().count() == 0 {
204 for entry in fabric.groups().iter() {
206 if entry.endpoints.contains(&endpoint_id) {
207 group_list = group_list.push(&entry.group_id)?;
208 }
209 }
210 } else {
211 for gid in request_group_list.into_iter().flatten() {
213 if let Some(entry) = fabric.groups().get(gid) {
214 if entry.endpoints.contains(&endpoint_id) {
215 group_list = group_list.push(&gid)?;
216 }
217 }
218 }
219 }
220
221 group_list.end()?.end()
222 })
223 }
224
225 fn handle_remove_group<P: TLVBuilderParent>(
226 &self,
227 ctx: impl InvokeContext,
228 request: RemoveGroupRequest<'_>,
229 response: RemoveGroupResponseBuilder<P>,
230 ) -> Result<P, Error> {
231 let fab_idx = ctx.exchange().accessor()?.fab_idx()?;
232 let group_id = request.group_id()?;
233 let endpoint_id = ctx.cmd().endpoint_id;
234
235 let mut persist = FabricPersist::new(ctx.kv());
236
237 let status = ctx.exchange().with_state(|state| {
238 if group_id == 0 {
240 return Ok(IMStatusCode::ConstraintError);
241 }
242
243 let fabric = state.fabrics.fabric_mut(fab_idx)?;
244
245 if fabric.groups_mut().remove(endpoint_id, Some(group_id)) {
247 if !state.failsafe.is_armed_for(fab_idx.get()) {
251 persist.store(fabric)?;
252 }
253
254 ctx.exchange().matter().transport().notify_groups_changed();
255
256 Ok(IMStatusCode::Success)
257 } else {
258 Ok(IMStatusCode::NotFound)
259 }
260 })?;
261
262 persist.run()?;
263
264 response.status(status as u8)?.group_id(group_id)?.end()
265 }
266
267 fn handle_remove_all_groups(&self, ctx: impl InvokeContext) -> Result<(), Error> {
268 let fab_idx = ctx.exchange().accessor()?.fab_idx()?;
269 let endpoint_id = ctx.cmd().endpoint_id;
270
271 let mut persist = FabricPersist::new(ctx.kv());
272
273 ctx.exchange().with_state(|state| {
274 let fabric = state.fabrics.fabric_mut(fab_idx)?;
275
276 fabric.groups_mut().remove(endpoint_id, None);
277
278 if !state.failsafe.is_armed_for(fab_idx.get()) {
282 persist.store(fabric)?;
283 }
284
285 ctx.exchange().matter().transport().notify_groups_changed();
286
287 Ok(())
288 })?;
289
290 persist.run()?;
291
292 Ok(())
293 }
294
295 fn handle_add_group_if_identifying(
296 &self,
297 _ctx: impl InvokeContext,
298 _request: AddGroupIfIdentifyingRequest<'_>,
299 ) -> Result<(), Error> {
300 todo!()
302 }
303}