use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum ChannelType {
Satellite = 0,
Cable = 1,
Terrestrial = 2,
Ott = 3,
}
impl ChannelType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(ChannelType::Satellite),
1 => Some(ChannelType::Cable),
2 => Some(ChannelType::Terrestrial),
3 => Some(ChannelType::Ott),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<ChannelType> for u8 {
fn from(val: ChannelType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum LineupInfoType {
Mso = 0,
}
impl LineupInfoType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(LineupInfoType::Mso),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<LineupInfoType> for u8 {
fn from(val: LineupInfoType) -> Self {
val as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum Status {
Success = 0,
Multiplematches = 1,
Nomatches = 2,
}
impl Status {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Status::Success),
1 => Some(Status::Multiplematches),
2 => Some(Status::Nomatches),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<Status> for u8 {
fn from(val: Status) -> Self {
val as u8
}
}
pub type RecordingFlag = u8;
pub mod recordingflag {
pub const SCHEDULED: u8 = 0x01;
pub const RECORD_SERIES: u8 = 0x02;
pub const RECORDED: u8 = 0x04;
}
#[derive(Debug, serde::Serialize)]
pub struct ChannelInfo {
pub major_number: Option<u16>,
pub minor_number: Option<u16>,
pub name: Option<String>,
pub call_sign: Option<String>,
pub affiliate_call_sign: Option<String>,
pub identifier: Option<String>,
pub type_: Option<ChannelType>,
}
#[derive(Debug, serde::Serialize)]
pub struct ChannelPaging {
pub previous_token: Option<PageToken>,
pub next_token: Option<PageToken>,
}
#[derive(Debug, serde::Serialize)]
pub struct LineupInfo {
pub operator_name: Option<String>,
pub lineup_name: Option<String>,
pub postal_code: Option<String>,
pub lineup_info_type: Option<LineupInfoType>,
}
#[derive(Debug, serde::Serialize)]
pub struct PageToken {
pub limit: Option<u16>,
pub after: Option<String>,
pub before: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramCast {
pub name: Option<String>,
pub role: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramCategory {
pub category: Option<String>,
pub sub_category: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct Program {
pub identifier: Option<String>,
pub channel: Option<ChannelInfo>,
pub start_time: Option<u64>,
pub end_time: Option<u64>,
pub title: Option<String>,
pub subtitle: Option<String>,
pub description: Option<String>,
pub audio_languages: Option<Vec<String>>,
pub ratings: Option<Vec<String>>,
pub thumbnail_url: Option<String>,
pub poster_art_url: Option<String>,
pub dvbi_url: Option<String>,
pub release_date: Option<String>,
pub parental_guidance_text: Option<String>,
pub recording_flag: Option<RecordingFlag>,
pub series_info: Option<SeriesInfo>,
pub category_list: Option<Vec<ProgramCategory>>,
pub cast_list: Option<Vec<ProgramCast>>,
}
#[derive(Debug, serde::Serialize)]
pub struct SeriesInfo {
pub season: Option<String>,
pub episode: Option<String>,
}
pub fn encode_change_channel(match_: String) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(match_)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_change_channel_by_number(major_number: u16, minor_number: u16) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt16(major_number)).into(),
(1, tlv::TlvItemValueEnc::UInt16(minor_number)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_skip_channel(count: i16) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Int16(count)).into(),
]),
};
Ok(tlv.encode()?)
}
pub 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>> {
let page_token_enc = if let Some(s) = page_token {
let mut fields = Vec::new();
if let Some(x) = s.limit { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = s.after { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = s.before { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
tlv::TlvItemValueEnc::StructInvisible(fields)
} else {
tlv::TlvItemValueEnc::StructInvisible(Vec::new())
};
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt64(start_time)).into(),
(1, tlv::TlvItemValueEnc::UInt64(end_time)).into(),
(2, tlv::TlvItemValueEnc::Array(channel_list.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.major_number { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.minor_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.call_sign { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.affiliate_call_sign { fields.push((4, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.identifier { fields.push((5, tlv::TlvItemValueEnc::String(x.clone())).into()); }
if let Some(x) = v.type_ { fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
(3, page_token_enc).into(),
(5, tlv::TlvItemValueEnc::UInt8(recording_flag.unwrap_or_default())).into(),
(7, tlv::TlvItemValueEnc::OctetString(data)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
(1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
(3, tlv::TlvItemValueEnc::OctetString(data)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_cancel_record_program(program_identifier: String, should_record_series: bool, data: Vec<u8>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(program_identifier)).into(),
(1, tlv::TlvItemValueEnc::Bool(should_record_series)).into(),
(3, tlv::TlvItemValueEnc::OctetString(data)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<ChannelInfo>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(ChannelInfo {
major_number: item.get_int(&[0]).map(|v| v as u16),
minor_number: item.get_int(&[1]).map(|v| v as u16),
name: item.get_string_owned(&[2]),
call_sign: item.get_string_owned(&[3]),
affiliate_call_sign: item.get_string_owned(&[4]),
identifier: item.get_string_owned(&[5]),
type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
});
}
}
Ok(res)
}
pub fn decode_lineup(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<LineupInfo>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(LineupInfo {
operator_name: item.get_string_owned(&[0]),
lineup_name: item.get_string_owned(&[1]),
postal_code: item.get_string_owned(&[2]),
lineup_info_type: item.get_int(&[3]).and_then(|v| LineupInfoType::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_current_channel(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<ChannelInfo>> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(Some(ChannelInfo {
major_number: item.get_int(&[0]).map(|v| v as u16),
minor_number: item.get_int(&[1]).map(|v| v as u16),
name: item.get_string_owned(&[2]),
call_sign: item.get_string_owned(&[3]),
affiliate_call_sign: item.get_string_owned(&[4]),
identifier: item.get_string_owned(&[5]),
type_: item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
}))
} else {
Ok(None)
}
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x0504 {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0504, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_channel_list(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_lineup(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_current_channel(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
_ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
}
}
pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
vec![
(0x0000, "ChannelList"),
(0x0001, "Lineup"),
(0x0002, "CurrentChannel"),
]
}
#[derive(Debug, serde::Serialize)]
pub struct ChangeChannelResponse {
pub status: Option<Status>,
pub data: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct ProgramGuideResponse {
pub paging: Option<ChannelPaging>,
pub program_list: Option<Vec<Program>>,
}
pub fn decode_change_channel_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ChangeChannelResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ChangeChannelResponse {
status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
data: item.get_string_owned(&[1]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}
pub fn decode_program_guide_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ProgramGuideResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ProgramGuideResponse {
paging: {
if let Some(nested_tlv) = item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(ChannelPaging {
previous_token: {
if let Some(nested_tlv) = nested_item.get(&[0]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 0, value: nested_tlv.clone() };
Some(PageToken {
limit: nested_item.get_int(&[0]).map(|v| v as u16),
after: nested_item.get_string_owned(&[1]),
before: nested_item.get_string_owned(&[2]),
})
} else {
None
}
} else {
None
}
},
next_token: {
if let Some(nested_tlv) = nested_item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(PageToken {
limit: nested_item.get_int(&[0]).map(|v| v as u16),
after: nested_item.get_string_owned(&[1]),
before: nested_item.get_string_owned(&[2]),
})
} else {
None
}
} else {
None
}
},
})
} else {
None
}
} else {
None
}
},
program_list: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
let mut items = Vec::new();
for list_item in l {
items.push(Program {
identifier: list_item.get_string_owned(&[0]),
channel: {
if let Some(nested_tlv) = list_item.get(&[1]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
Some(ChannelInfo {
major_number: nested_item.get_int(&[0]).map(|v| v as u16),
minor_number: nested_item.get_int(&[1]).map(|v| v as u16),
name: nested_item.get_string_owned(&[2]),
call_sign: nested_item.get_string_owned(&[3]),
affiliate_call_sign: nested_item.get_string_owned(&[4]),
identifier: nested_item.get_string_owned(&[5]),
type_: nested_item.get_int(&[6]).and_then(|v| ChannelType::from_u8(v as u8)),
})
} else {
None
}
} else {
None
}
},
start_time: list_item.get_int(&[2]),
end_time: list_item.get_int(&[3]),
title: list_item.get_string_owned(&[4]),
subtitle: list_item.get_string_owned(&[5]),
description: list_item.get_string_owned(&[6]),
audio_languages: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[7]) {
let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
Some(items)
} else {
None
}
},
ratings: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[8]) {
let items: Vec<String> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::String(v) = &e.value { Some(v.clone()) } else { None } }).collect();
Some(items)
} else {
None
}
},
thumbnail_url: list_item.get_string_owned(&[9]),
poster_art_url: list_item.get_string_owned(&[10]),
dvbi_url: list_item.get_string_owned(&[11]),
release_date: list_item.get_string_owned(&[12]),
parental_guidance_text: list_item.get_string_owned(&[13]),
recording_flag: list_item.get_int(&[14]).map(|v| v as u8),
series_info: {
if let Some(nested_tlv) = list_item.get(&[15]) {
if let tlv::TlvItemValue::List(_) = nested_tlv {
let nested_item = tlv::TlvItem { tag: 15, value: nested_tlv.clone() };
Some(SeriesInfo {
season: nested_item.get_string_owned(&[0]),
episode: nested_item.get_string_owned(&[1]),
})
} else {
None
}
} else {
None
}
},
category_list: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[16]) {
let mut items = Vec::new();
for list_item in l {
items.push(ProgramCategory {
category: list_item.get_string_owned(&[0]),
sub_category: list_item.get_string_owned(&[1]),
});
}
Some(items)
} else {
None
}
},
cast_list: {
if let Some(tlv::TlvItemValue::List(l)) = list_item.get(&[17]) {
let mut items = Vec::new();
for list_item in l {
items.push(ProgramCast {
name: list_item.get_string_owned(&[0]),
role: list_item.get_string_owned(&[1]),
});
}
Some(items)
} else {
None
}
},
});
}
Some(items)
} else {
None
}
},
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}