Skip to main content

rs_matter/im/
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//! Interaction Model Client implementation.
19//!
20//! This module provides client-side functionality for sending IM requests
21//! (Read, Write, Invoke, Subscribe) to Matter devices and processing their
22//! responses.
23//!
24//! Subscribe support covers the *establishment* phase only — the
25//! `SubscribeRequest`, the priming `ReportData` chunks and the
26//! terminal `SubscribeResponse`. Server-initiated post-establishment
27//! reports arrive on new exchanges over the same session and require
28//! a separate listening abstraction layered on top of the transport.
29
30use either::Either;
31
32pub use super::{AttrId, ClusterId, EndptId};
33
34use crate::error::{Error, ErrorCode};
35#[cfg(feature = "groups")]
36use crate::tlv::TLVWriteParent;
37use crate::tlv::{FromTLV, TLVBuilderParent, TLVElement, TLVTag, TLVWrite, TagType, ToTLV};
38use crate::transport::exchange::{Exchange, OwnedSender, OwnedSenderTx};
39#[cfg(feature = "groups")]
40use crate::utils::storage::WriteBuf;
41
42use super::{
43    IMStatusCode, InvReqBuilder, InvokeResp, OpCode, ReadReqBuilder, ReportDataResp, StatusResp,
44    SubscribeReqBuilder, SubscribeResp, TimedReq, WriteReqBuilder, WriteResp, IM_REVISION,
45};
46
47/// IM Client trait — extension over an [`Exchange`] that adds the
48/// Matter Interaction Model client operations.
49///
50/// Implemented for [`Exchange<'a>`]; user code just `use`s this trait
51/// to get method-syntax access on any exchange handle. Two flavours
52/// of method live on this trait:
53///
54/// - `*_sender` — hands the caller a typed `*Sender` they drive
55///   manually. Maximum control, full visibility into the retransmit
56///   loop and chunked response iteration.
57/// - `*_with` / `*_with_async` — takes a build closure that
58///   writes the request straight into the TX buffer; the retransmit
59///   loop is handled internally and the first response chunk is
60///   handed back for the caller to iterate via `complete()`.
61///
62/// On top of these, the codegen-emitted per-cluster
63/// `<ClusterName>Client<'a>` traits add high-level single-shot
64/// methods (`<cluster>_<command>` / `<cluster>_<attr>_read` /
65/// `<cluster>_<attr>_write`) that bake in the cluster/attr/cmd IDs
66/// and the chunk-drain / status-to-error conversion for the common
67/// case.
68///
69/// The trait sits over `Self: Into<Exchange<'a>>` so any type that
70/// converts to an exchange can opt in via a one-line blanket impl;
71/// `Exchange<'a>` itself implements `Into<Exchange<'a>>` for free via
72/// the standard-library identity impl.
73///
74/// # Lifecycle
75///
76/// Every method **consumes** the exchange (`self` by value) — one
77/// exchange is one IM transaction, end of story. After the method
78/// returns, the exchange is closed and the slot is released; callers
79/// wanting to issue another transaction must initiate a fresh
80/// exchange.
81pub trait ImClient<'a>: Sized + Into<Exchange<'a>> {
82    /// Perform an IM read transaction.
83    ///
84    /// # Arguments
85    /// - `build` closure that writes the `ReadRequestMessage` TLV body
86    ///   NOTE: The closure is `FnMut` because the MRP layer may retransmit the
87    ///   request multiple times; it MUST produce the same TLV output on every call.
88    ///
89    /// # Returns
90    /// - `Ok(ReadRespChunk)` for the first response chunk; multi-chunk
91    ///   `ReportData` streams iterate via `ReadRespChunk::complete()`
92    /// - `Err` if the transaction fails at any point (request build,
93    ///   I/O, response parsing, etc.)
94    async fn read_with<B>(self, mut build: B) -> Result<ReadRespChunk<'a>, Error>
95    where
96        B: FnMut(ReadReqBuilder<ReadSender<'a>>) -> Result<ReadSender<'a>, Error>,
97    {
98        // Drives the retransmit loop on the caller's behalf
99        // (build closure idempotency contract — same TLV bytes on
100        // every call). First response chunk handed back; the caller
101        // iterates further chunks via `chunk.complete()`.
102        let mut sender = self.read_sender().await?;
103        loop {
104            match sender.tx().await? {
105                TxOutcome::BuildRequest(builder) => {
106                    sender = build(builder)?;
107                }
108                TxOutcome::GotResponse(chunk) => return Ok(chunk),
109            }
110        }
111    }
112
113    /// Perform an IM read transaction without using a closure.
114    ///
115    /// # Returns
116    /// - `Ok(ReadSender)` ready for the caller to drive manually via `ReadSender::tx()`
117    ///   The first call to [`ReadSender::tx`] yields the initial builder.
118    ///   See [`invoke_sender`](Self::invoke_sender) for the full pattern.
119    /// - `Err` if the transaction fails at any point (I/O, etc.)
120    async fn read_sender(self) -> Result<ReadSender<'a>, Error> {
121        let exchange: Exchange<'a> = self.into();
122        let sender = exchange.into_sender()?;
123        Ok(ReadSender {
124            state: ReadSenderState::Ready(sender),
125        })
126    }
127
128    /// Perform an IM write transaction.
129    ///
130    /// # Arguments
131    /// - `build` closure that writes the `WriteRequestMessage` TLV body.
132    ///   NOTE: the closure is `FnMut` because the MRP layer may retransmit the
133    ///   request multiple times; it MUST produce the same TLV output on every call.
134    ///
135    /// # Returns
136    /// - `Ok(WriteRespHandle)` once the request is ACK-ed and the response is parsed; call
137    ///   `WriteRespHandle::response()` to inspect the parsed `WriteResp`.
138    /// - `Err` if the transaction fails at any point (request build, I/O, response parsing, etc.)
139    async fn write_with<B>(
140        self,
141        timed_timeout_ms: Option<u16>,
142        mut build: B,
143    ) -> Result<WriteRespHandle<'a>, Error>
144    where
145        B: FnMut(WriteReqBuilder<WriteSender<'a>>) -> Result<WriteSender<'a>, Error>,
146    {
147        let mut sender = self.write_sender(timed_timeout_ms).await?;
148        loop {
149            match sender.tx().await? {
150                TxOutcome::BuildRequest(builder) => {
151                    sender = build(builder)?;
152                }
153                TxOutcome::GotResponse(handle) => return Ok(handle),
154            }
155        }
156    }
157
158    /// Perform an IM write transaction without using a closure.
159    ///
160    /// # Arguments
161    /// - `timed_timeout_ms` if `Some`, perform the initial handshake via a `TimedRequest` with the given timeout (in milliseconds)
162    ///
163    /// # Returns
164    /// - `Ok(WriteSender)` ready for the caller to drive manually via `WriteSender::tx()`
165    ///   The first call to [`WriteSender::tx`] yields the initial builder.
166    ///   See [`invoke_sender`](Self::invoke_sender) for the full pattern.
167    /// - `Err` if the transaction fails at any point (I/O, etc.)
168    async fn write_sender(self, timed_timeout_ms: Option<u16>) -> Result<WriteSender<'a>, Error> {
169        let mut exchange: Exchange<'a> = self.into();
170        if let Some(timeout_ms) = timed_timeout_ms {
171            send_timed_request(&mut exchange, timeout_ms).await?;
172        }
173        let sender = exchange.into_sender()?;
174        Ok(WriteSender {
175            state: WriteSenderState::Ready(sender),
176        })
177    }
178
179    /// Perform an IM invoke transaction.
180    ///
181    /// # Arguments
182    /// - `timed_timeout_ms` if `Some`, perform the initial handshake via a `TimedRequest` with the given timeout (in milliseconds)
183    /// - `build` closure that writes the `InvokeRequestMessage` TLV body
184    ///   NOTE: The closure is `FnMut` because the MRP layer may retransmit the
185    ///   request multiple times; it MUST produce the same TLV output on every call.
186    ///
187    /// # Returns
188    /// - `Ok(InvokeRespChunk)` once the request is ACK-ed and the first response chunk is parsed;
189    ///   multi-chunk `InvokeResponse` streams iterate via `InvokeRespChunk::complete()`.
190    /// - `Err` if the transaction fails at any point (request build, I/O, response parsing, etc.)
191    async fn invoke_with<B>(
192        self,
193        timed_timeout_ms: Option<u16>,
194        mut build: B,
195    ) -> Result<InvokeRespChunk<'a>, Error>
196    where
197        B: FnMut(InvReqBuilder<InvokeSender<'a>>) -> Result<InvokeSender<'a>, Error>,
198    {
199        // Drives the retransmit loop on the caller's behalf:
200        // the `build` closure is (re-)run on every framework attempt
201        // (so it must remain idempotent — same TLV bytes on every
202        // call), and the first response chunk is returned to the
203        // caller for direct inspection / `complete()` iteration.
204        let mut sender = self.invoke_sender(timed_timeout_ms).await?;
205        loop {
206            match sender.tx().await? {
207                TxOutcome::BuildRequest(builder) => {
208                    sender = build(builder)?;
209                }
210                TxOutcome::GotResponse(chunk) => return Ok(chunk),
211            }
212        }
213    }
214
215    /// Perform an IM invoke transaction without using a closure.
216    ///
217    /// # Arguments
218    /// - `timed_timeout_ms` if `Some`, perform the initial handshake via a `TimedRequest` with the given timeout (in milliseconds)
219    ///
220    /// # Returns
221    /// - `Ok(InvokeSender)` ready for the caller to drive manually via `InvokeSender::tx()`.
222    ///   The first call to [`InvokeSender::tx`] yields the initial builder.
223    /// - `Err` if the transaction fails at any point (I/O, etc.)
224    ///
225    /// # Lifecycle
226    ///
227    /// 1. `let mut sender = exchange.invoke_sender(None).await?;`
228    /// 2. `loop { match sender.tx().await? { TxOutcome::BuildRequest(b) => sender = build(b)?, TxOutcome::GotResponse(c) => break c } }`
229    /// 3. `loop { let resp = chunk.response()?; …; match chunk.complete().await? { … } }`
230    async fn invoke_sender(self, timed_timeout_ms: Option<u16>) -> Result<InvokeSender<'a>, Error> {
231        let mut exchange: Exchange<'a> = self.into();
232        if let Some(timeout_ms) = timed_timeout_ms {
233            send_timed_request(&mut exchange, timeout_ms).await?;
234        }
235        let sender = exchange.into_sender()?;
236        Ok(InvokeSender {
237            state: InvokeSenderState::Ready(sender),
238        })
239    }
240
241    /// Perform the *establishment* phase of an IM subscribe
242    /// transaction.
243    ///
244    /// On the wire the establishment is a sequence of priming
245    /// `ReportData` chunks (each ACK-ed by the client with
246    /// `StatusResponse(Success)`) followed by a single
247    /// `SubscribeResponse` carrying `subscription_id` and the chosen
248    /// `max_int`. This method drives the request side and hands the
249    /// caller back the first priming chunk; the caller iterates
250    /// further priming chunks (and gets the terminal
251    /// [`SubscribeEstablished`]) via [`SubscribePrimingChunk::complete`].
252    ///
253    /// # Arguments
254    /// - `build` — closure that writes the `SubscribeRequestMessage`
255    ///   TLV body via the streaming [`SubscribeReqBuilder`]. NOTE:
256    ///   `FnMut` because the MRP layer may retransmit the request;
257    ///   it MUST produce the same TLV output on every call.
258    ///
259    /// # Returns
260    /// - `Ok(SubscribePrimingChunk)` for the first priming chunk;
261    ///   walk the chunk loop via [`SubscribePrimingChunk::complete`].
262    /// - `Err` on any failure (request build, I/O, response parsing,
263    ///   peer-side validation `StatusResponse(non-Success)`, …)
264    ///
265    /// # Scope: establishment only
266    ///
267    /// The *active* subscription phase — server-initiated
268    /// `ReportData` messages arriving on new exchanges throughout
269    /// the lifetime of the subscription — is **not** covered by
270    /// this method. That requires a listening loop on the
271    /// fabric/peer-node pair and is a separate piece of
272    /// infrastructure to layer on top. Once the
273    /// [`SubscribeEstablished`] is returned, the
274    /// fabric+peer+subscription-id triple identifies the active
275    /// subscription for any such future incoming reports.
276    async fn subscribe_with<B>(self, mut build: B) -> Result<SubscribePrimingChunk<'a>, Error>
277    where
278        B: FnMut(SubscribeReqBuilder<SubscribeSender<'a>>) -> Result<SubscribeSender<'a>, Error>,
279    {
280        let mut sender = self.subscribe_sender().await?;
281        loop {
282            match sender.tx().await? {
283                TxOutcome::BuildRequest(builder) => {
284                    sender = build(builder)?;
285                }
286                TxOutcome::GotResponse(chunk) => return Ok(chunk),
287            }
288        }
289    }
290
291    /// Perform the establishment phase of an IM subscribe transaction
292    /// without using a closure.
293    ///
294    /// # Returns
295    /// - `Ok(SubscribeSender)` ready to be driven manually via
296    ///   [`SubscribeSender::tx`]. The first call yields the initial
297    ///   [`SubscribeReqBuilder`].
298    /// - `Err` if the underlying exchange handoff fails.
299    async fn subscribe_sender(self) -> Result<SubscribeSender<'a>, Error> {
300        let exchange: Exchange<'a> = self.into();
301        let sender = exchange.into_sender()?;
302        Ok(SubscribeSender {
303            state: SubscribeSenderState::Ready(sender),
304        })
305    }
306
307    /// Perform a fire-and-forget IM invoke — for group (multicast) exchanges
308    /// opened with [`Exchange::initiate_group`].
309    ///
310    /// Sends a single `InvokeRequestMessage` with `SuppressResponse = true`
311    /// and completes as soon as the message is out: groupcast invokes elicit
312    /// no responses and group messages carry no MRP, so there is nothing to
313    /// wait for. Push the command entries with endpoint-less paths
314    /// ([`CmdDataBuilder::path_from`] with an endpoint-less
315    /// [`crate::im::CmdPath`]) — receivers apply them to their group-member
316    /// endpoints.
317    #[cfg(feature = "groups")]
318    async fn group_invoke_with<B>(self, mut build: B) -> Result<(), Error>
319    where
320        B: for<'x, 'y> FnMut(
321            GroupInvokeRequestsBuilder<'x, 'y>,
322        ) -> Result<GroupInvokeRequestsBuilder<'x, 'y>, Error>,
323    {
324        let mut exchange: Exchange<'a> = self.into();
325
326        exchange
327            .send_with(|_, wb| {
328                let builder =
329                    InvReqBuilder::new(TLVWriteParent::new("GroupInvoke", wb), &TLVTag::Anonymous)?
330                        .suppress_response(true)?
331                        .invoke_requests()?;
332
333                let builder = build(builder)?;
334
335                builder.end()?.end()?;
336
337                Ok(Some(OpCode::InvokeRequest.meta().reliable(false)))
338            })
339            .await
340    }
341}
342
343/// The builder handed to [`ImClient::group_invoke_with`]'s closure: the
344/// `InvokeRequests` array of a `SuppressResponse` invoke, writing into the
345/// exchange's TX buffer.
346#[cfg(feature = "groups")]
347pub type GroupInvokeRequestsBuilder<'x, 'y> = crate::im::CmdDataArrayBuilder<
348    InvReqBuilder<TLVWriteParent<&'static str, &'x mut WriteBuf<'y>>, 3>,
349>;
350
351/// Blanket impl so any [`Exchange<'a>`] is an [`ImClient<'a>`] when
352/// the trait is `use`d. The default-method bodies do all the work;
353/// this impl just opts the type in.
354impl<'a> ImClient<'a> for Exchange<'a> {}
355
356/// Outcome of calling `.tx()` on a transaction sender (`*Sender`).
357#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
358#[cfg_attr(feature = "defmt", derive(defmt::Format))]
359pub enum TxOutcome<F, S> {
360    /// Framework needs the request bytes (re-)built into a fresh TX slot;
361    BuildRequest(F),
362    /// Framework has received the peer's ACK; here's the first response chunk.
363    GotResponse(S),
364}
365
366// =====================================================================
367// Transaction types for the `read` opcode.
368//
369// Mirrors the `invoke` set: `ReadSender` drives the MRP retransmit
370// loop; the codegen `ReadReqBuilder` writes through `ReadSenderSlot`
371// while the slot is live; `ReadRespChunk` gives chunk-by-chunk
372// access to the resulting `ReportData` stream.
373// =====================================================================
374
375/// Cornerstone `read` transaction. See module docs for the
376/// pattern. Returned by [`ImClient::read_sender`].
377pub struct ReadSender<'a> {
378    state: ReadSenderState<'a>,
379}
380
381enum ReadSenderState<'a> {
382    Ready(OwnedSender<'a>),
383    Slot(ReadSenderSlot<'a>),
384}
385
386impl<'a> ReadSender<'a> {
387    /// Drive one round of the MRP retransmit loop. See
388    /// [`InvokeSender::tx`] for the full contract; the read variant is
389    /// identical except the right arm holds a [`ReadRespChunk`].
390    pub async fn tx(
391        mut self,
392    ) -> Result<TxOutcome<ReadReqBuilder<ReadSender<'a>>, ReadRespChunk<'a>>, Error> {
393        let sender = match self.state {
394            ReadSenderState::Slot(slot) => slot.commit()?,
395            ReadSenderState::Ready(s) => s,
396        };
397
398        match sender.tx().await? {
399            Either::Left(tx) => {
400                self.state = ReadSenderState::Slot(ReadSenderSlot { tx, cursor: 0 });
401                let builder = ReadReqBuilder::new(self, &TLVTag::Anonymous)?;
402                Ok(TxOutcome::BuildRequest(builder))
403            }
404            Either::Right(exchange) => Ok(TxOutcome::GotResponse(
405                ReadRespChunk::receive(exchange).await?,
406            )),
407        }
408    }
409}
410
411impl<'a> TLVBuilderParent for ReadSender<'a> {
412    type Write = ReadSenderSlot<'a>;
413
414    fn writer(&mut self) -> &mut Self::Write {
415        match &mut self.state {
416            ReadSenderState::Slot(slot) => slot,
417            ReadSenderState::Ready(_) => panic!(
418                "ReadSender::writer() called outside the build phase — \
419                 only reachable via a ReadReqBuilder yielded by ReadSender::tx."
420            ),
421        }
422    }
423}
424
425impl<'a> core::fmt::Debug for ReadSender<'a> {
426    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
427        write!(f, "ReadSender")
428    }
429}
430
431#[cfg(feature = "defmt")]
432impl<'a> defmt::Format for ReadSender<'a> {
433    fn format(&self, f: defmt::Formatter<'_>) {
434        defmt::write!(f, "ReadSender")
435    }
436}
437
438/// Internal serialization handle for the in-flight build of a
439/// [`ReadSender`]. See [`InvokeSenderSlot`] for the design rationale —
440/// this type exists only because [`TLVBuilderParent`] requires the
441/// `Write` associated type to be a named type.
442pub struct ReadSenderSlot<'a> {
443    tx: OwnedSenderTx<'a>,
444    cursor: usize,
445}
446
447impl<'a> ReadSenderSlot<'a> {
448    fn commit(self) -> Result<OwnedSender<'a>, Error> {
449        self.tx.complete(0, self.cursor, OpCode::ReadRequest.into())
450    }
451}
452
453impl<'a> TLVWrite for ReadSenderSlot<'a> {
454    type Position = usize;
455
456    fn write(&mut self, byte: u8) -> Result<(), Error> {
457        let payload = self.tx.payload();
458        if self.cursor >= payload.len() {
459            return Err(ErrorCode::NoSpace.into());
460        }
461        payload[self.cursor] = byte;
462        self.cursor += 1;
463        Ok(())
464    }
465
466    fn get_tail(&self) -> Self::Position {
467        self.cursor
468    }
469
470    fn rewind_to(&mut self, pos: Self::Position) {
471        self.cursor = pos;
472    }
473}
474
475impl<'a> core::fmt::Debug for ReadSenderSlot<'a> {
476    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
477        write!(f, "ReadSenderSlot({})", self.cursor)
478    }
479}
480
481#[cfg(feature = "defmt")]
482impl<'a> defmt::Format for ReadSenderSlot<'a> {
483    fn format(&self, f: defmt::Formatter<'_>) {
484        defmt::write!(f, "ReadSenderSlot({})", self.cursor)
485    }
486}
487
488/// First (possibly only) response chunk of a `read`
489/// transaction. Returned by [`ReadSender::tx`] once the peer has ACK-ed
490/// the request and the first `ReportData` chunk is parsed.
491///
492/// Multi-chunk `ReportData` streams iterate via
493/// [`complete`](Self::complete) — same shape as [`InvokeRespChunk`].
494pub struct ReadRespChunk<'a> {
495    exchange: Exchange<'a>,
496}
497
498impl<'a> ReadRespChunk<'a> {
499    async fn receive(mut exchange: Exchange<'a>) -> Result<Self, Error> {
500        exchange.recv_fetch().await?;
501        {
502            let rx = exchange.rx()?;
503            check_opcode(rx.meta().proto_opcode, OpCode::ReportData)?;
504        }
505        Ok(Self { exchange })
506    }
507
508    /// Borrowed access to the parsed `ReportDataResp` for this chunk.
509    pub fn response(&self) -> Result<ReportDataResp<'_>, Error> {
510        let rx = self.exchange.rx()?;
511        let element = TLVElement::new(rx.payload());
512        ReportDataResp::from_tlv(&element)
513    }
514
515    /// ACK the current chunk; if `more_chunks=true`, fetch + parse
516    /// the next chunk and return it as `Some(next)`. Otherwise drop
517    /// the exchange and return `None`.
518    pub async fn complete(mut self) -> Result<Option<Self>, Error> {
519        let (more_chunks, suppress_response) = {
520            let resp = self.response()?;
521            (
522                resp.more_chunks.unwrap_or(false),
523                resp.suppress_response.unwrap_or(false),
524            )
525        };
526
527        if more_chunks {
528            // Request next chunk.
529            self.exchange
530                .send_with(|_, wb| {
531                    StatusResp::write(wb, IMStatusCode::Success)?;
532                    Ok(Some(OpCode::StatusResponse.into()))
533                })
534                .await?;
535
536            self.exchange.recv_fetch().await?;
537            {
538                let rx = self.exchange.rx()?;
539                check_opcode(rx.meta().proto_opcode, OpCode::ReportData)?;
540            }
541
542            Ok(Some(self))
543        } else {
544            if !suppress_response {
545                self.exchange
546                    .send_with(|_, wb| {
547                        StatusResp::write(wb, IMStatusCode::Success)?;
548                        Ok(Some(OpCode::StatusResponse.into()))
549                    })
550                    .await?;
551            } else {
552                self.exchange.acknowledge().await?;
553            }
554            Ok(None)
555        }
556    }
557}
558
559// =====================================================================
560// Transaction types for the `write` opcode.
561//
562// Mirrors the `invoke` / `read` sets. `WriteResponseMessage`
563// is single-message per spec (no chunking), so the receive side has
564// a [`WriteRespHandle`] with just a [`response()`](WriteRespHandle::response)
565// method — no `complete()` iteration.
566// =====================================================================
567
568/// Cornerstone `write` transaction. See module docs for the
569/// pattern. Returned by [`ImClient::write_sender`].
570pub struct WriteSender<'a> {
571    state: WriteSenderState<'a>,
572}
573
574enum WriteSenderState<'a> {
575    Ready(OwnedSender<'a>),
576    Slot(WriteSenderSlot<'a>),
577}
578
579impl<'a> WriteSender<'a> {
580    /// Drive one round of the MRP retransmit loop. Mirrors
581    /// [`InvokeSender::tx`] / [`ReadSender::tx`] except the right arm
582    /// returns a [`WriteRespHandle`] (no chunking on write).
583    pub async fn tx(
584        mut self,
585    ) -> Result<TxOutcome<WriteReqBuilder<WriteSender<'a>>, WriteRespHandle<'a>>, Error> {
586        let sender = match self.state {
587            WriteSenderState::Slot(slot) => slot.commit()?,
588            WriteSenderState::Ready(s) => s,
589        };
590
591        match sender.tx().await? {
592            Either::Left(tx) => {
593                self.state = WriteSenderState::Slot(WriteSenderSlot { tx, cursor: 0 });
594                let builder = WriteReqBuilder::new(self, &TLVTag::Anonymous)?;
595                Ok(TxOutcome::BuildRequest(builder))
596            }
597            Either::Right(exchange) => Ok(TxOutcome::GotResponse(
598                WriteRespHandle::receive(exchange).await?,
599            )),
600        }
601    }
602}
603
604impl<'a> TLVBuilderParent for WriteSender<'a> {
605    type Write = WriteSenderSlot<'a>;
606
607    fn writer(&mut self) -> &mut Self::Write {
608        match &mut self.state {
609            WriteSenderState::Slot(slot) => slot,
610            WriteSenderState::Ready(_) => panic!(
611                "WriteSender::writer() called outside the build phase — \
612                 only reachable via a WriteReqBuilder yielded by WriteSender::tx."
613            ),
614        }
615    }
616}
617
618impl<'a> core::fmt::Debug for WriteSender<'a> {
619    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
620        write!(f, "WriteSender")
621    }
622}
623
624#[cfg(feature = "defmt")]
625impl<'a> defmt::Format for WriteSender<'a> {
626    fn format(&self, f: defmt::Formatter<'_>) {
627        defmt::write!(f, "WriteSender")
628    }
629}
630
631/// Internal serialization handle for the in-flight build of a
632/// [`WriteSender`]. See [`InvokeSenderSlot`] for the design rationale.
633pub struct WriteSenderSlot<'a> {
634    tx: OwnedSenderTx<'a>,
635    cursor: usize,
636}
637
638impl<'a> WriteSenderSlot<'a> {
639    fn commit(self) -> Result<OwnedSender<'a>, Error> {
640        self.tx
641            .complete(0, self.cursor, OpCode::WriteRequest.into())
642    }
643}
644
645impl<'a> TLVWrite for WriteSenderSlot<'a> {
646    type Position = usize;
647
648    fn write(&mut self, byte: u8) -> Result<(), Error> {
649        let payload = self.tx.payload();
650        if self.cursor >= payload.len() {
651            return Err(ErrorCode::NoSpace.into());
652        }
653        payload[self.cursor] = byte;
654        self.cursor += 1;
655        Ok(())
656    }
657
658    fn get_tail(&self) -> Self::Position {
659        self.cursor
660    }
661
662    fn rewind_to(&mut self, pos: Self::Position) {
663        self.cursor = pos;
664    }
665}
666
667impl<'a> core::fmt::Debug for WriteSenderSlot<'a> {
668    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
669        write!(f, "WriteSenderSlot({})", self.cursor)
670    }
671}
672
673#[cfg(feature = "defmt")]
674impl<'a> defmt::Format for WriteSenderSlot<'a> {
675    fn format(&self, f: defmt::Formatter<'_>) {
676        defmt::write!(f, "WriteSenderSlot({})", self.cursor)
677    }
678}
679
680/// Handle to the (single, non-chunked) response of a `write`
681/// transaction. Returned by [`WriteSender::tx`] once the peer has
682/// ACK-ed the request and the response is parsed.
683///
684/// Unlike [`InvokeRespChunk`] / [`ReadRespChunk`], `WriteResponse`
685/// is a single message per Matter Core spec — no chunk
686/// iteration is needed; just call [`response`](Self::response) to
687/// inspect the parsed [`WriteResp`].
688pub struct WriteRespHandle<'a> {
689    exchange: Exchange<'a>,
690}
691
692impl<'a> WriteRespHandle<'a> {
693    async fn receive(mut exchange: Exchange<'a>) -> Result<Self, Error> {
694        exchange.recv_fetch().await?;
695        {
696            let rx = exchange.rx()?;
697            check_opcode(rx.meta().proto_opcode, OpCode::WriteResponse)?;
698        }
699        // ACK here (via standalone `acknowledge()`, not `send_with`)
700        // because `send_with` would clear the RX buffer and break
701        // zero-copy access from `response()` below.
702        exchange.acknowledge().await?;
703        Ok(Self { exchange })
704    }
705
706    /// Borrowed access to the parsed `WriteResp`. The returned value
707    /// points into the exchange's RX buffer, which stays valid until
708    /// this handle is dropped.
709    pub fn response(&self) -> Result<WriteResp<'_>, Error> {
710        let rx = self.exchange.rx()?;
711        WriteResp::from_tlv(&TLVElement::new(rx.payload()))
712    }
713}
714
715// =====================================================================
716// Transaction types for the `invoke` opcode.
717//
718// `InvokeSender` is the cornerstone of the closure-free, scratch-buffer-
719// free IM client. It owns the exchange end-to-end (via `OwnedSender`
720// internally) and exposes a `tx().await` method that drives one
721// round of the MRP retransmit loop. The user matches on the result:
722//
723// - `TxOutcome::BuildRequest(builder)` → (re-)build the request bytes via the
724//   typestate builder; `.end()` returns the `InvokeSender` back for the
725//   next round.
726// - `TxOutcome::GotResponse(chunk)` → the request has been ACK-ed; here's
727//   the first response chunk. Iterate via `chunk.complete().await`.
728//
729// Closure-based and scratch-buffer-based variants
730// will be layered on top, mirroring how `Exchange::send_with` and
731// `Exchange::send` are layered on top of `Exchange::sender`.
732// =====================================================================
733
734/// Cornerstone `invoke` transaction. See module docs for the
735/// pattern. Returned by [`ImClient::invoke_sender`].
736///
737/// Public surface is intentionally narrow: a single async
738/// [`tx`](Self::tx) method that drives one round of the MRP loop.
739/// The TLV-serialization plumbing the codegen request builder uses
740/// to fill the request bytes lives in a separate
741/// [`InvokeSenderSlot`] type accessed via [`TLVBuilderParent::writer`],
742/// so that `u8` / `start_struct` / etc. don't appear directly on
743/// `InvokeSender` and tempt users to drive the TX buffer by hand.
744pub struct InvokeSender<'a> {
745    state: InvokeSenderState<'a>,
746}
747
748enum InvokeSenderState<'a> {
749    /// Between rounds: own a sender, no slot. The first `tx()` call
750    /// from this state acquires a slot and hands back a builder. The
751    /// `n`-th call (n ≥ 1) here means the previous round's bytes are
752    /// in flight; we wait for the next framework event.
753    Ready(OwnedSender<'a>),
754    /// During build: own a fully-prepared [`InvokeSenderSlot`] (TX slot
755    /// plus cursor) that the codegen builder writes into via
756    /// [`TLVBuilderParent::writer`]. The next `tx()` call commits
757    /// `slot`'s bytes via [`OwnedSenderTx::complete`] and transitions
758    /// back to `Ready`.
759    Slot(InvokeSenderSlot<'a>),
760}
761
762impl<'a> InvokeSender<'a> {
763    /// Drive one round of the MRP retransmit loop.
764    ///
765    /// - Returns `TxOutcome::BuildRequest(builder)` when the framework needs
766    ///   the request bytes (re-)built into a fresh TX slot. The
767    ///   builder's `P` parent is this `InvokeSender`; calling `.end()`
768    ///   on the message builder hands the `InvokeSender` back, ready
769    ///   for the next `tx()` call.
770    /// - Returns `TxOutcome::GotResponse(chunk)` once the framework has
771    ///   received the peer's ACK; iterate the chunk loop via
772    ///   [`InvokeRespChunk::complete`].
773    ///
774    /// The first call after [`ImClient::invoke_sender`] is guaranteed
775    /// to yield `TxOutcome::BuildRequest(builder)` because no message has been sent yet.
776    pub async fn tx(
777        mut self,
778    ) -> Result<TxOutcome<InvReqBuilder<InvokeSender<'a>>, InvokeRespChunk<'a>>, Error> {
779        // 1. If we're in Slot state, commit the bytes we just built.
780        let sender = match self.state {
781            InvokeSenderState::Slot(slot) => slot.commit()?,
782            InvokeSenderState::Ready(s) => s,
783        };
784
785        // 2. Ask the framework for the next event.
786        match sender.tx().await? {
787            Either::Left(tx) => {
788                // Re-build needed (initial or retransmit). Move to
789                // Slot state and hand back a fresh builder.
790                self.state = InvokeSenderState::Slot(InvokeSenderSlot { tx, cursor: 0 });
791                let builder = InvReqBuilder::new(self, &TLVTag::Anonymous)?;
792                Ok(TxOutcome::BuildRequest(builder))
793            }
794            Either::Right(exchange) => {
795                // ACK received — fetch and parse the first response chunk.
796                Ok(TxOutcome::GotResponse(
797                    InvokeRespChunk::receive(exchange).await?,
798                ))
799            }
800        }
801    }
802}
803
804impl<'a> TLVBuilderParent for InvokeSender<'a> {
805    type Write = InvokeSenderSlot<'a>;
806
807    fn writer(&mut self) -> &mut Self::Write {
808        match &mut self.state {
809            InvokeSenderState::Slot(slot) => slot,
810            // The only way to reach `writer()` on an `InvokeSender` is
811            // through the codegen builder constructed inside
812            // [`InvokeSender::tx`]'s `TxOutcome::BuildRequest` arm, which transitions to
813            // `Slot` state before yielding the builder. Hitting this
814            // branch means the invariant was violated externally —
815            // panic rather than corrupt the TX buffer silently.
816            InvokeSenderState::Ready(_) => panic!(
817                "InvokeSender::writer() called outside the build phase \
818                 (state = Ready); only reachable via an InvReqBuilder \
819                 yielded by InvokeSender::tx — see module docs."
820            ),
821        }
822    }
823}
824
825impl<'a> core::fmt::Debug for InvokeSender<'a> {
826    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
827        write!(f, "InvokeSender")
828    }
829}
830
831#[cfg(feature = "defmt")]
832impl<'a> defmt::Format for InvokeSender<'a> {
833    fn format(&self, f: defmt::Formatter<'_>) {
834        defmt::write!(f, "InvokeSender")
835    }
836}
837
838/// Internal serialization handle for the in-flight build of an
839/// [`InvokeSender`].
840///
841/// This type only exists because [`TLVBuilderParent`] requires the
842/// `Write` associated type to be a named type (so the codegen request
843/// builders can write through it). Users should not interact with it
844/// directly — go through [`InvokeSender::tx`] and the typed
845/// [`InvReqBuilder`] it returns.
846///
847/// Fields are private to enforce that the only way to drive
848/// `cursor` forward is by writing TLV through the
849/// [`TLVWrite`] impl below.
850pub struct InvokeSenderSlot<'a> {
851    tx: OwnedSenderTx<'a>,
852    cursor: usize,
853}
854
855impl<'a> InvokeSenderSlot<'a> {
856    /// Consume the slot — commit the bytes accumulated in `cursor`
857    /// via [`OwnedSenderTx::complete`] and return the
858    /// [`OwnedSender`] for the next retransmit-loop iteration.
859    fn commit(self) -> Result<OwnedSender<'a>, Error> {
860        self.tx
861            .complete(0, self.cursor, OpCode::InvokeRequest.into())
862    }
863}
864
865impl<'a> TLVWrite for InvokeSenderSlot<'a> {
866    type Position = usize;
867
868    fn write(&mut self, byte: u8) -> Result<(), Error> {
869        let payload = self.tx.payload();
870        if self.cursor >= payload.len() {
871            return Err(ErrorCode::NoSpace.into());
872        }
873        payload[self.cursor] = byte;
874        self.cursor += 1;
875        Ok(())
876    }
877
878    /// Byte offset into the active TX-slot payload at which the next
879    /// `write()` would land. Used by the derived `ToTLV` impls (and
880    /// similar helpers) to mark a rollback anchor before composing a
881    /// TLV structure.
882    fn get_tail(&self) -> Self::Position {
883        self.cursor
884    }
885
886    /// Roll the cursor back to a position previously returned by
887    /// [`get_tail`]. Used by derived `ToTLV` impls to unwind a
888    /// partially-written TLV structure on error.
889    fn rewind_to(&mut self, pos: Self::Position) {
890        self.cursor = pos;
891    }
892}
893
894impl<'a> core::fmt::Debug for InvokeSenderSlot<'a> {
895    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
896        write!(f, "InvokeSenderSlot({})", self.cursor)
897    }
898}
899
900#[cfg(feature = "defmt")]
901impl<'a> defmt::Format for InvokeSenderSlot<'a> {
902    fn format(&self, f: defmt::Formatter<'_>) {
903        defmt::write!(f, "InvokeSenderSlot({})", self.cursor)
904    }
905}
906
907/// First (possibly only) response chunk of an `invoke`
908/// transaction. Returned by [`InvokeSender::tx`] once the peer has
909/// ACK-ed the request.
910///
911/// The borrowed [`response`](Self::response) method gives zero-copy
912/// access to the parsed [`InvokeResp`] backed by the exchange's RX
913/// buffer; the buffer stays valid until [`complete`](Self::complete)
914/// is called.
915///
916/// For multi-chunk responses (the server signals `more_chunks=true`),
917/// [`complete`](Self::complete) returns `Some(next_chunk)` so the
918/// caller can iterate; otherwise it returns `None` and drops the
919/// exchange.
920///
921/// Per Matter Core spec, a server MAY reply to a command
922/// declared with `DefaultSuccess` (no explicit response struct) by
923/// sending a plain `StatusResponse(Success)` instead of a full
924/// `InvokeResponse`. In that case the chunk is *status-only*:
925/// [`response`](Self::response) returns `None`, and
926/// [`complete`](Self::complete) is terminal (returns `None`).
927pub struct InvokeRespChunk<'a> {
928    exchange: Exchange<'a>,
929    /// `true` when the peer replied with `StatusResponse(Success)`
930    /// instead of a real `InvokeResponse` (DefaultSuccess commands).
931    status_only: bool,
932}
933
934impl<'a> InvokeRespChunk<'a> {
935    async fn receive(mut exchange: Exchange<'a>) -> Result<Self, Error> {
936        exchange.recv_fetch().await?;
937        let opcode = exchange.rx()?.meta().proto_opcode;
938
939        if opcode == OpCode::InvokeResponse as u8 {
940            Ok(Self {
941                exchange,
942                status_only: false,
943            })
944        } else if opcode == OpCode::StatusResponse as u8 {
945            // DefaultSuccess command — server replied with a plain
946            // StatusResponse. Translate non-Success codes to errors;
947            // otherwise treat as an empty (status-only) chunk.
948            let status = {
949                let rx = exchange.rx()?;
950                let element = TLVElement::new(rx.payload());
951                StatusResp::from_tlv(&element)?.status
952            };
953            if status == IMStatusCode::Success {
954                Ok(Self {
955                    exchange,
956                    status_only: true,
957                })
958            } else {
959                error!("Invoke reply: StatusResponse({:?})", status);
960                Err(status.to_error_code().unwrap_or(ErrorCode::Failure).into())
961            }
962        } else {
963            Err(ErrorCode::InvalidOpcode.into())
964        }
965    }
966
967    /// Whether the peer replied with `StatusResponse(Success)`
968    /// (DefaultSuccess command) rather than a real `InvokeResponse`.
969    /// In that case [`response`](Self::response) returns `None`.
970    pub fn is_status_only(&self) -> bool {
971        self.status_only
972    }
973
974    /// Borrowed access to the parsed `InvokeResp` for this chunk —
975    /// `None` if the chunk is status-only (see [`is_status_only`]).
976    /// The returned value points into the exchange's RX buffer, so
977    /// its lifetime is the borrow of this `InvokeRespChunk`.
978    pub fn response(&self) -> Result<Option<InvokeResp<'_>>, Error> {
979        if self.status_only {
980            return Ok(None);
981        }
982        let rx = self.exchange.rx()?;
983        let element = TLVElement::new(rx.payload());
984        InvokeResp::from_tlv(&element).map(Some)
985    }
986
987    /// ACK the current chunk and, if the server signalled
988    /// `more_chunks=true`, fetch + parse the next chunk and return
989    /// it as `Some(next)`. Otherwise (final chunk, or status-only)
990    /// drop the exchange and return `None`.
991    ///
992    /// Chunking flow control (Matter Core): on receipt of
993    /// any `InvokeResponseMessage` with `MoreChunkedResponses=true`,
994    /// the receiver SHALL reply with `StatusResponse(Success)` and
995    /// the sender SHALL await that ACK before transmitting the next
996    /// chunk. On the **final** chunk (`MoreChunks=false`) no trailer
997    /// is sent — per spec the `SuppressResponse` field on an
998    /// `InvokeResponseMessage` is *ignored by the client*, and Matter
999    /// "does not support responses to InvokeResponse actions" at the
1000    /// action layer. So the terminal-chunk branch is MRP-ack only,
1001    /// regardless of the `SuppressResponse` value the server echoed.
1002    pub async fn complete(mut self) -> Result<Option<Self>, Error> {
1003        if self.status_only {
1004            // Status-only chunks are terminal — no chunking, no
1005            // additional StatusResponse round-trip needed. Just ACK
1006            // the message at the MRP layer and we're done.
1007            self.exchange.acknowledge().await?;
1008            return Ok(None);
1009        }
1010
1011        let (more_chunks, suppress_response) = {
1012            let resp = self
1013                .response()?
1014                .expect("status_only checked above; response() must be Some");
1015            (
1016                resp.more_chunks.unwrap_or(false),
1017                resp.suppress_response.unwrap_or(false),
1018            )
1019        };
1020
1021        if more_chunks {
1022            // If MoreChunkedMessages is true,
1023            // SuppressResponse SHALL be false. A peer that
1024            // violates this is malformed — abort the chain.
1025            if suppress_response {
1026                send_abort(&mut self.exchange).await?;
1027                return Err(ErrorCode::InvalidData.into());
1028            }
1029
1030            // Per the spec — flow-control ACK between chunks.
1031            self.exchange
1032                .send_with(|_, wb| {
1033                    StatusResp::write(wb, IMStatusCode::Success)?;
1034                    Ok(Some(OpCode::StatusResponse.into()))
1035                })
1036                .await?;
1037
1038            self.exchange.recv_fetch().await?;
1039            {
1040                let rx = self.exchange.rx()?;
1041                check_opcode(rx.meta().proto_opcode, OpCode::InvokeResponse)?;
1042            }
1043
1044            Ok(Some(self))
1045        } else {
1046            // Final (or only) chunk. Per the spec, Matter does not
1047            // support responses to InvokeResponse actions — the
1048            // SuppressResponse field is ignored by the client. MRP
1049            // -ack only.
1050            self.exchange.acknowledge().await?;
1051            Ok(None)
1052        }
1053    }
1054}
1055
1056// =====================================================================
1057// Transaction types for the `subscribe` opcode.
1058//
1059// On the wire the establishment of a subscription is:
1060//   1. Client → SubscribeRequest
1061//   2. Server → ReportData (priming, with `more_chunks=true` until
1062//      the last chunk has `more_chunks=false`); client ACKs each
1063//      with `StatusResponse(Success)`
1064//   3. Server → SubscribeResponse (carries `subscription_id` and
1065//      the chosen `max_int`)
1066//
1067// `SubscribeSender` drives the request side; `SubscribePrimingChunk`
1068// owns the response stream during priming. The terminal
1069// `complete()` returns either another priming chunk (more reports
1070// coming) or `SubscribeEstablished` carrying the subscription id /
1071// max interval. The exchange is dropped at that point; ongoing
1072// (post-establishment) report messages arrive on server-initiated
1073// exchanges and require a separate listening abstraction.
1074// =====================================================================
1075
1076/// Cornerstone `subscribe` transaction. See module docs for the
1077/// pattern. Returned by [`ImClient::subscribe_sender`].
1078pub struct SubscribeSender<'a> {
1079    state: SubscribeSenderState<'a>,
1080}
1081
1082enum SubscribeSenderState<'a> {
1083    Ready(OwnedSender<'a>),
1084    Slot(SubscribeSenderSlot<'a>),
1085}
1086
1087impl<'a> SubscribeSender<'a> {
1088    /// Drive one round of the MRP retransmit loop. Same shape as
1089    /// [`ReadSender::tx`] except the right arm holds a
1090    /// [`SubscribePrimingChunk`] (the first priming `ReportData`).
1091    pub async fn tx(
1092        mut self,
1093    ) -> Result<TxOutcome<SubscribeReqBuilder<SubscribeSender<'a>>, SubscribePrimingChunk<'a>>, Error>
1094    {
1095        let sender = match self.state {
1096            SubscribeSenderState::Slot(slot) => slot.commit()?,
1097            SubscribeSenderState::Ready(s) => s,
1098        };
1099
1100        match sender.tx().await? {
1101            Either::Left(tx) => {
1102                self.state = SubscribeSenderState::Slot(SubscribeSenderSlot { tx, cursor: 0 });
1103                let builder = SubscribeReqBuilder::new(self, &TLVTag::Anonymous)?;
1104                Ok(TxOutcome::BuildRequest(builder))
1105            }
1106            Either::Right(exchange) => Ok(TxOutcome::GotResponse(
1107                SubscribePrimingChunk::receive(exchange).await?,
1108            )),
1109        }
1110    }
1111}
1112
1113impl<'a> TLVBuilderParent for SubscribeSender<'a> {
1114    type Write = SubscribeSenderSlot<'a>;
1115
1116    fn writer(&mut self) -> &mut Self::Write {
1117        match &mut self.state {
1118            SubscribeSenderState::Slot(slot) => slot,
1119            SubscribeSenderState::Ready(_) => panic!(
1120                "SubscribeSender::writer() called outside the build phase — \
1121                 only reachable via a SubscribeReqBuilder yielded by SubscribeSender::tx."
1122            ),
1123        }
1124    }
1125}
1126
1127impl<'a> core::fmt::Debug for SubscribeSender<'a> {
1128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1129        write!(f, "SubscribeSender")
1130    }
1131}
1132
1133#[cfg(feature = "defmt")]
1134impl<'a> defmt::Format for SubscribeSender<'a> {
1135    fn format(&self, f: defmt::Formatter<'_>) {
1136        defmt::write!(f, "SubscribeSender")
1137    }
1138}
1139
1140/// Internal serialization handle for the in-flight build of a
1141/// [`SubscribeSender`]. Same role as [`InvokeSenderSlot`] —
1142/// see its docs for why this type exists.
1143pub struct SubscribeSenderSlot<'a> {
1144    tx: OwnedSenderTx<'a>,
1145    cursor: usize,
1146}
1147
1148impl<'a> SubscribeSenderSlot<'a> {
1149    fn commit(self) -> Result<OwnedSender<'a>, Error> {
1150        self.tx
1151            .complete(0, self.cursor, OpCode::SubscribeRequest.into())
1152    }
1153}
1154
1155impl<'a> TLVWrite for SubscribeSenderSlot<'a> {
1156    type Position = usize;
1157
1158    fn write(&mut self, byte: u8) -> Result<(), Error> {
1159        let payload = self.tx.payload();
1160        if self.cursor >= payload.len() {
1161            return Err(ErrorCode::NoSpace.into());
1162        }
1163        payload[self.cursor] = byte;
1164        self.cursor += 1;
1165        Ok(())
1166    }
1167
1168    fn get_tail(&self) -> Self::Position {
1169        self.cursor
1170    }
1171
1172    fn rewind_to(&mut self, pos: Self::Position) {
1173        self.cursor = pos;
1174    }
1175}
1176
1177impl<'a> core::fmt::Debug for SubscribeSenderSlot<'a> {
1178    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1179        write!(f, "SubscribeSenderSlot({})", self.cursor)
1180    }
1181}
1182
1183#[cfg(feature = "defmt")]
1184impl<'a> defmt::Format for SubscribeSenderSlot<'a> {
1185    fn format(&self, f: defmt::Formatter<'_>) {
1186        defmt::write!(f, "SubscribeSenderSlot({})", self.cursor)
1187    }
1188}
1189
1190/// First (possibly only) priming `ReportData` chunk of a subscribe
1191/// transaction. Returned by [`SubscribeSender::tx`] once the peer has
1192/// ACK-ed the `SubscribeRequest` and the first `ReportData` is
1193/// parsed. Same `response()` shape as [`ReadRespChunk`].
1194///
1195/// Walk the priming sequence — and pick up the final
1196/// [`SubscribeEstablished`] — via [`Self::complete`].
1197pub struct SubscribePrimingChunk<'a> {
1198    exchange: Exchange<'a>,
1199}
1200
1201impl<'a> core::fmt::Debug for SubscribePrimingChunk<'a> {
1202    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1203        write!(f, "SubscribePrimingChunk")
1204    }
1205}
1206
1207#[cfg(feature = "defmt")]
1208impl<'a> defmt::Format for SubscribePrimingChunk<'a> {
1209    fn format(&self, f: defmt::Formatter<'_>) {
1210        defmt::write!(f, "SubscribePrimingChunk")
1211    }
1212}
1213
1214impl<'a> SubscribePrimingChunk<'a> {
1215    async fn receive(mut exchange: Exchange<'a>) -> Result<Self, Error> {
1216        exchange.recv_fetch().await?;
1217        {
1218            let rx = exchange.rx()?;
1219            check_opcode(rx.meta().proto_opcode, OpCode::ReportData)?;
1220        }
1221        Ok(Self { exchange })
1222    }
1223
1224    /// Borrowed access to the parsed `ReportDataResp` for this
1225    /// priming chunk. The returned value points into the exchange's
1226    /// RX buffer; its lifetime is the borrow of this chunk.
1227    pub fn response(&self) -> Result<ReportDataResp<'_>, Error> {
1228        let rx = self.exchange.rx()?;
1229        let element = TLVElement::new(rx.payload());
1230        ReportDataResp::from_tlv(&element)
1231    }
1232
1233    /// ACK the current priming chunk and advance to the next stage:
1234    ///
1235    /// - If the chunk's `more_chunks=true`: send
1236    ///   `StatusResponse(Success)`, fetch the next priming
1237    ///   `ReportData`, and return `Ok(NextChunk(self))`.
1238    /// - If `more_chunks=false`: send the trailing
1239    ///   `StatusResponse(Success)`, then await + parse the peer's
1240    ///   `SubscribeResponse`, and return `Ok(Established(...))` with
1241    ///   the subscription id and chosen max interval.
1242    /// - If the priming stream is aborted (peer sends
1243    ///   `StatusResponse(non-Success)` instead of either `ReportData`
1244    ///   or `SubscribeResponse`), return `Err`.
1245    pub async fn complete(mut self) -> Result<SubscribeOutcome<'a>, Error> {
1246        let (more_chunks, suppress_response) = {
1247            let resp = self.response()?;
1248            (
1249                resp.more_chunks.unwrap_or(false),
1250                resp.suppress_response.unwrap_or(false),
1251            )
1252        };
1253
1254        if more_chunks {
1255            // Spec forbids suppress_response=true alongside
1256            // more_chunks=true (same constraint as ReadRespChunk).
1257            if suppress_response {
1258                send_abort(&mut self.exchange).await?;
1259                return Err(ErrorCode::InvalidData.into());
1260            }
1261
1262            // ACK with StatusResponse(Success), fetch next ReportData.
1263            self.exchange
1264                .send_with(|_, wb| {
1265                    StatusResp::write(wb, IMStatusCode::Success)?;
1266                    Ok(Some(OpCode::StatusResponse.into()))
1267                })
1268                .await?;
1269
1270            self.exchange.recv_fetch().await?;
1271            {
1272                let rx = self.exchange.rx()?;
1273                check_opcode(rx.meta().proto_opcode, OpCode::ReportData)?;
1274            }
1275
1276            Ok(SubscribeOutcome::NextChunk(self))
1277        } else {
1278            // Last priming ReportData. Send the trailing
1279            // StatusResponse(Success) (unless the server explicitly
1280            // suppressed it — unusual for subscribe but legal) and
1281            // wait for the peer's SubscribeResponse.
1282            if !suppress_response {
1283                self.exchange
1284                    .send_with(|_, wb| {
1285                        StatusResp::write(wb, IMStatusCode::Success)?;
1286                        Ok(Some(OpCode::StatusResponse.into()))
1287                    })
1288                    .await?;
1289            }
1290
1291            self.exchange.recv_fetch().await?;
1292            let opcode = self.exchange.rx()?.meta().proto_opcode;
1293
1294            if opcode == OpCode::SubscribeResponse as u8 {
1295                let (subscription_id, max_int) = {
1296                    let rx = self.exchange.rx()?;
1297                    let resp = SubscribeResp::from_tlv(&TLVElement::new(rx.payload()))?;
1298                    (resp.subs_id, resp.max_int)
1299                };
1300                // ACK the SubscribeResponse at the MRP layer. After
1301                // this the establishment exchange is terminal; the
1302                // ongoing subscription lives on the (fab, peer, sub_id)
1303                // triple via server-initiated future exchanges.
1304                self.exchange.acknowledge().await?;
1305                Ok(SubscribeOutcome::Established(SubscribeEstablished {
1306                    subscription_id,
1307                    max_int,
1308                }))
1309            } else if opcode == OpCode::StatusResponse as u8 {
1310                // Peer aborted the establishment after the last
1311                // priming chunk — e.g. ran out of subscription
1312                // slots. Translate the status into an Error.
1313                let status = {
1314                    let rx = self.exchange.rx()?;
1315                    StatusResp::from_tlv(&TLVElement::new(rx.payload()))?.status
1316                };
1317                self.exchange.acknowledge().await?;
1318                error!(
1319                    "Subscribe establishment aborted: StatusResponse({:?})",
1320                    status
1321                );
1322                Err(status.to_error_code().unwrap_or(ErrorCode::Failure).into())
1323            } else {
1324                Err(ErrorCode::InvalidOpcode.into())
1325            }
1326        }
1327    }
1328}
1329
1330/// What [`SubscribePrimingChunk::complete`] returns: either the
1331/// next priming chunk in the sequence, or the terminal
1332/// [`SubscribeEstablished`] carrying the negotiated subscription
1333/// identity.
1334#[derive(Debug)]
1335#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1336pub enum SubscribeOutcome<'a> {
1337    /// More priming `ReportData` chunks coming — process this one
1338    /// and call `complete()` again on it.
1339    NextChunk(SubscribePrimingChunk<'a>),
1340    /// Establishment complete: subscription is active on the peer.
1341    /// The exchange is no longer needed (it has been dropped); the
1342    /// `(fabric, peer_node_id, subscription_id)` triple identifies
1343    /// the subscription for any server-initiated future reports.
1344    Established(SubscribeEstablished),
1345}
1346
1347/// Result of a successful subscribe-establishment: the
1348/// subscription-identifier issued by the peer plus the maximum
1349/// reporting interval (seconds) the peer committed to.
1350#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1351#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1352pub struct SubscribeEstablished {
1353    /// Subscription identifier chosen by the peer (Matter Core spec).
1354    /// Combined with the accessing fabric and the peer
1355    /// node id, this is the lookup key for the active subscription.
1356    pub subscription_id: u32,
1357    /// Maximum reporting interval (seconds) the peer committed to.
1358    /// The peer MUST report no less frequently than this — see
1359    /// Matter Core spec. Use this to drive a watchdog if the
1360    /// caller wants to detect a silently-broken subscription.
1361    pub max_int: u16,
1362}
1363
1364// =====================================================================
1365// Module-private helpers shared by trait default impls.
1366//
1367// The IM-client trait below has default-impl methods that drive each
1368// IM transaction end-to-end. They share several response-loop bodies
1369// (chunked-response handling for read/invoke, single-response handling
1370// for write, the timed-request handshake, the abort path); those live
1371// here as freestanding `pub(crate)` fns rather than trait methods so
1372// that we don't have to expose them as required trait items the way
1373// trait inheritance would force.
1374// =====================================================================
1375
1376/// Send a timed-request handshake and wait for `StatusResponse(Success)`.
1377/// Used before timed writes/invokes.
1378async fn send_timed_request(exchange: &mut Exchange<'_>, timeout_ms: u16) -> Result<(), Error> {
1379    let req = TimedReq {
1380        timeout: timeout_ms,
1381        interaction_model_revision: Some(IM_REVISION),
1382    };
1383
1384    exchange
1385        .send_with(|_, wb| {
1386            req.to_tlv(&TagType::Anonymous, wb)?;
1387            Ok(Some(OpCode::TimedRequest.into()))
1388        })
1389        .await?;
1390
1391    exchange.recv_fetch().await?;
1392
1393    let rx = exchange.rx()?;
1394    check_opcode(rx.meta().proto_opcode, OpCode::StatusResponse)?;
1395
1396    let status_resp = StatusResp::from_tlv(&TLVElement::new(rx.payload()))?;
1397    if status_resp.status != IMStatusCode::Success {
1398        error!("TimedRequest failed with status: {:?}", status_resp.status);
1399        return Err(status_resp
1400            .status
1401            .to_error_code()
1402            .unwrap_or(ErrorCode::Failure)
1403            .into());
1404    }
1405
1406    Ok(())
1407}
1408
1409/// Abort a chunked transaction by sending `StatusResponse(Failure)`.
1410///
1411/// This tells the server we are not continuing the transaction, preventing
1412/// it from waiting indefinitely for the next `StatusResponse(Success)`.
1413async fn send_abort(exchange: &mut Exchange<'_>) -> Result<(), Error> {
1414    exchange
1415        .send_with(|_, wb| {
1416            StatusResp::write(wb, IMStatusCode::Failure)?;
1417            Ok(Some(OpCode::StatusResponse.into()))
1418        })
1419        .await
1420}
1421
1422/// Check that the received opcode matches the expected one.
1423fn check_opcode(received: u8, expected: OpCode) -> Result<(), Error> {
1424    if received != expected as u8 {
1425        error!(
1426            "Unexpected IM opcode: received {}, expected {:?}",
1427            received, expected
1428        );
1429        Err(ErrorCode::InvalidOpcode.into())
1430    } else {
1431        Ok(())
1432    }
1433}