Skip to main content

matc/clusters/codec/
web_rtc_provider.rs

1//! Matter TLV encoders and decoders for WebRTC Transport Provider Cluster
2//! Cluster ID: 0x0553
3//!
4//! This file is automatically generated from WebRTC_Provider.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Struct definitions
17
18#[derive(Debug, serde::Serialize)]
19pub struct SFrame {
20    pub cipher_suite: Option<u16>,
21    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
22    pub base_key: Option<Vec<u8>>,
23    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
24    pub kid: Option<Vec<u8>>,
25}
26
27// Command encoders
28
29/// Parameters for SolicitOffer command
30pub struct SolicitOfferParams {
31    pub stream_usage: u8,
32    pub originating_endpoint_id: u16,
33    pub video_stream_id: Option<u16>,
34    pub audio_stream_id: Option<u16>,
35    pub ice_transport_policy: Option<String>,
36    pub metadata_enabled: bool,
37    pub s_frame_config: Option<SFrame>,
38    pub video_streams: Option<Vec<u16>>,
39    pub audio_streams: Option<Vec<u16>>,
40}
41
42/// Encode SolicitOffer command (0x00)
43pub fn encode_solicit_offer(params: SolicitOfferParams) -> anyhow::Result<Vec<u8>> {
44    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
45    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt8(params.stream_usage)).into());
46    tlv_fields.push((1, tlv::TlvItemValueEnc::UInt16(params.originating_endpoint_id)).into());
47    tlv_fields.push((2, tlv::TlvItemValueEnc::UInt16(params.video_stream_id.unwrap_or(0))).into());
48    tlv_fields.push((3, tlv::TlvItemValueEnc::UInt16(params.audio_stream_id.unwrap_or(0))).into());
49    if let Some(x) = params.ice_transport_policy { tlv_fields.push((5, tlv::TlvItemValueEnc::String(x)).into()); }
50    tlv_fields.push((6, tlv::TlvItemValueEnc::Bool(params.metadata_enabled)).into());
51    if let Some(s_frame_config) = params.s_frame_config {
52        // Encode struct SFrameStruct
53        let mut s_frame_config_fields = Vec::new();
54        if let Some(x) = s_frame_config.cipher_suite { s_frame_config_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
55        if let Some(x) = s_frame_config.base_key { s_frame_config_fields.push((1, tlv::TlvItemValueEnc::OctetString(x.clone())).into()); }
56        if let Some(x) = s_frame_config.kid { s_frame_config_fields.push((2, tlv::TlvItemValueEnc::OctetString(x.clone())).into()); }
57        tlv_fields.push((7, tlv::TlvItemValueEnc::StructInvisible(s_frame_config_fields)).into());
58    }
59    if let Some(video_streams) = params.video_streams {
60        tlv_fields.push((8, tlv::TlvItemValueEnc::StructAnon(video_streams.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
61    }
62    if let Some(audio_streams) = params.audio_streams {
63        tlv_fields.push((9, tlv::TlvItemValueEnc::StructAnon(audio_streams.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
64    }
65    let tlv = tlv::TlvItemEnc {
66        tag: 0,
67        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
68    };
69    Ok(tlv.encode()?)
70}
71
72/// Parameters for ProvideOffer command
73pub struct ProvideOfferParams {
74    pub web_rtc_session_id: Option<u8>,
75    pub sdp: String,
76    pub stream_usage: Option<u8>,
77    pub originating_endpoint_id: Option<u16>,
78    pub video_stream_id: Option<u16>,
79    pub audio_stream_id: Option<u16>,
80    pub ice_transport_policy: Option<String>,
81    pub metadata_enabled: bool,
82    pub s_frame_config: Option<SFrame>,
83    pub video_streams: Option<Vec<u16>>,
84    pub audio_streams: Option<Vec<u16>>,
85}
86
87/// Encode ProvideOffer command (0x02)
88pub fn encode_provide_offer(params: ProvideOfferParams) -> anyhow::Result<Vec<u8>> {
89    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
90    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt8(params.web_rtc_session_id.unwrap_or(0))).into());
91    tlv_fields.push((1, tlv::TlvItemValueEnc::String(params.sdp)).into());
92    if let Some(x) = params.stream_usage { tlv_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
93    if let Some(x) = params.originating_endpoint_id { tlv_fields.push((3, tlv::TlvItemValueEnc::UInt16(x)).into()); }
94    tlv_fields.push((4, tlv::TlvItemValueEnc::UInt16(params.video_stream_id.unwrap_or(0))).into());
95    tlv_fields.push((5, tlv::TlvItemValueEnc::UInt16(params.audio_stream_id.unwrap_or(0))).into());
96    if let Some(x) = params.ice_transport_policy { tlv_fields.push((7, tlv::TlvItemValueEnc::String(x)).into()); }
97    tlv_fields.push((8, tlv::TlvItemValueEnc::Bool(params.metadata_enabled)).into());
98    if let Some(s_frame_config) = params.s_frame_config {
99        // Encode struct SFrameStruct
100        let mut s_frame_config_fields = Vec::new();
101        if let Some(x) = s_frame_config.cipher_suite { s_frame_config_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
102        if let Some(x) = s_frame_config.base_key { s_frame_config_fields.push((1, tlv::TlvItemValueEnc::OctetString(x.clone())).into()); }
103        if let Some(x) = s_frame_config.kid { s_frame_config_fields.push((2, tlv::TlvItemValueEnc::OctetString(x.clone())).into()); }
104        tlv_fields.push((9, tlv::TlvItemValueEnc::StructInvisible(s_frame_config_fields)).into());
105    }
106    if let Some(video_streams) = params.video_streams {
107        tlv_fields.push((10, tlv::TlvItemValueEnc::StructAnon(video_streams.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
108    }
109    if let Some(audio_streams) = params.audio_streams {
110        tlv_fields.push((11, tlv::TlvItemValueEnc::StructAnon(audio_streams.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
111    }
112    let tlv = tlv::TlvItemEnc {
113        tag: 0,
114        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
115    };
116    Ok(tlv.encode()?)
117}
118
119/// Encode ProvideAnswer command (0x04)
120pub fn encode_provide_answer(web_rtc_session_id: u8, sdp: String) -> anyhow::Result<Vec<u8>> {
121    let tlv = tlv::TlvItemEnc {
122        tag: 0,
123        value: tlv::TlvItemValueEnc::StructInvisible(vec![
124        (0, tlv::TlvItemValueEnc::UInt8(web_rtc_session_id)).into(),
125        (1, tlv::TlvItemValueEnc::String(sdp)).into(),
126        ]),
127    };
128    Ok(tlv.encode()?)
129}
130
131/// Encode ProvideICECandidates command (0x05)
132pub fn encode_provide_ice_candidates(web_rtc_session_id: u8) -> anyhow::Result<Vec<u8>> {
133    let tlv = tlv::TlvItemEnc {
134        tag: 0,
135        value: tlv::TlvItemValueEnc::StructInvisible(vec![
136        (0, tlv::TlvItemValueEnc::UInt8(web_rtc_session_id)).into(),
137        ]),
138    };
139    Ok(tlv.encode()?)
140}
141
142/// Encode EndSession command (0x06)
143pub fn encode_end_session(web_rtc_session_id: u8, reason: u8) -> anyhow::Result<Vec<u8>> {
144    let tlv = tlv::TlvItemEnc {
145        tag: 0,
146        value: tlv::TlvItemValueEnc::StructInvisible(vec![
147        (0, tlv::TlvItemValueEnc::UInt8(web_rtc_session_id)).into(),
148        (1, tlv::TlvItemValueEnc::UInt8(reason)).into(),
149        ]),
150    };
151    Ok(tlv.encode()?)
152}
153
154// Attribute decoders
155
156/// Decode CurrentSessions attribute (0x0000)
157pub fn decode_current_sessions(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<u8>> {
158    let mut res = Vec::new();
159    if let tlv::TlvItemValue::List(v) = inp {
160        for item in v {
161            if let tlv::TlvItemValue::Int(i) = &item.value {
162                res.push(*i as u8);
163            }
164        }
165    }
166    Ok(res)
167}
168
169
170// JSON dispatcher function
171
172/// Decode attribute value and return as JSON string
173///
174/// # Parameters
175/// * `cluster_id` - The cluster identifier
176/// * `attribute_id` - The attribute identifier
177/// * `tlv_value` - The TLV value to decode
178///
179/// # Returns
180/// JSON string representation of the decoded value or error
181pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
182    // Verify this is the correct cluster
183    if cluster_id != 0x0553 {
184        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0553, got {}\"}}", cluster_id);
185    }
186
187    match attribute_id {
188        0x0000 => {
189            match decode_current_sessions(tlv_value) {
190                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
191                Err(e) => format!("{{\"error\": \"{}\"}}", e),
192            }
193        }
194        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
195    }
196}
197
198/// Get list of all attributes supported by this cluster
199///
200/// # Returns
201/// Vector of tuples containing (attribute_id, attribute_name)
202pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
203    vec![
204        (0x0000, "CurrentSessions"),
205    ]
206}
207
208// Command listing
209
210pub fn get_command_list() -> Vec<(u32, &'static str)> {
211    vec![
212        (0x00, "SolicitOffer"),
213        (0x02, "ProvideOffer"),
214        (0x04, "ProvideAnswer"),
215        (0x05, "ProvideICECandidates"),
216        (0x06, "EndSession"),
217    ]
218}
219
220pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
221    match cmd_id {
222        0x00 => Some("SolicitOffer"),
223        0x02 => Some("ProvideOffer"),
224        0x04 => Some("ProvideAnswer"),
225        0x05 => Some("ProvideICECandidates"),
226        0x06 => Some("EndSession"),
227        _ => None,
228    }
229}
230
231pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
232    match cmd_id {
233        0x00 => Some(vec![
234            crate::clusters::codec::CommandField { tag: 0, name: "stream_usage", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
235            crate::clusters::codec::CommandField { tag: 1, name: "originating_endpoint_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
236            crate::clusters::codec::CommandField { tag: 2, name: "video_stream_id", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: true },
237            crate::clusters::codec::CommandField { tag: 3, name: "audio_stream_id", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: true },
238            crate::clusters::codec::CommandField { tag: 5, name: "ice_transport_policy", kind: crate::clusters::codec::FieldKind::String, optional: true, nullable: false },
239            crate::clusters::codec::CommandField { tag: 6, name: "metadata_enabled", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
240            crate::clusters::codec::CommandField { tag: 7, name: "s_frame_config", kind: crate::clusters::codec::FieldKind::Struct { name: "SFrameStruct" }, optional: true, nullable: false },
241            crate::clusters::codec::CommandField { tag: 8, name: "video_streams", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: true, nullable: false },
242            crate::clusters::codec::CommandField { tag: 9, name: "audio_streams", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: true, nullable: false },
243        ]),
244        0x02 => Some(vec![
245            crate::clusters::codec::CommandField { tag: 0, name: "web_rtc_session_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: true },
246            crate::clusters::codec::CommandField { tag: 1, name: "sdp", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
247            crate::clusters::codec::CommandField { tag: 2, name: "stream_usage", kind: crate::clusters::codec::FieldKind::U8, optional: true, nullable: false },
248            crate::clusters::codec::CommandField { tag: 3, name: "originating_endpoint_id", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: false },
249            crate::clusters::codec::CommandField { tag: 4, name: "video_stream_id", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: true },
250            crate::clusters::codec::CommandField { tag: 5, name: "audio_stream_id", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: true },
251            crate::clusters::codec::CommandField { tag: 7, name: "ice_transport_policy", kind: crate::clusters::codec::FieldKind::String, optional: true, nullable: false },
252            crate::clusters::codec::CommandField { tag: 8, name: "metadata_enabled", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
253            crate::clusters::codec::CommandField { tag: 9, name: "s_frame_config", kind: crate::clusters::codec::FieldKind::Struct { name: "SFrameStruct" }, optional: true, nullable: false },
254            crate::clusters::codec::CommandField { tag: 10, name: "video_streams", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: true, nullable: false },
255            crate::clusters::codec::CommandField { tag: 11, name: "audio_streams", kind: crate::clusters::codec::FieldKind::List { entry_type: "uint16" }, optional: true, nullable: false },
256        ]),
257        0x04 => Some(vec![
258            crate::clusters::codec::CommandField { tag: 0, name: "web_rtc_session_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
259            crate::clusters::codec::CommandField { tag: 1, name: "sdp", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
260        ]),
261        0x05 => Some(vec![
262            crate::clusters::codec::CommandField { tag: 0, name: "web_rtc_session_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
263        ]),
264        0x06 => Some(vec![
265            crate::clusters::codec::CommandField { tag: 0, name: "web_rtc_session_id", kind: crate::clusters::codec::FieldKind::U32, optional: false, nullable: false },
266            crate::clusters::codec::CommandField { tag: 1, name: "reason", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: false },
267        ]),
268        _ => None,
269    }
270}
271
272pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
273    match cmd_id {
274        0x00 => Err(anyhow::anyhow!("command \"SolicitOffer\" has complex args: use raw mode")),
275        0x02 => Err(anyhow::anyhow!("command \"ProvideOffer\" has complex args: use raw mode")),
276        0x04 => {
277        let web_rtc_session_id = crate::clusters::codec::json_util::get_u8(args, "web_rtc_session_id")?;
278        let sdp = crate::clusters::codec::json_util::get_string(args, "sdp")?;
279        encode_provide_answer(web_rtc_session_id, sdp)
280        }
281        0x05 => {
282        let web_rtc_session_id = crate::clusters::codec::json_util::get_u8(args, "web_rtc_session_id")?;
283        encode_provide_ice_candidates(web_rtc_session_id)
284        }
285        0x06 => {
286        let web_rtc_session_id = crate::clusters::codec::json_util::get_u8(args, "web_rtc_session_id")?;
287        let reason = crate::clusters::codec::json_util::get_u8(args, "reason")?;
288        encode_end_session(web_rtc_session_id, reason)
289        }
290        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
291    }
292}
293
294#[derive(Debug, serde::Serialize)]
295pub struct SolicitOfferResponse {
296    pub web_rtc_session_id: Option<u8>,
297    pub deferred_offer: Option<bool>,
298    pub video_stream_id: Option<u16>,
299    pub audio_stream_id: Option<u16>,
300}
301
302#[derive(Debug, serde::Serialize)]
303pub struct ProvideOfferResponse {
304    pub web_rtc_session_id: Option<u8>,
305    pub video_stream_id: Option<u16>,
306    pub audio_stream_id: Option<u16>,
307}
308
309// Command response decoders
310
311/// Decode SolicitOfferResponse command response (01)
312pub fn decode_solicit_offer_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SolicitOfferResponse> {
313    if let tlv::TlvItemValue::List(_fields) = inp {
314        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
315        Ok(SolicitOfferResponse {
316                web_rtc_session_id: item.get_int(&[0]).map(|v| v as u8),
317                deferred_offer: item.get_bool(&[1]),
318                video_stream_id: item.get_int(&[2]).map(|v| v as u16),
319                audio_stream_id: item.get_int(&[3]).map(|v| v as u16),
320        })
321    } else {
322        Err(anyhow::anyhow!("Expected struct fields"))
323    }
324}
325
326/// Decode ProvideOfferResponse command response (03)
327pub fn decode_provide_offer_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProvideOfferResponse> {
328    if let tlv::TlvItemValue::List(_fields) = inp {
329        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
330        Ok(ProvideOfferResponse {
331                web_rtc_session_id: item.get_int(&[0]).map(|v| v as u8),
332                video_stream_id: item.get_int(&[1]).map(|v| v as u16),
333                audio_stream_id: item.get_int(&[2]).map(|v| v as u16),
334        })
335    } else {
336        Err(anyhow::anyhow!("Expected struct fields"))
337    }
338}
339
340// Typed facade (invokes + reads)
341
342/// Invoke `SolicitOffer` command on cluster `WebRTC Transport Provider`.
343pub async fn solicit_offer(conn: &crate::controller::Connection, endpoint: u16, params: SolicitOfferParams) -> anyhow::Result<SolicitOfferResponse> {
344    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_CMD_ID_SOLICITOFFER, &encode_solicit_offer(params)?).await?;
345    decode_solicit_offer_response(&tlv)
346}
347
348/// Invoke `ProvideOffer` command on cluster `WebRTC Transport Provider`.
349pub async fn provide_offer(conn: &crate::controller::Connection, endpoint: u16, params: ProvideOfferParams) -> anyhow::Result<ProvideOfferResponse> {
350    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_CMD_ID_PROVIDEOFFER, &encode_provide_offer(params)?).await?;
351    decode_provide_offer_response(&tlv)
352}
353
354/// Invoke `ProvideAnswer` command on cluster `WebRTC Transport Provider`.
355pub async fn provide_answer(conn: &crate::controller::Connection, endpoint: u16, web_rtc_session_id: u8, sdp: String) -> anyhow::Result<()> {
356    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_CMD_ID_PROVIDEANSWER, &encode_provide_answer(web_rtc_session_id, sdp)?).await?;
357    Ok(())
358}
359
360/// Invoke `ProvideICECandidates` command on cluster `WebRTC Transport Provider`.
361pub async fn provide_ice_candidates(conn: &crate::controller::Connection, endpoint: u16, web_rtc_session_id: u8) -> anyhow::Result<()> {
362    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_CMD_ID_PROVIDEICECANDIDATES, &encode_provide_ice_candidates(web_rtc_session_id)?).await?;
363    Ok(())
364}
365
366/// Invoke `EndSession` command on cluster `WebRTC Transport Provider`.
367pub async fn end_session(conn: &crate::controller::Connection, endpoint: u16, web_rtc_session_id: u8, reason: u8) -> anyhow::Result<()> {
368    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_CMD_ID_ENDSESSION, &encode_end_session(web_rtc_session_id, reason)?).await?;
369    Ok(())
370}
371
372/// Read `CurrentSessions` attribute from cluster `WebRTC Transport Provider`.
373pub async fn read_current_sessions(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<u8>> {
374    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_WEBRTC_TRANSPORT_PROVIDER, crate::clusters::defs::CLUSTER_WEBRTC_TRANSPORT_PROVIDER_ATTR_ID_CURRENTSESSIONS).await?;
375    decode_current_sessions(&tlv)
376}
377