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 ChannelType {
16 Satellite = 0,
18 Cable = 1,
20 Terrestrial = 2,
22 Ott = 3,
24}
25
26impl ChannelType {
27 pub fn from_u8(value: u8) -> Option<Self> {
29 match value {
30 0 => Some(ChannelType::Satellite),
31 1 => Some(ChannelType::Cable),
32 2 => Some(ChannelType::Terrestrial),
33 3 => Some(ChannelType::Ott),
34 _ => None,
35 }
36 }
37
38 pub fn to_u8(self) -> u8 {
40 self as u8
41 }
42}
43
44impl From<ChannelType> for u8 {
45 fn from(val: ChannelType) -> Self {
46 val as u8
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[repr(u8)]
52pub enum LineupInfoType {
53 Mso = 0,
55}
56
57impl LineupInfoType {
58 pub fn from_u8(value: u8) -> Option<Self> {
60 match value {
61 0 => Some(LineupInfoType::Mso),
62 _ => None,
63 }
64 }
65
66 pub fn to_u8(self) -> u8 {
68 self as u8
69 }
70}
71
72impl From<LineupInfoType> for u8 {
73 fn from(val: LineupInfoType) -> Self {
74 val as u8
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
79#[repr(u8)]
80pub enum Status {
81 Success = 0,
83 Multiplematches = 1,
85 Nomatches = 2,
87}
88
89impl Status {
90 pub fn from_u8(value: u8) -> Option<Self> {
92 match value {
93 0 => Some(Status::Success),
94 1 => Some(Status::Multiplematches),
95 2 => Some(Status::Nomatches),
96 _ => None,
97 }
98 }
99
100 pub fn to_u8(self) -> u8 {
102 self as u8
103 }
104}
105
106impl From<Status> for u8 {
107 fn from(val: Status) -> Self {
108 val as u8
109 }
110}
111
112pub type RecordingFlag = u8;
116
117pub mod recordingflag {
119 pub const SCHEDULED: u8 = 0x01;
121 pub const RECORD_SERIES: u8 = 0x02;
123 pub const RECORDED: u8 = 0x04;
125}
126
127#[derive(Debug, serde::Serialize)]
130pub struct ChannelInfo {
131 pub major_number: Option<u16>,
132 pub minor_number: Option<u16>,
133 pub name: Option<String>,
134 pub call_sign: Option<String>,
135 pub affiliate_call_sign: Option<String>,
136 pub identifier: Option<String>,
137 pub type_: Option<ChannelType>,
138}
139
140#[derive(Debug, serde::Serialize)]
141pub struct ChannelPaging {
142 pub previous_token: Option<PageToken>,
143 pub next_token: Option<PageToken>,
144}
145
146#[derive(Debug, serde::Serialize)]
147pub struct LineupInfo {
148 pub operator_name: Option<String>,
149 pub lineup_name: Option<String>,
150 pub postal_code: Option<String>,
151 pub lineup_info_type: Option<LineupInfoType>,
152}
153
154#[derive(Debug, serde::Serialize)]
155pub struct PageToken {
156 pub limit: Option<u16>,
157 pub after: Option<String>,
158 pub before: Option<String>,
159}
160
161#[derive(Debug, serde::Serialize)]
162pub struct ProgramCast {
163 pub name: Option<String>,
164 pub role: Option<String>,
165}
166
167#[derive(Debug, serde::Serialize)]
168pub struct ProgramCategory {
169 pub category: Option<String>,
170 pub sub_category: Option<String>,
171}
172
173#[derive(Debug, serde::Serialize)]
174pub struct Program {
175 pub identifier: Option<String>,
176 pub channel: Option<ChannelInfo>,
177 pub start_time: Option<u64>,
178 pub end_time: Option<u64>,
179 pub title: Option<String>,
180 pub subtitle: Option<String>,
181 pub description: Option<String>,
182 pub audio_languages: Option<Vec<String>>,
183 pub ratings: Option<Vec<String>>,
184 pub thumbnail_url: Option<String>,
185 pub poster_art_url: Option<String>,
186 pub dvbi_url: Option<String>,
187 pub release_date: Option<String>,
188 pub parental_guidance_text: Option<String>,
189 pub recording_flag: Option<RecordingFlag>,
190 pub series_info: Option<SeriesInfo>,
191 pub category_list: Option<Vec<ProgramCategory>>,
192 pub cast_list: Option<Vec<ProgramCast>>,
193}
194
195#[derive(Debug, serde::Serialize)]
196pub struct SeriesInfo {
197 pub season: Option<String>,
198 pub episode: Option<String>,
199}
200
201pub fn encode_change_channel(match_: String) -> anyhow::Result<Vec<u8>> {
205 let tlv = tlv::TlvItemEnc {
206 tag: 0,
207 value: tlv::TlvItemValueEnc::StructInvisible(vec![
208 (0, tlv::TlvItemValueEnc::String(match_)).into(),
209 ]),
210 };
211 Ok(tlv.encode()?)
212}
213
214pub fn encode_change_channel_by_number(major_number: u16, minor_number: u16) -> anyhow::Result<Vec<u8>> {
216 let tlv = tlv::TlvItemEnc {
217 tag: 0,
218 value: tlv::TlvItemValueEnc::StructInvisible(vec![
219 (0, tlv::TlvItemValueEnc::UInt16(major_number)).into(),
220 (1, tlv::TlvItemValueEnc::UInt16(minor_number)).into(),
221 ]),
222 };
223 Ok(tlv.encode()?)
224}
225
226pub fn encode_skip_channel(count: i16) -> anyhow::Result<Vec<u8>> {
228 let tlv = tlv::TlvItemEnc {
229 tag: 0,
230 value: tlv::TlvItemValueEnc::StructInvisible(vec![
231 (0, tlv::TlvItemValueEnc::Int16(count)).into(),
232 ]),
233 };
234 Ok(tlv.encode()?)
235}
236
237pub fn encode_get_program_guide(start_time: u64, end_time: u64, channel_list: Vec<ChannelInfo>, page_token: Option<PageToken>, recording_flag: Option<RecordingFlag>, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
239 let page_token_enc = if let Some(s) = page_token {
241 let mut fields = Vec::new();
242 if let Some(x) = s.limit { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
243 if let Some(x) = s.after { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
244 if let Some(x) = s.before { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
245 tlv::TlvItemValueEnc::StructInvisible(fields)
246 } else {
247 tlv::TlvItemValueEnc::StructInvisible(Vec::new())
248 };
249 let tlv = tlv::TlvItemEnc {
250 tag: 0,
251 value: tlv::TlvItemValueEnc::StructInvisible(vec![
252 (0, tlv::TlvItemValueEnc::UInt64(start_time)).into(),
253 (1, tlv::TlvItemValueEnc::UInt64(end_time)).into(),
254 (2, tlv::TlvItemValueEnc::Array(channel_list.into_iter().map(|v| {
255 let mut fields = Vec::new();
256 if let Some(x) = v.major_number { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
257 if let Some(x) = v.minor_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
258 if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
259 if let Some(x) = v.call_sign { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
260 if let Some(x) = v.affiliate_call_sign { fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
261 if let Some(x) = v.identifier { fields.push((5, tlv::TlvItemValueEnc::String(x.clone())).into()); }
262 if let Some(x) = v.type_ { fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
263 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
264 }).collect())).into(),
265 (3, page_token_enc).into(),
266 (5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into(),
267 (7, tlv::TlvItemValueEnc::OctetString(data)).into(),
268 ]),
269 };
270 Ok(tlv.encode()?)
271}
272
273pub fn encode_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
275 let tlv = tlv::TlvItemEnc {
276 tag: 0,
277 value: tlv::TlvItemValueEnc::StructInvisible(vec![
278 (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
279 (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
280 (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
281 ]),
282 };
283 Ok(tlv.encode()?)
284}
285
286pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
288 let tlv = tlv::TlvItemEnc {
289 tag: 0,
290 value: tlv::TlvItemValueEnc::StructInvisible(vec![
291 (0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
292 (1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
293 (3, tlv::TlvItemValueEnc::OctetString(data)).into(),
294 ]),
295 };
296 Ok(tlv.encode()?)
297}
298
299pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
303 let mut res = Vec::new();
304 if let tlv::TlvItemValue::List(v) = inp {
305 for item in v {
306 res.push(ChannelInfo {
307 major_number: item.get_int(&[0]).map(|v| v as u16),
308 minor_number: item.get_int(&[1]).map(|v| v as u16),
309 name: item.get_string_owned(&[2]),
310 call_sign: item.get_string_owned(&[3]),
311 affiliate_call_sign: item.get_string_owned(&[4]),
312 identifier: item.get_string_owned(&[5]),
313 type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
314 });
315 }
316 }
317 Ok(res)
318}
319
320pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
322 if let tlv::TlvItemValue::List(_fields) = inp {
323 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
325 Ok(Some(LineupInfo {
326 operator_name: item.get_string_owned(&[0]),
327 lineup_name: item.get_string_owned(&[1]),
328 postal_code: item.get_string_owned(&[2]),
329 lineup_info_type: item.get_int(&[3]).and_then(|v| LineupInfoType::from_u8(v as u8)),
330 }))
331 } else {
335 Ok(None)
336 }
338}
339
340pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
342 if let tlv::TlvItemValue::List(_fields) = inp {
343 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
345 Ok(Some(ChannelInfo {
346 major_number: item.get_int(&[0]).map(|v| v as u16),
347 minor_number: item.get_int(&[1]).map(|v| v as u16),
348 name: item.get_string_owned(&[2]),
349 call_sign: item.get_string_owned(&[3]),
350 affiliate_call_sign: item.get_string_owned(&[4]),
351 identifier: item.get_string_owned(&[5]),
352 type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
353 }))
354 } else {
358 Ok(None)
359 }
361}
362
363
364pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
376 if cluster_id != 0x0504 {
378 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
379 }
380
381 match attribute_id {
382 0x0000 => {
383 match decode_channel_list(tlv_value) {
384 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
385 Err(e) => format!("{{\"error\": \"{}\"}}", e),
386 }
387 }
388 0x0001 => {
389 match decode_lineup(tlv_value) {
390 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
391 Err(e) => format!("{{\"error\": \"{}\"}}", e),
392 }
393 }
394 0x0002 => {
395 match decode_current_channel(tlv_value) {
396 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
397 Err(e) => format!("{{\"error\": \"{}\"}}", e),
398 }
399 }
400 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
401 }
402}
403
404pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
409 vec![
410 (0x0000, "ChannelList"),
411 (0x0001, "Lineup"),
412 (0x0002, "CurrentChannel"),
413 ]
414}
415
416#[derive(Debug, serde::Serialize)]
417pub struct ChangeChannelResponse {
418 pub status: Option<Status>,
419 pub data: Option<String>,
420}
421
422#[derive(Debug, serde::Serialize)]
423pub struct ProgramGuideResponse {
424 pub paging: Option<ChannelPaging>,
425 pub program_list: Option<Vec<Program>>,
426}
427
428pub fn decode_change_channel_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ChangeChannelResponse> {
432 if let tlv::TlvItemValue::List(_fields) = inp {
433 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
434 Ok(ChangeChannelResponse {
435 status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
436 data: item.get_string_owned(&[1]),
437 })
438 } else {
439 Err(anyhow::anyhow!("Expected struct fields"))
440 }
441}
442
443pub fn decode_program_guide_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgramGuideResponse> {
445 if let tlv::TlvItemValue::List(_fields) = inp {
446 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
447 Ok(ProgramGuideResponse {
448 paging: {
449 if let Some(nested_tlv) = item.get(&[0]) {
450 if let tlv::TlvItemValue::List(_) = nested_tlv {
451 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
452 Some(ChannelPaging {
453 previous_token: {
454 if let Some(nested_tlv) = nested_item.get(&[0]) {
455 if let tlv::TlvItemValue::List(_) = nested_tlv {
456 let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
457 Some(PageToken {
458 limit: nested_item.get_int(&[0]).map(|v| v as u16),
459 after: nested_item.get_string_owned(&[1]),
460 before: nested_item.get_string_owned(&[2]),
461 })
462 } else {
463 None
464 }
465 } else {
466 None
467 }
468 },
469 next_token: {
470 if let Some(nested_tlv) = nested_item.get(&[1]) {
471 if let tlv::TlvItemValue::List(_) = nested_tlv {
472 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
473 Some(PageToken {
474 limit: nested_item.get_int(&[0]).map(|v| v as u16),
475 after: nested_item.get_string_owned(&[1]),
476 before: nested_item.get_string_owned(&[2]),
477 })
478 } else {
479 None
480 }
481 } else {
482 None
483 }
484 },
485 })
486 } else {
487 None
488 }
489 } else {
490 None
491 }
492 },
493 program_list: {
494 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
495 let mut items = Vec::new();
496 for list_item in l {
497 items.push(Program {
498 identifier: list_item.get_string_owned(&[0]),
499 channel: {
500 if let Some(nested_tlv) = list_item.get(&[1]) {
501 if let tlv::TlvItemValue::List(_) = nested_tlv {
502 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
503 Some(ChannelInfo {
504 major_number: nested_item.get_int(&[0]).map(|v| v as u16),
505 minor_number: nested_item.get_int(&[1]).map(|v| v as u16),
506 name: nested_item.get_string_owned(&[2]),
507 call_sign: nested_item.get_string_owned(&[3]),
508 affiliate_call_sign: nested_item.get_string_owned(&[4]),
509 identifier: nested_item.get_string_owned(&[5]),
510 type_: nested_item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
511 })
512 } else {
513 None
514 }
515 } else {
516 None
517 }
518 },
519 start_time: list_item.get_int(&[2]),
520 end_time: list_item.get_int(&[3]),
521 title: list_item.get_string_owned(&[4]),
522 subtitle: list_item.get_string_owned(&[5]),
523 description: list_item.get_string_owned(&[6]),
524 audio_languages: {
525 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[7]) {
526 let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
527 Some(items)
528 } else {
529 None
530 }
531 },
532 ratings: {
533 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[8]) {
534 let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
535 Some(items)
536 } else {
537 None
538 }
539 },
540 thumbnail_url: list_item.get_string_owned(&[9]),
541 poster_art_url: list_item.get_string_owned(&[10]),
542 dvbi_url: list_item.get_string_owned(&[11]),
543 release_date: list_item.get_string_owned(&[12]),
544 parental_guidance_text: list_item.get_string_owned(&[13]),
545 recording_flag: list_item.get_int(&[14]).map(|v| v as u8),
546 series_info: {
547 if let Some(nested_tlv) = list_item.get(&[15]) {
548 if let tlv::TlvItemValue::List(_) = nested_tlv {
549 let nested_item = tlv::TlvItem { tag: 15, value: nested_tlv.clone() };
550 Some(SeriesInfo {
551 season: nested_item.get_string_owned(&[0]),
552 episode: nested_item.get_string_owned(&[1]),
553 })
554 } else {
555 None
556 }
557 } else {
558 None
559 }
560 },
561 category_list: {
562 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[16]) {
563 let mut items = Vec::new();
564 for list_item in l {
565 items.push(ProgramCategory {
566 category: list_item.get_string_owned(&[0]),
567 sub_category: list_item.get_string_owned(&[1]),
568 });
569 }
570 Some(items)
571 } else {
572 None
573 }
574 },
575 cast_list: {
576 if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[17]) {
577 let mut items = Vec::new();
578 for list_item in l {
579 items.push(ProgramCast {
580 name: list_item.get_string_owned(&[0]),
581 role: list_item.get_string_owned(&[1]),
582 });
583 }
584 Some(items)
585 } else {
586 None
587 }
588 },
589 });
590 }
591 Some(items)
592 } else {
593 None
594 }
595 },
596 })
597 } else {
598 Err(anyhow::anyhow!("Expected struct fields"))
599 }
600}
601