Skip to main content

rs_matter/dm/clusters/
desc.rs

1/*
2 *
3 *    Copyright (c) 2025-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! This module contains the implementation of the Descriptor cluster and its handler.
19
20use core::fmt::Debug;
21
22use crate::dm::{ArrayAttributeRead, Cluster, Dataver, Endpoint, EndptId, Metadata, ReadContext};
23use crate::error::{Error, ErrorCode};
24use crate::tlv::{TLVBuilderParent, ToTLVArrayBuilder, ToTLVBuilder};
25use crate::utils::sync::DynBase;
26use crate::with;
27
28pub use crate::dm::clusters::decl::descriptor::*;
29
30/// A parts matcher suitable for regular Matter devices
31#[derive(Debug)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33struct StandardPartsMatcher;
34
35impl DynBase for StandardPartsMatcher {}
36
37impl PartsMatcher for StandardPartsMatcher {
38    fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool {
39        our_endpoint == 0 && endpoint != our_endpoint
40    }
41}
42
43/// A parts matcher suitable for the aggregator endpoints of bridged Matter devices
44///
45/// This matcher matches ALL endpoints that are not the root endpoint (0) and not the aggregator endpoint itself.
46///
47/// For more complex scenarios, where the node needs to contain multiple aggregators, or where the node
48/// might contain non-bridged endpoints, user needs to supply its own `PartsMatcher` implementation.
49#[derive(Debug)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51struct AggregatorPartsMatcher;
52
53impl DynBase for AggregatorPartsMatcher {}
54
55impl PartsMatcher for AggregatorPartsMatcher {
56    fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool {
57        endpoint != our_endpoint && endpoint != 0
58    }
59}
60
61/// A trait for describing which endpoints (parts) should be returned
62/// from the POV of our endpoint
63///
64/// For standard Matter devices, all endpoints should be returned as parts.
65/// However - for queries on aggregator endpoints (i.e. those present in Matter bridges) -
66/// only endpoints different from the aggregator and from the root endpoint should be returned.
67pub trait PartsMatcher: DynBase + Debug {
68    /// Return `true` if the endpoint should be returned as a part
69    ///
70    /// # Arguments
71    /// - `our_endpoint`: The endpoint ID of the endpoint that is being queried
72    /// - `endpoint`: The endpoint ID of the endpoint that is being checked
73    fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool;
74}
75
76impl<T> PartsMatcher for &T
77where
78    T: PartsMatcher,
79{
80    fn matches(&self, our_endpoint: EndptId, endpoint: EndptId) -> bool {
81        (**self).matches(our_endpoint, endpoint)
82    }
83}
84
85/// The system implementation of a handler for the Descriptor Matter cluster.
86#[derive(Clone, Debug)]
87#[cfg_attr(feature = "defmt", derive(defmt::Format))]
88pub struct DescHandler<'a> {
89    dataver: Dataver,
90    matcher: &'a dyn PartsMatcher,
91}
92
93impl DescHandler<'static> {
94    /// Create a new instance of `DescHandler` with the given `Dataver`
95    /// and a matcher suitable for regular Matter devices
96    pub const fn new(dataver: Dataver) -> Self {
97        Self::new_matching(dataver, &StandardPartsMatcher)
98    }
99
100    /// Create a new instance of `DescHandler` with the given `Dataver`
101    /// and a matcher suitable for aggregator endpoints
102    pub const fn new_aggregator(dataver: Dataver) -> Self {
103        Self::new_matching(dataver, &AggregatorPartsMatcher)
104    }
105}
106
107impl<'a> DescHandler<'a> {
108    /// Create a new instance of `DescHandler` with the given `Dataver`
109    /// and a custom matcher
110    pub const fn new_matching(dataver: Dataver, matcher: &'a dyn PartsMatcher) -> DescHandler<'a> {
111        Self { dataver, matcher }
112    }
113
114    /// Adapt the handler instance to the generic `rs-matter` `Handler` trait
115    pub const fn adapt(self) -> HandlerAdaptor<Self> {
116        HandlerAdaptor(self)
117    }
118
119    fn with_endpoint<F, R>(ctx: impl ReadContext, f: F) -> Result<R, Error>
120    where
121        F: FnOnce(&Endpoint) -> Result<R, Error>,
122    {
123        let metadata = ctx.metadata();
124
125        metadata.access(|node| {
126            let endpoint = node
127                .endpoint(ctx.attr().endpoint_id)
128                .ok_or_else(|| Error::new(ErrorCode::EndpointNotFound))?;
129
130            f(endpoint)
131        })
132    }
133}
134
135impl ClusterHandler for DescHandler<'_> {
136    const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required)).with_cmds(with!());
137
138    fn dataver(&self) -> u32 {
139        self.dataver.get()
140    }
141
142    fn dataver_changed(&self) {
143        self.dataver.changed();
144    }
145
146    fn device_type_list<P: TLVBuilderParent>(
147        &self,
148        ctx: impl ReadContext,
149        builder: ArrayAttributeRead<DeviceTypeStructArrayBuilder<P>, DeviceTypeStructBuilder<P>>,
150    ) -> Result<P, Error> {
151        Self::with_endpoint(ctx, |endpoint| match builder {
152            ArrayAttributeRead::ReadAll(mut builder) => {
153                for dev_type in endpoint.device_types {
154                    builder = builder
155                        .push()?
156                        .device_type(dev_type.dtype as _)?
157                        .revision(dev_type.drev)?
158                        .end()?;
159                }
160
161                builder.end()
162            }
163            ArrayAttributeRead::ReadOne(index, builder) => {
164                let Some(dev_type) = endpoint.device_types.get(index as usize) else {
165                    return Err(ErrorCode::ConstraintError.into());
166                };
167
168                builder
169                    .device_type(dev_type.dtype as _)?
170                    .revision(dev_type.drev)?
171                    .end()
172            }
173            ArrayAttributeRead::ReadNone(builder) => builder.end(),
174        })
175    }
176
177    fn server_list<P: TLVBuilderParent>(
178        &self,
179        ctx: impl ReadContext,
180        builder: ArrayAttributeRead<ToTLVArrayBuilder<P, u32>, ToTLVBuilder<P, u32>>,
181    ) -> Result<P, Error> {
182        Self::with_endpoint(ctx, |endpoint| match builder {
183            ArrayAttributeRead::ReadAll(mut builder) => {
184                for cluster in endpoint.clusters {
185                    builder = builder.push(&cluster.id)?;
186                }
187
188                builder.end()
189            }
190            ArrayAttributeRead::ReadOne(index, builder) => {
191                let Some(cluster) = endpoint.clusters.get(index as usize) else {
192                    return Err(ErrorCode::ConstraintError.into());
193                };
194
195                builder.set(&cluster.id)
196            }
197            ArrayAttributeRead::ReadNone(builder) => builder.end(),
198        })
199    }
200
201    fn client_list<P: TLVBuilderParent>(
202        &self,
203        ctx: impl ReadContext,
204        builder: ArrayAttributeRead<ToTLVArrayBuilder<P, u32>, ToTLVBuilder<P, u32>>,
205    ) -> Result<P, Error> {
206        Self::with_endpoint(ctx, |endpoint| match builder {
207            ArrayAttributeRead::ReadAll(mut builder) => {
208                for client_id in endpoint.client_clusters {
209                    builder = builder.push(client_id)?;
210                }
211                builder.end()
212            }
213            ArrayAttributeRead::ReadOne(index, builder) => {
214                let Some(client_id) = endpoint.client_clusters.get(index as usize) else {
215                    return Err(ErrorCode::ConstraintError.into());
216                };
217                builder.set(client_id)
218            }
219            ArrayAttributeRead::ReadNone(builder) => builder.end(),
220        })
221    }
222
223    fn parts_list<P: TLVBuilderParent>(
224        &self,
225        ctx: impl ReadContext,
226        builder: ArrayAttributeRead<ToTLVArrayBuilder<P, u16>, ToTLVBuilder<P, u16>>,
227    ) -> Result<P, Error> {
228        let metadata = ctx.metadata();
229
230        metadata.access(|node| {
231            let mut ep_ids = node
232                .endpoints
233                .iter()
234                .map(|e| e.id)
235                .filter(|e| self.matcher.matches(ctx.attr().endpoint_id, *e));
236
237            match builder {
238                ArrayAttributeRead::ReadAll(mut builder) => {
239                    for id in ep_ids {
240                        builder = builder.push(&id)?;
241                    }
242
243                    builder.end()
244                }
245                ArrayAttributeRead::ReadOne(index, builder) => {
246                    let Some(ep_id) = ep_ids.nth(index as usize) else {
247                        return Err(ErrorCode::ConstraintError.into());
248                    };
249
250                    builder.set(&ep_id)
251                }
252                ArrayAttributeRead::ReadNone(builder) => builder.end(),
253            }
254        })
255    }
256}