1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16#[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
27pub 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
42pub 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 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
72pub 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
87pub 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 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
119pub 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
131pub 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
142pub 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
154pub 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
170pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
182 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
198pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
203 vec![
204 (0x0000, "CurrentSessions"),
205 ]
206}
207
208pub 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
309pub 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
326pub 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
340pub 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
348pub 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
354pub 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
360pub 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
366pub 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
372pub 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