matter_mrp.h
Message Reliability Protocol: backoff, retransmit, dedup.
Overview
Message Reliability Protocol: backoff, retransmit, dedup.
Matter runs over UDP, so reliability is the application's problem. MRP is the
answer: mark a message as needing an acknowledgement, retransmit on an
exponential backoff until it is acked, and drop counters you have already
seen.
Two objects with two different lifetimes, deliberately not merged:
struct matter_mrp_window per SESSION — duplicate suppression
struct matter_mrp per EXCHANGE — one un-acked message, one owed ack
NO TIMERS LIVE HERE. Every entry point takes now_ms and the object only
ever computes deadlines, so the caller owns the timer and this layer stays
testable on the host with a fake clock. That is also the stage 0 work-queue
constraint honoured by construction: a module that never arms a timer cannot
accidentally arm one on k_sys_work_q, which was measured at 3,568 of 4,096
bytes with the reader running.
depends on matter_status.h · used by matter_exchange.h matter_mrp.c
API
Cstruct matter_mrp_window
Replay window over a peer's message counters.
bitmap bit i records that counter max_counter - (i + 1) has been seen, so
a message up to MATTER_MRP_WINDOW_BITS behind the high-water mark is still
placed exactly. Anything older than that is refused, because there is no
longer any evidence either way and accepting is the unsafe guess.
Cstruct matter_mrp
Per-exchange reliability state. At most ONE un-acked message at a time, which is the protocol's own rule and not a simplification: MRP has no send window, so an exchange with a message in flight must wait (CircuitMatter enforces the same thing at exchange.py:67-68). The exchange layer owns the message bytes; this struct holds only the counter and the deadlines.
##define MATTER_MRP_MARGIN_NUM 1127u
##define MATTER_MRP_MARGIN_DEN 1024u
##define MATTER_MRP_BASE_NUM 16u
##define MATTER_MRP_BASE_DEN 10u
##define MATTER_MRP_JITTER_BASE 1024u
Jitter is (JITTER_BASE + rand[0,255]) / JITTER_BASE, i.e. 1.0 .. 1.249.
##define MATTER_MRP_THRESHOLD 1u
Retransmissions before the backoff starts growing.
##define MATTER_MRP_MAX_EXP 4u
CHIP caps the exponent here, "reasonable maximum after 5 tries".
##define MATTER_MRP_MAX_TRANSMISSIONS 5u
Total transmissions including the first: 1 + CHIP's MAX_RETRANS of 4.
##define MATTER_MRP_IDLE_INTERVAL_MS 500u
Peer retransmit intervals advertised by default (ms).
##define MATTER_MRP_ACTIVE_INTERVAL_MS 300u
##define MATTER_MRP_STANDALONE_ACK_MS 200u
Grace period to piggyback an ack on a real outbound message before giving up and sending a bare one (ReliableMessageProtocolConfig.h:95, exchange.py:29).
##define MATTER_MRP_SENDER_BOOST_MS 1500u
Flat addition to every retransmit deadline on a Thread transport (ReliableMessageProtocolConfig.h:178-184: 1500 ms when CHIP_ENABLE_OPENTHREAD and not Linux, 0 otherwise). It is separate from matter_mrp_backoff_ms() because the spec formula does not contain it -- it is CHIP's allowance for mesh latency, applied where the transport is known. This node is a Thread node, so matter_mrp_arm() adds it.
##define MATTER_MRP_WINDOW_BITS 32u
Counter window width in bits (CHIPConfig.h:309). One uint32_t.
Fuint32_t matter_mrp_backoff_ms(uint32_t base_ms, uint8_t send_count, uint8_t jitter)
Delay before the n'th retransmission of a message already sent send_count times, per Matter Core 4.12.2.1: i = MRP_BACKOFF_MARGIN * base t = i * MRP_BACKOFF_BASE^max(0, n - MRP_BACKOFF_THRESHOLD) t = t * (1.0 + random(0,1) * MRP_BACKOFF_JITTER) @param base_ms peer's advertised idle or active retransmit interval. @param send_count transmissions ALREADY made, so 1 after the first send. Zero is treated as 1; there is no backoff before a first send. @param jitter one random byte. Pass 0 for a deterministic schedule. @return delay in ms, saturating at UINT32_MAX. Excludes SENDER_BOOST.
Fvoid matter_mrp_window_init(struct matter_mrp_window *w)
Fint matter_mrp_window_check(const struct matter_mrp_window *w, uint32_t counter)
Test a counter WITHOUT recording it. Split from commit on purpose. The counter is only trustworthy once the message it rode in on has been authenticated, so the caller must check, decrypt, and only then commit. Committing first would let anyone who can put a UDP datagram on the mesh drag max_counter forward and lock out the real peer. @return MATTER_OK if new, MATTER_E_DUP if already seen or too old.
Fvoid matter_mrp_window_commit(struct matter_mrp_window *w, uint32_t counter)
Record an authenticated counter, advancing the window if it is ahead.
Cenum matter_mrp_action
What matter_mrp_poll() says is due now.
Fvoid matter_mrp_init(struct matter_mrp *m, uint32_t base_ms)
@param base_ms peer's retransmit interval, e.g. MATTER_MRP_ACTIVE_INTERVAL_MS.
Fint matter_mrp_arm(struct matter_mrp *m, uint32_t counter, uint32_t now_ms, uint8_t jitter)
Register a reliable message as sent and set its retransmit deadline. Call after every transmission of it, the first included: each call bumps send_count, which is what grows the backoff. @return MATTER_OK, or MATTER_E_STATE if a DIFFERENT counter is already in flight, since MRP permits only one.
Fint matter_mrp_on_ack(struct matter_mrp *m, uint32_t counter)
Apply a received acknowledgement. @return MATTER_OK if it cleared the in-flight message, MATTER_E_STATE if nothing was in flight, MATTER_E_INVAL if it acked a different counter (stale or forged -- either way the in-flight message stays pending).
Fvoid matter_mrp_on_reliable_recv(struct matter_mrp *m, uint32_t counter, uint32_t now_ms)
Note an inbound message that requested an acknowledgement, starting the piggyback grace period.
Fbool matter_mrp_take_ack(struct matter_mrp *m, uint32_t *counter)
Claim the owed ack so an outbound message can carry it, clearing the pending standalone. @return true if there was one, and *counter is set.
Fenum matter_mrp_action matter_mrp_poll(struct matter_mrp *m, uint32_t now_ms, uint32_t *counter)
What is due at now_ms. @param counter receives the message counter the action refers to.
Fbool matter_mrp_next_deadline(const struct matter_mrp *m, uint32_t *out_ms)
Deadline of the soonest pending event, for arming one timer. @param out_ms receives an absolute ms deadline, untouched when false. @return false when nothing is pending and no timer is needed.