rs_matter/dm/clusters/diag_logs/client.rs
1/*
2 *
3 * Copyright (c) 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//! The Diagnostic Logs cluster *client* side (the controller fetching logs).
19//!
20//! The client sends `RetrieveLogsRequest` via the generated `DiagnosticLogsClient`
21//! proxy on an [`Exchange`](crate::transport::exchange::Exchange); when the server
22//! answers a `BDX` request by pushing the log over BDX, the client receives it
23//! with [`DiagLogsBdxHandler`] - a [`BdxHandler`] it chains into its responder,
24//! the mirror of the server-side
25//! [`OtaBdxHandler`](crate::dm::clusters::ota_prov::OtaBdxHandler).
26
27use crate::bdx::{BdxHandler, BdxReader, BdxResponder, BdxStatus};
28use crate::error::Error;
29
30use super::MAX_FILE_DESIGNATOR;
31
32/// A destination for diagnostic logs received over BDX - the *client* side.
33///
34/// Implemented by an application that collects logs from devices (e.g. a small
35/// management controller that fetches logs and forwards them to a cloud). The
36/// [`DiagLogsBdxHandler`] calls [`receive`](Self::receive) once per incoming BDX
37/// log transfer; pull the bytes from `reader` yourself and store them however you
38/// like - there is no per-chunk callback.
39pub trait DiagLogsReceiver {
40 /// Receive one diagnostic-log transfer. `file_designator` is the one the
41 /// client named in its `RetrieveLogsRequest` (use it to correlate the
42 /// transfer with that request). Read from `reader` until it returns `0`; the
43 /// transfer is abandoned if the reader is dropped before end-of-stream.
44 async fn receive(
45 &self,
46 file_designator: &[u8],
47 reader: &mut BdxReader<'_>,
48 ) -> Result<(), Error>;
49}
50
51impl<T> DiagLogsReceiver for &T
52where
53 T: DiagLogsReceiver,
54{
55 async fn receive(
56 &self,
57 file_designator: &[u8],
58 reader: &mut BdxReader<'_>,
59 ) -> Result<(), Error> {
60 T::receive(self, file_designator, reader).await
61 }
62}
63
64/// The client side of Diagnostic Logs over BDX: a [`BdxHandler`] that accepts a
65/// log a device *pushes* over BDX and hands it to a [`DiagLogsReceiver`].
66///
67/// This mirrors [`OtaBdxHandler`](crate::dm::clusters::ota_prov::OtaBdxHandler) on
68/// the server side. Wrap it in a [`Bdx`](crate::bdx::Bdx) handler and chain that
69/// into your responder: after you send a `RetrieveLogsRequest` with
70/// `RequestedProtocol = BDX` (via the generated `DiagnosticLogsClient` proxy on an
71/// [`Exchange`](crate::transport::exchange::Exchange)), the device opens a BDX
72/// transfer back to you, and the responder routes it here - concurrently with the
73/// still-in-flight request, so no explicit `accept`/`select` is needed.
74pub struct DiagLogsBdxHandler<R> {
75 receiver: R,
76}
77
78impl<R> DiagLogsBdxHandler<R> {
79 /// Create a new handler that delivers each received log to `receiver`.
80 pub const fn new(receiver: R) -> Self {
81 Self { receiver }
82 }
83}
84
85impl<R: DiagLogsReceiver> BdxHandler for DiagLogsBdxHandler<R> {
86 async fn handles(&self, responder: &BdxResponder<'_>) -> bool {
87 // We only accept logs being pushed to us (uploads), never downloads.
88 matches!(responder, BdxResponder::Upload(_))
89 }
90
91 async fn handle(&self, responder: BdxResponder<'_>) -> Result<(), Error> {
92 let responder = match responder {
93 BdxResponder::Upload(responder) => responder,
94 other => return other.reject(BdxStatus::TransferMethodNotSupported).await,
95 };
96
97 // Copy the file designator out before `reply` releases the held init.
98 let mut fd = heapless::Vec::<u8, MAX_FILE_DESIGNATOR>::new();
99 if fd.extend_from_slice(responder.fd()).is_err() {
100 return responder.reject(BdxStatus::FileDesignatorUnknown).await;
101 }
102
103 let mut reader = responder.reply().await?;
104
105 self.receiver.receive(&fd, &mut reader).await
106 }
107}