Skip to main content

rs_matter/dm/clusters/
thread_diag.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 Thread Network Diagnostics cluster and its handler.
19
20use core::fmt::Debug;
21
22use rs_matter_macros::{FromTLV, ToTLV};
23
24use crate::dm::{ArrayAttributeRead, Dataver, InvokeContext, ReadContext};
25use crate::error::{Error, ErrorCode};
26use crate::tlv::{
27    Nullable, NullableBuilder, Octets, OctetsBuilder, TLVBuilderParent, ToTLVArrayBuilder,
28    ToTLVBuilder, Utf8StrBuilder,
29};
30use crate::with;
31
32pub use crate::dm::clusters::decl::thread_network_diagnostics::*;
33
34use super::wifi_diag::WirelessDiag;
35
36/// Thread Neighbor Table as returned by the `ThreadDiag` trait
37#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub struct NeighborTable {
40    pub ext_address: u64,
41    pub age: u32,
42    pub rloc16: u16,
43    pub link_frame_counter: u32,
44    pub mle_frame_counter: u32,
45    pub lqi: u8,
46    pub average_rssi: Option<i8>,
47    pub last_rssi: Option<i8>,
48    pub frame_error_rate: u8,
49    pub message_error_rate: u8,
50    pub rx_on_when_idle: bool,
51    pub full_thread_device: bool,
52    pub full_network_data: bool,
53    pub is_child: bool,
54}
55
56/// A snapshot of the IEEE 802.15.4 MAC counters — the cluster's `MACCounts`
57/// (`MACCNT`) feature set, attributes `TxTotalCount` .. `RxErrOtherCount` —
58/// as returned by [`ThreadDiag::mac_counters`].
59///
60/// The two `*MaxRetryExpiryCount` values are `Option`s because not every
61/// Thread backend tracks them; `None` surfaces as an unsupported attribute.
62#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
63#[cfg_attr(feature = "defmt", derive(defmt::Format))]
64pub struct MacCounters {
65    pub tx_total_count: u32,
66    pub tx_unicast_count: u32,
67    pub tx_broadcast_count: u32,
68    pub tx_ack_requested_count: u32,
69    pub tx_acked_count: u32,
70    pub tx_no_ack_requested_count: u32,
71    pub tx_data_count: u32,
72    pub tx_data_poll_count: u32,
73    pub tx_beacon_count: u32,
74    pub tx_beacon_request_count: u32,
75    pub tx_other_count: u32,
76    pub tx_retry_count: u32,
77    pub tx_direct_max_retry_expiry_count: Option<u32>,
78    pub tx_indirect_max_retry_expiry_count: Option<u32>,
79    pub tx_err_cca_count: u32,
80    pub tx_err_abort_count: u32,
81    pub tx_err_busy_channel_count: u32,
82    pub rx_total_count: u32,
83    pub rx_unicast_count: u32,
84    pub rx_broadcast_count: u32,
85    pub rx_data_count: u32,
86    pub rx_data_poll_count: u32,
87    pub rx_beacon_count: u32,
88    pub rx_beacon_request_count: u32,
89    pub rx_other_count: u32,
90    pub rx_address_filtered_count: u32,
91    pub rx_dest_addr_filtered_count: u32,
92    pub rx_duplicated_count: u32,
93    pub rx_err_no_frame_count: u32,
94    pub rx_err_unknown_neighbor_count: u32,
95    pub rx_err_invalid_src_addr_count: u32,
96    pub rx_err_sec_count: u32,
97    pub rx_err_fcs_count: u32,
98    pub rx_err_other_count: u32,
99}
100
101impl NeighborTable {
102    /// Reads the `NeighborTable` into the provided `NeighborTableStructBuilder`.
103    fn read_into<P: TLVBuilderParent>(
104        &self,
105        builder: NeighborTableStructBuilder<P>,
106    ) -> Result<P, Error> {
107        builder
108            .ext_address(self.ext_address)?
109            .age(self.age)?
110            .rloc_16(self.rloc16)?
111            .link_frame_counter(self.link_frame_counter)?
112            .mle_frame_counter(self.mle_frame_counter)?
113            .lqi(self.lqi)?
114            .average_rssi(Nullable::new(self.average_rssi))?
115            .last_rssi(Nullable::new(self.last_rssi))?
116            .frame_error_rate(self.frame_error_rate)?
117            .message_error_rate(self.message_error_rate)?
118            .rx_on_when_idle(self.rx_on_when_idle)?
119            .full_thread_device(self.full_thread_device)?
120            .full_network_data(self.full_network_data)?
121            .is_child(self.is_child)?
122            .end()
123    }
124}
125
126/// Thread Route Table as returned by the `ThreadDiag` trait
127#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)]
128#[cfg_attr(feature = "defmt", derive(defmt::Format))]
129pub struct RouteTable {
130    pub ext_address: u64,
131    pub rloc16: u16,
132    pub router_id: u8,
133    pub next_hop: u8,
134    pub path_cost: u8,
135    pub lqi_in: u8,
136    pub lqi_out: u8,
137    pub age: u8,
138    pub allocated: bool,
139    pub link_established: bool,
140}
141
142impl RouteTable {
143    /// Reads the `RouteTable` into the provided `RouteTableStructBuilder`.
144    fn read_into<P: TLVBuilderParent>(
145        &self,
146        builder: RouteTableStructBuilder<P>,
147    ) -> Result<P, Error> {
148        builder
149            .ext_address(self.ext_address)?
150            .rloc_16(self.rloc16)?
151            .router_id(self.router_id)?
152            .next_hop(self.next_hop)?
153            .path_cost(self.path_cost)?
154            .lqi_in(self.lqi_in)?
155            .lqi_out(self.lqi_out)?
156            .age(self.age)?
157            .allocated(self.allocated)?
158            .link_established(self.link_established)?
159            .end()
160    }
161}
162
163/// Thread Routing Role as returned by the `ThreadDiag` trait
164#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)]
165#[cfg_attr(feature = "defmt", derive(defmt::Format))]
166pub struct SecurityPolicy {
167    pub rotation_time: u16,
168    pub flags: u16,
169}
170
171/// Thread Operational Dataset Components as returned by the `ThreadDiag` trait
172#[derive(Debug, Clone, Eq, PartialEq, Hash, FromTLV, ToTLV)]
173#[cfg_attr(feature = "defmt", derive(defmt::Format))]
174pub struct OperationalDatasetComponents {
175    pub active_timestamp_present: bool,
176    pub pending_timestamp_present: bool,
177    pub master_key_present: bool,
178    pub network_name_present: bool,
179    pub extended_pan_id_present: bool,
180    pub mesh_local_prefix_present: bool,
181    pub delay_present: bool,
182    pub pan_id_present: bool,
183    pub channel_present: bool,
184    pub pskc_present: bool,
185    pub security_policy_present: bool,
186    pub channel_mask_present: bool,
187}
188
189impl OperationalDatasetComponents {
190    /// Reads the `OperationalDatasetComponents` into the provided `OperationalDatasetComponentsBuilder`.
191    fn read_into<P: TLVBuilderParent>(
192        &self,
193        builder: OperationalDatasetComponentsBuilder<P>,
194    ) -> Result<P, Error> {
195        builder
196            .active_timestamp_present(self.active_timestamp_present)?
197            .pending_timestamp_present(self.pending_timestamp_present)?
198            .master_key_present(self.master_key_present)?
199            .network_name_present(self.network_name_present)?
200            .extended_pan_id_present(self.extended_pan_id_present)?
201            .mesh_local_prefix_present(self.mesh_local_prefix_present)?
202            .delay_present(self.delay_present)?
203            .pan_id_present(self.pan_id_present)?
204            .channel_present(self.channel_present)?
205            .pskc_present(self.pskc_present)?
206            .security_policy_present(self.security_policy_present)?
207            .channel_mask_present(self.channel_mask_present)?
208            .end()
209    }
210}
211
212/// The minimal set of data required to implement the Thread Network Diagnostics Cluster
213///
214/// The names of the methods in this trait are matching 1:1 the mandatory attributes of the
215/// Thread Network Diagnostics Cluster.
216pub trait ThreadDiag: WirelessDiag {
217    fn channel(&self) -> Result<Option<u16>, Error> {
218        Ok(None)
219    }
220
221    fn routing_role(&self) -> Result<Option<RoutingRoleEnum>, Error> {
222        Ok(None)
223    }
224
225    fn network_name(
226        &self,
227        f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>,
228    ) -> Result<(), Error> {
229        f(None)
230    }
231
232    fn pan_id(&self) -> Result<Option<u16>, Error> {
233        Ok(None)
234    }
235
236    fn extended_pan_id(&self) -> Result<Option<u64>, Error> {
237        Ok(None)
238    }
239
240    #[allow(clippy::type_complexity)]
241    fn mesh_local_prefix(
242        &self,
243        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
244    ) -> Result<(), Error> {
245        f(None)
246    }
247
248    fn neighbor_table(
249        &self,
250        _f: &mut dyn FnMut(&NeighborTable) -> Result<(), Error>,
251    ) -> Result<(), Error> {
252        Ok(())
253    }
254
255    fn route_table(
256        &self,
257        _f: &mut dyn FnMut(&RouteTable) -> Result<(), Error>,
258    ) -> Result<(), Error> {
259        Ok(())
260    }
261
262    fn partition_id(&self) -> Result<Option<u32>, Error> {
263        Ok(None)
264    }
265
266    fn weighting(&self) -> Result<Option<u16>, Error> {
267        Ok(None)
268    }
269
270    fn data_version(&self) -> Result<Option<u16>, Error> {
271        Ok(None)
272    }
273
274    fn stable_data_version(&self) -> Result<Option<u16>, Error> {
275        Ok(None)
276    }
277
278    fn leader_router_id(&self) -> Result<Option<u8>, Error> {
279        Ok(None)
280    }
281
282    fn ext_address(&self) -> Result<Option<u64>, Error> {
283        Ok(None)
284    }
285
286    fn rloc_16(&self) -> Result<Option<u16>, Error> {
287        Ok(None)
288    }
289
290    fn security_policy(&self) -> Result<Option<SecurityPolicy>, Error> {
291        Ok(None)
292    }
293
294    #[allow(clippy::type_complexity)]
295    fn channel_page0_mask(
296        &self,
297        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
298    ) -> Result<(), Error> {
299        f(None)
300    }
301
302    #[allow(clippy::type_complexity)]
303    fn operational_dataset_components(
304        &self,
305        f: &mut dyn FnMut(Option<&OperationalDatasetComponents>) -> Result<(), Error>,
306    ) -> Result<(), Error> {
307        f(None)
308    }
309
310    #[allow(clippy::type_complexity)]
311    fn active_network_faults_list(
312        &self,
313        _f: &mut dyn FnMut(NetworkFaultEnum) -> Result<(), Error>,
314    ) -> Result<(), Error> {
315        Ok(())
316    }
317
318    /// Lend a snapshot of the IEEE 802.15.4 MAC counters (the cluster's
319    /// `MACCounts` feature set) to the provided closure, or call it with
320    /// `None` if the backend does not track them - in which case the
321    /// corresponding attributes read as unsupported.
322    ///
323    /// Lent by reference (as the other composite payloads of this trait)
324    /// rather than returned by value: the snapshot is a ~144-byte struct.
325    #[allow(clippy::type_complexity)]
326    fn mac_counters(
327        &self,
328        f: &mut dyn FnMut(Option<&MacCounters>) -> Result<(), Error>,
329    ) -> Result<(), Error> {
330        f(None)
331    }
332}
333
334impl<T> ThreadDiag for &T
335where
336    T: ThreadDiag,
337{
338    fn mac_counters(
339        &self,
340        f: &mut dyn FnMut(Option<&MacCounters>) -> Result<(), Error>,
341    ) -> Result<(), Error> {
342        (*self).mac_counters(f)
343    }
344
345    fn channel(&self) -> Result<Option<u16>, Error> {
346        (*self).channel()
347    }
348
349    fn routing_role(&self) -> Result<Option<RoutingRoleEnum>, Error> {
350        (*self).routing_role()
351    }
352
353    fn network_name(
354        &self,
355        f: &mut dyn FnMut(Option<&str>) -> Result<(), Error>,
356    ) -> Result<(), Error> {
357        (*self).network_name(f)
358    }
359
360    fn pan_id(&self) -> Result<Option<u16>, Error> {
361        (*self).pan_id()
362    }
363
364    fn extended_pan_id(&self) -> Result<Option<u64>, Error> {
365        (*self).extended_pan_id()
366    }
367
368    fn mesh_local_prefix(
369        &self,
370        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
371    ) -> Result<(), Error> {
372        (*self).mesh_local_prefix(f)
373    }
374
375    fn neighbor_table(
376        &self,
377        f: &mut dyn FnMut(&NeighborTable) -> Result<(), Error>,
378    ) -> Result<(), Error> {
379        (*self).neighbor_table(f)
380    }
381
382    fn route_table(
383        &self,
384        f: &mut dyn FnMut(&RouteTable) -> Result<(), Error>,
385    ) -> Result<(), Error> {
386        (*self).route_table(f)
387    }
388
389    fn partition_id(&self) -> Result<Option<u32>, Error> {
390        (*self).partition_id()
391    }
392
393    fn weighting(&self) -> Result<Option<u16>, Error> {
394        (*self).weighting()
395    }
396
397    fn data_version(&self) -> Result<Option<u16>, Error> {
398        (*self).data_version()
399    }
400
401    fn stable_data_version(&self) -> Result<Option<u16>, Error> {
402        (*self).stable_data_version()
403    }
404
405    fn leader_router_id(&self) -> Result<Option<u8>, Error> {
406        (*self).leader_router_id()
407    }
408
409    fn ext_address(&self) -> Result<Option<u64>, Error> {
410        (*self).ext_address()
411    }
412
413    fn rloc_16(&self) -> Result<Option<u16>, Error> {
414        (*self).rloc_16()
415    }
416
417    fn security_policy(&self) -> Result<Option<SecurityPolicy>, Error> {
418        (*self).security_policy()
419    }
420
421    fn channel_page0_mask(
422        &self,
423        f: &mut dyn FnMut(Option<&[u8]>) -> Result<(), Error>,
424    ) -> Result<(), Error> {
425        (*self).channel_page0_mask(f)
426    }
427
428    fn operational_dataset_components(
429        &self,
430        f: &mut dyn FnMut(Option<&OperationalDatasetComponents>) -> Result<(), Error>,
431    ) -> Result<(), Error> {
432        (*self).operational_dataset_components(f)
433    }
434
435    fn active_network_faults_list(
436        &self,
437        f: &mut dyn FnMut(NetworkFaultEnum) -> Result<(), Error>,
438    ) -> Result<(), Error> {
439        (*self).active_network_faults_list(f)
440    }
441}
442
443impl ThreadDiag for () {}
444
445/// A cluster implementing the Matter Thread Diagnostics Cluster.
446#[derive(Clone)]
447pub struct ThreadDiagHandler<'a> {
448    dataver: Dataver,
449    diag: &'a dyn ThreadDiag,
450}
451
452impl<'a> ThreadDiagHandler<'a> {
453    /// Create a new instance.
454    pub const fn new(dataver: Dataver, diag: &'a dyn ThreadDiag) -> Self {
455        Self { dataver, diag }
456    }
457
458    /// Read one MAC counter off the wrapped [`ThreadDiag`]'s (lent) snapshot;
459    /// `AttributeNotFound` when the backend does not track the counters (or
460    /// this particular one).
461    fn counter(&self, pick: impl FnOnce(&MacCounters) -> Option<u32>) -> Result<u32, Error> {
462        let mut pick = Some(pick);
463        let mut value = None;
464
465        self.diag.mac_counters(&mut |counters| {
466            if let (Some(counters), Some(pick)) = (counters, pick.take()) {
467                value = pick(counters);
468            }
469
470            Ok(())
471        })?;
472
473        value.ok_or_else(|| ErrorCode::AttributeNotFound.into())
474    }
475
476    /// Adapt the handler instance to the generic `rs-matter` `Handler` trait
477    pub const fn adapt(self) -> HandlerAdaptor<Self> {
478        HandlerAdaptor(self)
479    }
480}
481
482impl ClusterHandler for ThreadDiagHandler<'_> {
483    const CLUSTER: crate::dm::Cluster<'static> =
484        FULL_CLUSTER.with_attrs(with!(required)).with_cmds(with!());
485
486    fn dataver(&self) -> u32 {
487        self.dataver.get()
488    }
489
490    fn dataver_changed(&self) {
491        self.dataver.changed();
492    }
493
494    fn channel(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
495        Ok(Nullable::new(self.diag.channel()?))
496    }
497
498    fn routing_role(&self, _ctx: impl ReadContext) -> Result<Nullable<RoutingRoleEnum>, Error> {
499        Ok(Nullable::new(self.diag.routing_role()?))
500    }
501
502    fn network_name<P: TLVBuilderParent>(
503        &self,
504        _ctx: impl ReadContext,
505        builder: NullableBuilder<P, Utf8StrBuilder<P>>,
506    ) -> Result<P, Error> {
507        let mut builder = Some(builder);
508        let mut parent = None;
509
510        self.diag.network_name(&mut |name| {
511            if let Some(name) = name {
512                parent = Some(unwrap!(builder.take()).non_null()?.set(name)?);
513            } else {
514                parent = Some(unwrap!(builder.take()).null()?);
515            }
516
517            Ok(())
518        })?;
519
520        Ok(unwrap!(parent))
521    }
522
523    fn pan_id(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
524        Ok(Nullable::new(self.diag.pan_id()?))
525    }
526
527    fn extended_pan_id(&self, _ctx: impl ReadContext) -> Result<Nullable<u64>, Error> {
528        Ok(Nullable::new(self.diag.extended_pan_id()?))
529    }
530
531    fn mesh_local_prefix<P: TLVBuilderParent>(
532        &self,
533        _ctx: impl ReadContext,
534        builder: NullableBuilder<P, OctetsBuilder<P>>,
535    ) -> Result<P, Error> {
536        let mut builder = Some(builder);
537        let mut parent = None;
538
539        self.diag.mesh_local_prefix(&mut |prefix| {
540            if let Some(prefix) = prefix {
541                parent = Some(
542                    unwrap!(builder.take())
543                        .non_null()?
544                        .set(Octets::new(prefix))?,
545                );
546            } else {
547                parent = Some(unwrap!(builder.take()).null()?);
548            }
549
550            Ok(())
551        })?;
552
553        Ok(unwrap!(parent))
554    }
555
556    fn neighbor_table<P: TLVBuilderParent>(
557        &self,
558        _ctx: impl ReadContext,
559        builder: ArrayAttributeRead<
560            NeighborTableStructArrayBuilder<P>,
561            NeighborTableStructBuilder<P>,
562        >,
563    ) -> Result<P, Error> {
564        match builder {
565            ArrayAttributeRead::ReadAll(builder) => {
566                let mut builder = Some(builder);
567
568                self.diag.neighbor_table(&mut |item| {
569                    builder = Some(item.read_into(unwrap!(builder.take()).push()?)?);
570
571                    Ok(())
572                })?;
573
574                unwrap!(builder).end()
575            }
576            ArrayAttributeRead::ReadOne(index, builder) => {
577                let mut builder = Some(builder);
578                let mut parent = None;
579                let mut current = 0;
580
581                self.diag.neighbor_table(&mut |item| {
582                    if index == current {
583                        parent = Some(item.read_into(unwrap!(builder.take()))?);
584                    }
585
586                    current += 1;
587
588                    Ok(())
589                })?;
590
591                if let Some(parent) = parent {
592                    Ok(parent)
593                } else {
594                    Err(ErrorCode::InvalidAction.into())
595                }
596            }
597            ArrayAttributeRead::ReadNone(builder) => builder.end(),
598        }
599    }
600
601    fn route_table<P: TLVBuilderParent>(
602        &self,
603        _ctx: impl ReadContext,
604        builder: ArrayAttributeRead<RouteTableStructArrayBuilder<P>, RouteTableStructBuilder<P>>,
605    ) -> Result<P, Error> {
606        match builder {
607            ArrayAttributeRead::ReadAll(builder) => {
608                let mut builder = Some(builder);
609
610                self.diag.route_table(&mut |item| {
611                    builder = Some(item.read_into(unwrap!(builder.take()).push()?)?);
612
613                    Ok(())
614                })?;
615
616                unwrap!(builder).end()
617            }
618            ArrayAttributeRead::ReadOne(index, builder) => {
619                let mut builder = Some(builder);
620                let mut parent = None;
621                let mut current = 0;
622
623                self.diag.route_table(&mut |item| {
624                    if index == current {
625                        parent = Some(item.read_into(unwrap!(builder.take()))?);
626                    }
627
628                    current += 1;
629
630                    Ok(())
631                })?;
632
633                if let Some(parent) = parent {
634                    Ok(parent)
635                } else {
636                    Err(ErrorCode::InvalidAction.into())
637                }
638            }
639            ArrayAttributeRead::ReadNone(builder) => builder.end(),
640        }
641    }
642
643    fn partition_id(&self, _ctx: impl ReadContext) -> Result<Nullable<u32>, Error> {
644        Ok(Nullable::new(self.diag.partition_id()?))
645    }
646
647    fn weighting(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
648        Ok(Nullable::new(self.diag.weighting()?))
649    }
650
651    fn data_version(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
652        Ok(Nullable::new(self.diag.data_version()?))
653    }
654
655    fn stable_data_version(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
656        Ok(Nullable::new(self.diag.stable_data_version()?))
657    }
658
659    fn leader_router_id(&self, _ctx: impl ReadContext) -> Result<Nullable<u8>, Error> {
660        Ok(Nullable::new(self.diag.leader_router_id()?))
661    }
662
663    fn security_policy<P: TLVBuilderParent>(
664        &self,
665        _ctx: impl ReadContext,
666        builder: NullableBuilder<P, SecurityPolicyBuilder<P>>,
667    ) -> Result<P, Error> {
668        let security_policy = self.diag.security_policy()?;
669        if let Some(security_policy) = security_policy {
670            builder
671                .non_null()?
672                .rotation_time(security_policy.rotation_time)?
673                .flags(security_policy.flags)?
674                .end()
675        } else {
676            builder.null()
677        }
678    }
679
680    fn channel_page_0_mask<P: TLVBuilderParent>(
681        &self,
682        _ctx: impl ReadContext,
683        builder: NullableBuilder<P, OctetsBuilder<P>>,
684    ) -> Result<P, Error> {
685        let mut builder = Some(builder);
686        let mut parent = None;
687
688        self.diag.channel_page0_mask(&mut |mask| {
689            if let Some(mask) = mask {
690                parent = Some(unwrap!(builder.take()).non_null()?.set(Octets::new(mask))?);
691            } else {
692                parent = Some(unwrap!(builder.take()).null()?);
693            }
694
695            Ok(())
696        })?;
697
698        Ok(unwrap!(parent.take()))
699    }
700
701    fn operational_dataset_components<P: TLVBuilderParent>(
702        &self,
703        _ctx: impl ReadContext,
704        builder: NullableBuilder<P, OperationalDatasetComponentsBuilder<P>>,
705    ) -> Result<P, Error> {
706        let mut builder = Some(builder);
707        let mut parent = None;
708
709        self.diag.operational_dataset_components(&mut |dsc| {
710            if let Some(dsc) = dsc {
711                parent = Some(dsc.read_into(unwrap!(builder.take()).non_null()?)?);
712            } else {
713                parent = Some(unwrap!(builder.take()).null()?);
714            }
715
716            Ok(())
717        })?;
718
719        Ok(unwrap!(parent))
720    }
721
722    fn active_network_faults_list<P: TLVBuilderParent>(
723        &self,
724        _ctx: impl ReadContext,
725        builder: ArrayAttributeRead<
726            ToTLVArrayBuilder<P, NetworkFaultEnum>,
727            ToTLVBuilder<P, NetworkFaultEnum>,
728        >,
729    ) -> Result<P, Error> {
730        match builder {
731            ArrayAttributeRead::ReadAll(builder) => {
732                let mut builder = Some(builder);
733
734                self.diag.active_network_faults_list(&mut |fault| {
735                    builder = Some(unwrap!(builder.take()).push(&fault)?);
736
737                    Ok(())
738                })?;
739
740                unwrap!(builder.take()).end()
741            }
742            ArrayAttributeRead::ReadOne(index, builder) => {
743                let mut builder = Some(builder);
744                let mut parent = None;
745                let mut current = 0;
746
747                self.diag.active_network_faults_list(&mut |fault| {
748                    if index == current {
749                        parent = Some(unwrap!(builder.take()).set(&fault)?);
750                    }
751
752                    current += 1;
753
754                    Ok(())
755                })?;
756
757                if let Some(parent) = parent {
758                    Ok(parent)
759                } else {
760                    Err(ErrorCode::InvalidAction.into())
761                }
762            }
763            ArrayAttributeRead::ReadNone(builder) => builder.end(),
764        }
765    }
766    fn ext_address(&self, _ctx: impl ReadContext) -> Result<Nullable<u64>, Error> {
767        Ok(Nullable::new(self.diag.ext_address()?))
768    }
769
770    fn rloc_16(&self, _ctx: impl ReadContext) -> Result<Nullable<u16>, Error> {
771        Ok(Nullable::new(self.diag.rloc_16()?))
772    }
773
774    // The MAC counter attributes (`MACCounts` feature set), all served off
775    // one `ThreadDiag::mac_counters` snapshot.
776
777    fn tx_total_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
778        self.counter(|counters| Some(counters.tx_total_count))
779    }
780
781    fn tx_unicast_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
782        self.counter(|counters| Some(counters.tx_unicast_count))
783    }
784
785    fn tx_broadcast_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
786        self.counter(|counters| Some(counters.tx_broadcast_count))
787    }
788
789    fn tx_ack_requested_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
790        self.counter(|counters| Some(counters.tx_ack_requested_count))
791    }
792
793    fn tx_acked_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
794        self.counter(|counters| Some(counters.tx_acked_count))
795    }
796
797    fn tx_no_ack_requested_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
798        self.counter(|counters| Some(counters.tx_no_ack_requested_count))
799    }
800
801    fn tx_data_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
802        self.counter(|counters| Some(counters.tx_data_count))
803    }
804
805    fn tx_data_poll_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
806        self.counter(|counters| Some(counters.tx_data_poll_count))
807    }
808
809    fn tx_beacon_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
810        self.counter(|counters| Some(counters.tx_beacon_count))
811    }
812
813    fn tx_beacon_request_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
814        self.counter(|counters| Some(counters.tx_beacon_request_count))
815    }
816
817    fn tx_other_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
818        self.counter(|counters| Some(counters.tx_other_count))
819    }
820
821    fn tx_retry_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
822        self.counter(|counters| Some(counters.tx_retry_count))
823    }
824
825    fn tx_err_cca_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
826        self.counter(|counters| Some(counters.tx_err_cca_count))
827    }
828
829    fn tx_err_abort_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
830        self.counter(|counters| Some(counters.tx_err_abort_count))
831    }
832
833    fn tx_err_busy_channel_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
834        self.counter(|counters| Some(counters.tx_err_busy_channel_count))
835    }
836
837    fn rx_total_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
838        self.counter(|counters| Some(counters.rx_total_count))
839    }
840
841    fn rx_unicast_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
842        self.counter(|counters| Some(counters.rx_unicast_count))
843    }
844
845    fn rx_broadcast_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
846        self.counter(|counters| Some(counters.rx_broadcast_count))
847    }
848
849    fn rx_data_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
850        self.counter(|counters| Some(counters.rx_data_count))
851    }
852
853    fn rx_data_poll_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
854        self.counter(|counters| Some(counters.rx_data_poll_count))
855    }
856
857    fn rx_beacon_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
858        self.counter(|counters| Some(counters.rx_beacon_count))
859    }
860
861    fn rx_beacon_request_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
862        self.counter(|counters| Some(counters.rx_beacon_request_count))
863    }
864
865    fn rx_other_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
866        self.counter(|counters| Some(counters.rx_other_count))
867    }
868
869    fn rx_address_filtered_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
870        self.counter(|counters| Some(counters.rx_address_filtered_count))
871    }
872
873    fn rx_dest_addr_filtered_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
874        self.counter(|counters| Some(counters.rx_dest_addr_filtered_count))
875    }
876
877    fn rx_duplicated_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
878        self.counter(|counters| Some(counters.rx_duplicated_count))
879    }
880
881    fn rx_err_no_frame_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
882        self.counter(|counters| Some(counters.rx_err_no_frame_count))
883    }
884
885    fn rx_err_unknown_neighbor_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
886        self.counter(|counters| Some(counters.rx_err_unknown_neighbor_count))
887    }
888
889    fn rx_err_invalid_src_addr_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
890        self.counter(|counters| Some(counters.rx_err_invalid_src_addr_count))
891    }
892
893    fn rx_err_sec_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
894        self.counter(|counters| Some(counters.rx_err_sec_count))
895    }
896
897    fn rx_err_fcs_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
898        self.counter(|counters| Some(counters.rx_err_fcs_count))
899    }
900
901    fn rx_err_other_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
902        self.counter(|counters| Some(counters.rx_err_other_count))
903    }
904
905    fn tx_direct_max_retry_expiry_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
906        self.counter(|counters| counters.tx_direct_max_retry_expiry_count)
907    }
908
909    fn tx_indirect_max_retry_expiry_count(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
910        self.counter(|counters| counters.tx_indirect_max_retry_expiry_count)
911    }
912
913    fn handle_reset_counts(&self, _ctx: impl InvokeContext) -> Result<(), Error> {
914        Err(ErrorCode::CommandNotFound.into())
915    }
916}
917
918impl Debug for ThreadDiagHandler<'_> {
919    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
920        f.debug_struct("ThreadDiagHandler")
921            .field("dataver", &self.dataver)
922            .finish()
923    }
924}
925
926#[cfg(feature = "defmt")]
927impl defmt::Format for ThreadDiagHandler<'_> {
928    fn format(&self, f: defmt::Formatter) {
929        defmt::write!(f, "ThreadDiagHandler {{ dataver: {} }}", self.dataver.get());
930    }
931}