matter_exchange.h
the unsecured exchange PASE runs on.
Overview
the unsecured exchange PASE runs on. Between BTP (a byte pipe) and PASE (five messages) sits the part that makes a Matter message a message: which session it belongs to, which exchange, whether it is a duplicate, and whether the peer is owed an acknowledgement. in message header | protocol header | payload out message header | protocol header | payload This handles exactly one exchange on the UNSECURED session, which is all commissioning needs before PASE finishes: session id 0, no encryption, the peer as initiator and this node as responder. Secure sessions are a different object -- they carry keys and a different counter -- and arrive with CASE. It deliberately does not know what PASE is. It reports the opcode and hands back the payload; the caller decides what to answer. That keeps the framing testable on its own, and means CASE will reuse it rather than fork it. No timers here either. Duplicate suppression and the ack bookkeeping are state, not scheduling; retransmission is matter_mrp.h's, driven by whoever owns a clock.
depends on matter_crypto.h matter_mrp.h matter_msg.h matter_status.h · used by matter_exchange.c
API
Cstruct matter_exchange
Matter exchange state including secure session keys, local and peer session IDs, MRP settings, exchange ID tracking, message counters, and acknowledgement tracking for a single commissioner session.
Cstruct matter_exchange_in
What a received message turned out to be. @ref opcode, @ref protocol_id, @ref exchange_id and @ref initiator are set even when matter_exchange_recv() goes on to REFUSE the message, so a caller can log what it turned away. The rest is meaningful only on MATTER_OK.
##define MATTER_PROTOCOL_SECURE_CHANNEL 0x0000u
Protocol 0x0000, vendor 0x0000. The only protocol this layer accepts.
##define MATTER_SESSION_ID_UNSECURED 0x0000u
Session id 0 is the unsecured session, by definition rather than by policy.
##define MATTER_EXCHANGE_HEADER_MAX (MATTER_MSG_HEADER_MAX + MATTER_PROTO_HEADER_MAX)
Worst-case bytes this prepends to a payload. Both headers at their largest. Real unsecured commissioning messages are far smaller -- 8 + 6 with an ack, no node ids -- but a caller sizing a buffer should not have to know that.
##define MATTER_PROTOCOL_INTERACTION_MODEL 0x0001u
Protocol 0x0001. Everything after commissioning's Secure Channel phase.
##define MATTER_PASE_NODE_ID 0ULL
PASE sessions have no operational identity, so the AEAD nonce carries node id zero in both directions (SecureSession.h:337, kUndefinedNodeId). CASE brings real node ids and will need them here.
Fvoid matter_exchange_init(struct matter_exchange *x, uint32_t entropy, bool mrp)
@param entropy a random word seeding the outbound counter; see matter_counter_init(). Unsecured counters wrap, so this is about not advertising uptime, not about safety. @param mrp false for BLE, true for UDP. See struct matter_exchange::mrp -- this is a property of the transport, not a preference.
Fint matter_exchange_recv(struct matter_exchange *x, const uint8_t *msg, size_t len, struct matter_exchange_in *in, uint8_t *pt, size_t pt_cap)
Decode one received message. Everything here arrives from an unauthenticated peer -- the unsecured session is where a commissioner is still a stranger -- so the checks are exhaustive rather than trusting: version, DSIZ, session id, session type, protocol id, and the vendor flag are all refused rather than ignored when wrong. @param pt scratch for the plaintext of an encrypted message; in points into it. Needs len bytes. Unused, and may be NULL, while unsecured. @return MATTER_OK; MATTER_E_DUP when the counter has been seen, in which case the peer must still be acknowledged but in must NOT be acted on; MATTER_E_STATE for a message on another exchange that this node will not follow -- a second exchange on the UNSECURED session, which means a second commissioner, or one that arrives with I clear and so is a reply to nothing; MATTER_E_INVAL for a message this layer will not carry; or whatever the header decoders returned.
Fint matter_exchange_promote(struct matter_exchange *x, uint16_t local_id, uint16_t peer_id, const struct matter_session_keys *keys, uint32_t entropy)
Adopt the secure session PASE just established. After this, messages addressed to local_id are decrypted and anything on the unsecured session is refused: a peer that has keys has no business talking in the clear. The exchange id is deliberately released here. The commissioner opens a NEW exchange on the secure session -- PASE's exchange is finished -- so holding the old id would refuse the first real message. @param entropy seeds a FRESH counter. Secure sessions must not reuse the unsecured session's counter space: a repeated counter under the same key repeats an AEAD nonce.
Fvoid matter_exchange_set_op_node_ids(struct matter_exchange *x, uint64_t local, uint64_t peer)
Name the operational identities a CASE session's nonces are built from. Call after matter_exchange_promote() for a CASE session; PASE must not call it at all. The node ids travel in no header, so a wrong pair here produces messages that are byte-perfect and simply will not authenticate -- and the peer has nothing to report but silence. @param local this node's operational node id, used when sealing. @param peer the far side's, used when opening.
Fint matter_exchange_reply(struct matter_exchange *x, uint8_t opcode, const uint8_t *payload, size_t payload_len, uint8_t *out, size_t cap, size_t *out_len)
Frame a reply on this exchange. With MRP on, carries any outstanding acknowledgement and sets R so the peer acknowledges this in turn. With MRP off -- which is what BLE uses -- neither flag is ever set, because BTP is already reliable. @param out needs MATTER_EXCHANGE_HEADER_MAX + payload_len bytes. @return MATTER_OK, MATTER_E_NOSPACE, MATTER_E_STATE if no message has been received yet, or MATTER_E_INVAL.
Fint matter_exchange_send_initiator(struct matter_exchange *x, uint16_t exchange_id, uint16_t protocol_id, uint8_t opcode, const uint8_t *payload, size_t payload_len, uint8_t *out, size_t cap, size_t *out_len)
Send as the INITIATOR of a new exchange, on a session this node responds on. Everything this node sent until now answered something. A subscription does not work that way: after the priming report the SERVER is the one that has to speak, and a controller that gets no report shows the accessory as not responding however healthy the session is. That is the whole of why the Home tile spins on "Unlocking" while the invoke it sent was answered SUCCESS. The SESSION role is unchanged and that is what makes this cheap: keys stay role-relative to CASE (this node still encrypts with r2i), and the message counter is per-session, not per-exchange, so x's counter is still the right one. Only the EXCHANGE role differs. @param exchange_id chosen by this node, and its alone. NOT written back into x -- the peer-initiated exchange that x describes is still live and still owns x->exchange_id. No acknowledgement is ever piggybacked here. An ack names a counter WITHIN an exchange, so carrying the peer's pending ack out on a different exchange acknowledges a message that exchange never saw. @return MATTER_OK, or MATTER_E_STATE on a closed exchange, or MATTER_E_NOSPACE if cap cannot hold the framed result.
Fint matter_exchange_send(struct matter_exchange *x, uint16_t protocol_id, uint8_t opcode, const uint8_t *payload, size_t payload_len, uint8_t *out, size_t cap, size_t *out_len)
Fint matter_exchange_standalone_ack(struct matter_exchange *x, uint8_t *out, size_t cap, size_t *out_len)
Frame a bare acknowledgement, for when there is nothing to say yet. Matter's standalone ack: Secure Channel opcode 0x10, empty payload. Needed when a reply cannot be produced inside the peer's retransmission timer, and after the final message of an exchange. @return MATTER_OK; MATTER_E_STATE when nothing is pending, or whenever MRP is off, since then there is no such thing as an outstanding ack.
##define MATTER_SC_OP_ACK 0x10u
Secure Channel MsgType 0x10 (Constants.h). Not a PASE opcode.