1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum MetricType {
16 Pixels = 0,
18 Percentage = 1,
20}
21
22impl MetricType {
23 pub fn from_u8(value: u8) -> Option<Self> {
25 match value {
26 0 => Some(MetricType::Pixels),
27 1 => Some(MetricType::Percentage),
28 _ => None,
29 }
30 }
31
32 pub fn to_u8(self) -> u8 {
34 self as u8
35 }
36}
37
38impl From<MetricType> for u8 {
39 fn from(val: MetricType) -> Self {
40 val as u8
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45#[repr(u8)]
46pub enum ParameterEnum {
47 Actor = 0,
49 Channel = 1,
51 Character = 2,
53 Director = 3,
55 Event = 4,
57 Franchise = 5,
59 Genre = 6,
61 League = 7,
63 Popularity = 8,
65 Provider = 9,
67 Sport = 10,
69 Sportsteam = 11,
71 Type = 12,
73 Video = 13,
75 Season = 14,
77 Episode = 15,
79 Any = 16,
81}
82
83impl ParameterEnum {
84 pub fn from_u8(value: u8) -> Option<Self> {
86 match value {
87 0 => Some(ParameterEnum::Actor),
88 1 => Some(ParameterEnum::Channel),
89 2 => Some(ParameterEnum::Character),
90 3 => Some(ParameterEnum::Director),
91 4 => Some(ParameterEnum::Event),
92 5 => Some(ParameterEnum::Franchise),
93 6 => Some(ParameterEnum::Genre),
94 7 => Some(ParameterEnum::League),
95 8 => Some(ParameterEnum::Popularity),
96 9 => Some(ParameterEnum::Provider),
97 10 => Some(ParameterEnum::Sport),
98 11 => Some(ParameterEnum::Sportsteam),
99 12 => Some(ParameterEnum::Type),
100 13 => Some(ParameterEnum::Video),
101 14 => Some(ParameterEnum::Season),
102 15 => Some(ParameterEnum::Episode),
103 16 => Some(ParameterEnum::Any),
104 _ => None,
105 }
106 }
107
108 pub fn to_u8(self) -> u8 {
110 self as u8
111 }
112}
113
114impl From<ParameterEnum> for u8 {
115 fn from(val: ParameterEnum) -> Self {
116 val as u8
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
121#[repr(u8)]
122pub enum Status {
123 Success = 0,
125 Urlnotavailable = 1,
127 Authfailed = 2,
129 Texttracknotavailable = 3,
131 Audiotracknotavailable = 4,
133}
134
135impl Status {
136 pub fn from_u8(value: u8) -> Option<Self> {
138 match value {
139 0 => Some(Status::Success),
140 1 => Some(Status::Urlnotavailable),
141 2 => Some(Status::Authfailed),
142 3 => Some(Status::Texttracknotavailable),
143 4 => Some(Status::Audiotracknotavailable),
144 _ => None,
145 }
146 }
147
148 pub fn to_u8(self) -> u8 {
150 self as u8
151 }
152}
153
154impl From<Status> for u8 {
155 fn from(val: Status) -> Self {
156 val as u8
157 }
158}
159
160pub type SupportedProtocols = u8;
164
165pub mod supportedprotocols {
167 pub const DASH: u8 = 0x01;
169 pub const HLS: u8 = 0x02;
171}
172
173#[derive(Debug, serde::Serialize)]
176pub struct AdditionalInfo {
177 pub name: Option<String>,
178 pub value: Option<String>,
179}
180
181#[derive(Debug, serde::Serialize)]
182pub struct BrandingInformation {
183 pub provider_name: Option<String>,
184 pub background: Option<StyleInformation>,
185 pub logo: Option<StyleInformation>,
186 pub progress_bar: Option<StyleInformation>,
187 pub splash: Option<StyleInformation>,
188 pub water_mark: Option<StyleInformation>,
189}
190
191#[derive(Debug, serde::Serialize)]
192pub struct ContentSearch {
193 pub parameter_list: Option<Vec<Parameter>>,
194}
195
196#[derive(Debug, serde::Serialize)]
197pub struct Dimension {
198 pub width: Option<u8>,
199 pub height: Option<u8>,
200 pub metric: Option<MetricType>,
201}
202
203#[derive(Debug, serde::Serialize)]
204pub struct Parameter {
205 pub type_: Option<ParameterEnum>,
206 pub value: Option<String>,
207 pub external_id_list: Option<Vec<AdditionalInfo>>,
208}
209
210#[derive(Debug, serde::Serialize)]
211pub struct PlaybackPreferences {
212 pub playback_position: Option<u64>,
213 pub text_track: Option<TrackPreference>,
214 pub audio_tracks: Option<Vec<TrackPreference>>,
215}
216
217#[derive(Debug, serde::Serialize)]
218pub struct StyleInformation {
219 pub image_url: Option<String>,
220 pub color: Option<String>,
221 pub size: Option<Dimension>,
222}
223
224#[derive(Debug, serde::Serialize)]
225pub struct TrackPreference {
226 pub language_code: Option<String>,
227 pub characteristics: Option<Vec<u8>>,
228 pub audio_output_index: Option<u8>,
229}
230
231pub fn encode_launch_content(search: ContentSearch, auto_play: bool, data: String, playback_preferences: PlaybackPreferences, use_current_context: bool) -> anyhow::Result<Vec<u8>> {
235 let mut search_fields = Vec::new();
237 if let Some(listv) = search.parameter_list {
238 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
239 let mut nested_fields = Vec::new();
240 if let Some(x) = inner.type_ { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
241 if let Some(x) = inner.value { nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
242 if let Some(listv) = inner.external_id_list {
243 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
244 let mut nested_fields = Vec::new();
245 if let Some(x) = inner.name { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
246 if let Some(x) = inner.value { nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
247 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
248 }).collect();
249 nested_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
250 }
251 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
252 }).collect();
253 search_fields.push((0, tlv::TlvItemValueEnc::Array(inner_vec)).into());
254 }
255 let mut playback_preferences_fields = Vec::new();
257 if let Some(x) = playback_preferences.playback_position { playback_preferences_fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
258 if let Some(inner) = playback_preferences.text_track {
259 let mut text_track_nested_fields = Vec::new();
260 if let Some(x) = inner.language_code { text_track_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
261 if let Some(listv) = inner.characteristics { text_track_nested_fields.push((1, tlv::TlvItemValueEnc::StructAnon(listv.into_iter().map(|x| (0, tlv::TlvItemValueEnc::UInt8(x)).into()).collect())).into()); }
262 if let Some(x) = inner.audio_output_index { text_track_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
263 playback_preferences_fields.push((1, tlv::TlvItemValueEnc::StructInvisible(text_track_nested_fields)).into());
264 }
265 if let Some(listv) = playback_preferences.audio_tracks {
266 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
267 let mut nested_fields = Vec::new();
268 if let Some(x) = inner.language_code { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
269 if let Some(listv) = inner.characteristics { nested_fields.push((1, tlv::TlvItemValueEnc::StructAnon(listv.into_iter().map(|x| (0, tlv::TlvItemValueEnc::UInt8(x)).into()).collect())).into()); }
270 if let Some(x) = inner.audio_output_index { nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
271 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
272 }).collect();
273 playback_preferences_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
274 }
275 let tlv = tlv::TlvItemEnc {
276 tag: 0,
277 value: tlv::TlvItemValueEnc::StructInvisible(vec![
278 (0, tlv::TlvItemValueEnc::StructInvisible(search_fields)).into(),
279 (1, tlv::TlvItemValueEnc::Bool(auto_play)).into(),
280 (2, tlv::TlvItemValueEnc::String(data)).into(),
281 (3, tlv::TlvItemValueEnc::StructInvisible(playback_preferences_fields)).into(),
282 (4, tlv::TlvItemValueEnc::Bool(use_current_context)).into(),
283 ]),
284 };
285 Ok(tlv.encode()?)
286}
287
288pub fn encode_launch_url(content_url: String, display_string: String, branding_information: BrandingInformation, playback_preferences: PlaybackPreferences) -> anyhow::Result<Vec<u8>> {
290 let mut branding_information_fields = Vec::new();
292 if let Some(x) = branding_information.provider_name { branding_information_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
293 if let Some(inner) = branding_information.background {
294 let mut background_nested_fields = Vec::new();
295 if let Some(x) = inner.image_url { background_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
296 if let Some(x) = inner.color { background_nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
297 if let Some(inner) = inner.size {
298 let mut size_nested_fields = Vec::new();
299 if let Some(x) = inner.metric { size_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
302 background_nested_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(size_nested_fields)).into());
303 }
304 branding_information_fields.push((1, tlv::TlvItemValueEnc::StructInvisible(background_nested_fields)).into());
305 }
306 if let Some(inner) = branding_information.logo {
307 let mut logo_nested_fields = Vec::new();
308 if let Some(x) = inner.image_url { logo_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
309 if let Some(x) = inner.color { logo_nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
310 if let Some(inner) = inner.size {
311 let mut size_nested_fields = Vec::new();
312 if let Some(x) = inner.metric { size_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
315 logo_nested_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(size_nested_fields)).into());
316 }
317 branding_information_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(logo_nested_fields)).into());
318 }
319 if let Some(inner) = branding_information.progress_bar {
320 let mut progress_bar_nested_fields = Vec::new();
321 if let Some(x) = inner.image_url { progress_bar_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
322 if let Some(x) = inner.color { progress_bar_nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
323 if let Some(inner) = inner.size {
324 let mut size_nested_fields = Vec::new();
325 if let Some(x) = inner.metric { size_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
328 progress_bar_nested_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(size_nested_fields)).into());
329 }
330 branding_information_fields.push((3, tlv::TlvItemValueEnc::StructInvisible(progress_bar_nested_fields)).into());
331 }
332 if let Some(inner) = branding_information.splash {
333 let mut splash_nested_fields = Vec::new();
334 if let Some(x) = inner.image_url { splash_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
335 if let Some(x) = inner.color { splash_nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
336 if let Some(inner) = inner.size {
337 let mut size_nested_fields = Vec::new();
338 if let Some(x) = inner.metric { size_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
341 splash_nested_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(size_nested_fields)).into());
342 }
343 branding_information_fields.push((4, tlv::TlvItemValueEnc::StructInvisible(splash_nested_fields)).into());
344 }
345 if let Some(inner) = branding_information.water_mark {
346 let mut water_mark_nested_fields = Vec::new();
347 if let Some(x) = inner.image_url { water_mark_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
348 if let Some(x) = inner.color { water_mark_nested_fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
349 if let Some(inner) = inner.size {
350 let mut size_nested_fields = Vec::new();
351 if let Some(x) = inner.metric { size_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
354 water_mark_nested_fields.push((2, tlv::TlvItemValueEnc::StructInvisible(size_nested_fields)).into());
355 }
356 branding_information_fields.push((5, tlv::TlvItemValueEnc::StructInvisible(water_mark_nested_fields)).into());
357 }
358 let mut playback_preferences_fields = Vec::new();
360 if let Some(x) = playback_preferences.playback_position { playback_preferences_fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
361 if let Some(inner) = playback_preferences.text_track {
362 let mut text_track_nested_fields = Vec::new();
363 if let Some(x) = inner.language_code { text_track_nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
364 if let Some(listv) = inner.characteristics { text_track_nested_fields.push((1, tlv::TlvItemValueEnc::StructAnon(listv.into_iter().map(|x| (0, tlv::TlvItemValueEnc::UInt8(x)).into()).collect())).into()); }
365 if let Some(x) = inner.audio_output_index { text_track_nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
366 playback_preferences_fields.push((1, tlv::TlvItemValueEnc::StructInvisible(text_track_nested_fields)).into());
367 }
368 if let Some(listv) = playback_preferences.audio_tracks {
369 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
370 let mut nested_fields = Vec::new();
371 if let Some(x) = inner.language_code { nested_fields.push((0, tlv::TlvItemValueEnc::String(x.clone())).into()); }
372 if let Some(listv) = inner.characteristics { nested_fields.push((1, tlv::TlvItemValueEnc::StructAnon(listv.into_iter().map(|x| (0, tlv::TlvItemValueEnc::UInt8(x)).into()).collect())).into()); }
373 if let Some(x) = inner.audio_output_index { nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
374 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
375 }).collect();
376 playback_preferences_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
377 }
378 let tlv = tlv::TlvItemEnc {
379 tag: 0,
380 value: tlv::TlvItemValueEnc::StructInvisible(vec![
381 (0, tlv::TlvItemValueEnc::String(content_url)).into(),
382 (1, tlv::TlvItemValueEnc::String(display_string)).into(),
383 (2, tlv::TlvItemValueEnc::StructInvisible(branding_information_fields)).into(),
384 (3, tlv::TlvItemValueEnc::StructInvisible(playback_preferences_fields)).into(),
385 ]),
386 };
387 Ok(tlv.encode()?)
388}
389
390pub fn decode_accept_header(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<String>> {
394 let mut res = Vec::new();
395 if let tlv::TlvItemValue::List(v) = inp {
396 for item in v {
397 if let tlv::TlvItemValue::String(s) = &item.value {
398 res.push(s.clone());
399 }
400 }
401 }
402 Ok(res)
403}
404
405pub fn decode_supported_streaming_protocols(inp: &tlv::TlvItemValue) -> anyhow::Result<SupportedProtocols> {
407 if let tlv::TlvItemValue::Int(v) = inp {
408 Ok(*v as u8)
409 } else {
410 Err(anyhow::anyhow!("Expected Integer"))
411 }
412}
413
414
415pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
427 if cluster_id != 0x050A {
429 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050A, got {}\"}}", cluster_id);
430 }
431
432 match attribute_id {
433 0x0000 => {
434 match decode_accept_header(tlv_value) {
435 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
436 Err(e) => format!("{{\"error\": \"{}\"}}", e),
437 }
438 }
439 0x0001 => {
440 match decode_supported_streaming_protocols(tlv_value) {
441 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
442 Err(e) => format!("{{\"error\": \"{}\"}}", e),
443 }
444 }
445 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
446 }
447}
448
449pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
454 vec![
455 (0x0000, "AcceptHeader"),
456 (0x0001, "SupportedStreamingProtocols"),
457 ]
458}
459
460#[derive(Debug, serde::Serialize)]
461pub struct LauncherResponse {
462 pub status: Option<Status>,
463 pub data: Option<String>,
464}
465
466pub fn decode_launcher_response(inp: &tlv::TlvItemValue) -> anyhow::Result<LauncherResponse> {
470 if let tlv::TlvItemValue::List(_fields) = inp {
471 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
472 Ok(LauncherResponse {
473 status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
474 data: item.get_string_owned(&[1]),
475 })
476 } else {
477 Err(anyhow::anyhow!("Expected struct fields"))
478 }
479}
480