use crate::tlv;
use anyhow;
use serde_json;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum StatusCode {
Invalidpincode = 2,
Invalidrating = 3,
Invalidchannel = 4,
Channelalreadyexist = 5,
Channelnotexist = 6,
Unidentifiableapplication = 7,
Applicationalreadyexist = 8,
Applicationnotexist = 9,
Timewindowalreadyexist = 10,
Timewindownotexist = 11,
}
impl StatusCode {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
2 => Some(StatusCode::Invalidpincode),
3 => Some(StatusCode::Invalidrating),
4 => Some(StatusCode::Invalidchannel),
5 => Some(StatusCode::Channelalreadyexist),
6 => Some(StatusCode::Channelnotexist),
7 => Some(StatusCode::Unidentifiableapplication),
8 => Some(StatusCode::Applicationalreadyexist),
9 => Some(StatusCode::Applicationnotexist),
10 => Some(StatusCode::Timewindowalreadyexist),
11 => Some(StatusCode::Timewindownotexist),
_ => None,
}
}
pub fn to_u8(self) -> u8 {
self as u8
}
}
impl From<StatusCode> for u8 {
fn from(val: StatusCode) -> Self {
val as u8
}
}
pub type DayOfWeek = u8;
pub mod dayofweek {
pub const SUNDAY: u8 = 0x01;
pub const MONDAY: u8 = 0x02;
pub const TUESDAY: u8 = 0x04;
pub const WEDNESDAY: u8 = 0x08;
pub const THURSDAY: u8 = 0x10;
pub const FRIDAY: u8 = 0x20;
pub const SATURDAY: u8 = 0x40;
}
#[derive(Debug, serde::Serialize)]
pub struct AppInfo {
pub catalog_vendor_id: Option<u16>,
pub application_id: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct BlockChannel {
pub block_channel_index: Option<u16>,
pub major_number: Option<u16>,
pub minor_number: Option<u16>,
pub identifier: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct RatingName {
pub rating_name: Option<String>,
pub rating_name_desc: Option<String>,
}
#[derive(Debug, serde::Serialize)]
pub struct TimePeriod {
pub start_hour: Option<u8>,
pub start_minute: Option<u8>,
pub end_hour: Option<u8>,
pub end_minute: Option<u8>,
}
#[derive(Debug, serde::Serialize)]
pub struct TimeWindow {
pub time_window_index: Option<u16>,
pub day_of_week: Option<DayOfWeek>,
pub time_period: Option<Vec<TimePeriod>>,
}
pub fn encode_update_pin(old_pin: String, new_pin: String) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(old_pin)).into(),
(1, tlv::TlvItemValueEnc::String(new_pin)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_add_bonus_time(pin_code: String, bonus_time: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(pin_code)).into(),
(1, tlv::TlvItemValueEnc::UInt32(bonus_time)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_screen_daily_time(screen_time: u32) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::UInt32(screen_time)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_on_demand_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(rating)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_scheduled_content_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::String(rating)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_add_block_channels(channels: Vec<BlockChannel>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Array(channels.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.block_channel_index { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.major_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.minor_number { fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.identifier { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_remove_block_channels(channel_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::StructAnon(channel_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_add_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_remove_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
let mut fields = Vec::new();
if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
}).collect())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_set_block_content_time_window(time_window: TimeWindow) -> anyhow::Result<Vec<u8>> {
let mut time_window_fields = Vec::new();
if let Some(x) = time_window.time_window_index { time_window_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
if let Some(x) = time_window.day_of_week { time_window_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
if let Some(listv) = time_window.time_period {
let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
let mut nested_fields = Vec::new();
if let Some(x) = inner.start_hour { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
if let Some(x) = inner.start_minute { nested_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
if let Some(x) = inner.end_hour { nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
if let Some(x) = inner.end_minute { nested_fields.push((3, tlv::TlvItemValueEnc::UInt8(x)).into()); }
(0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
}).collect();
time_window_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
}
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::StructInvisible(time_window_fields)).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn encode_remove_block_content_time_window(time_window_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
let tlv = tlv::TlvItemEnc {
tag: 0,
value: tlv::TlvItemValueEnc::StructInvisible(vec![
(0, tlv::TlvItemValueEnc::StructAnon(time_window_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
]),
};
Ok(tlv.encode()?)
}
pub fn decode_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_on_demand_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(RatingName {
rating_name: item.get_string_owned(&[0]),
rating_name_desc: item.get_string_owned(&[1]),
});
}
}
Ok(res)
}
pub fn decode_on_demand_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
if let tlv::TlvItemValue::String(v) = inp {
Ok(v.clone())
} else {
Err(anyhow::anyhow!("Expected String"))
}
}
pub fn decode_scheduled_content_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(RatingName {
rating_name: item.get_string_owned(&[0]),
rating_name_desc: item.get_string_owned(&[1]),
});
}
}
Ok(res)
}
pub fn decode_scheduled_content_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
if let tlv::TlvItemValue::String(v) = inp {
Ok(v.clone())
} else {
Err(anyhow::anyhow!("Expected String"))
}
}
pub fn decode_screen_daily_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u32)
} else {
Err(anyhow::anyhow!("Expected UInt32"))
}
}
pub fn decode_remaining_screen_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
if let tlv::TlvItemValue::Int(v) = inp {
Ok(*v as u32)
} else {
Err(anyhow::anyhow!("Expected UInt32"))
}
}
pub fn decode_block_unrated(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
if let tlv::TlvItemValue::Bool(v) = inp {
Ok(*v)
} else {
Err(anyhow::anyhow!("Expected Bool"))
}
}
pub fn decode_block_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<BlockChannel>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(BlockChannel {
block_channel_index: item.get_int(&[0]).map(|v| v as u16),
major_number: item.get_int(&[1]).map(|v| v as u16),
minor_number: item.get_int(&[2]).map(|v| v as u16),
identifier: item.get_string_owned(&[3]),
});
}
}
Ok(res)
}
pub fn decode_block_application_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AppInfo>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(AppInfo {
catalog_vendor_id: item.get_int(&[0]).map(|v| v as u16),
application_id: item.get_string_owned(&[1]),
});
}
}
Ok(res)
}
pub fn decode_block_content_time_window(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeWindow>> {
let mut res = Vec::new();
if let tlv::TlvItemValue::List(v) = inp {
for item in v {
res.push(TimeWindow {
time_window_index: item.get_int(&[0]).map(|v| v as u16),
day_of_week: item.get_int(&[1]).map(|v| v as u8),
time_period: {
if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
let mut items = Vec::new();
for list_item in l {
items.push(TimePeriod {
start_hour: list_item.get_int(&[0]).map(|v| v as u8),
start_minute: list_item.get_int(&[1]).map(|v| v as u8),
end_hour: list_item.get_int(&[2]).map(|v| v as u8),
end_minute: list_item.get_int(&[3]).map(|v| v as u8),
});
}
Some(items)
} else {
None
}
},
});
}
}
Ok(res)
}
pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
if cluster_id != 0x050F {
return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050F, got {}\"}}", cluster_id);
}
match attribute_id {
0x0000 => {
match decode_enabled(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0001 => {
match decode_on_demand_ratings(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0002 => {
match decode_on_demand_rating_threshold(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0003 => {
match decode_scheduled_content_ratings(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0004 => {
match decode_scheduled_content_rating_threshold(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0005 => {
match decode_screen_daily_time(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0006 => {
match decode_remaining_screen_time(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0007 => {
match decode_block_unrated(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0008 => {
match decode_block_channel_list(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x0009 => {
match decode_block_application_list(tlv_value) {
Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
Err(e) => format!("{{\"error\": \"{}\"}}", e),
}
}
0x000A => {
match decode_block_content_time_window(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, "Enabled"),
(0x0001, "OnDemandRatings"),
(0x0002, "OnDemandRatingThreshold"),
(0x0003, "ScheduledContentRatings"),
(0x0004, "ScheduledContentRatingThreshold"),
(0x0005, "ScreenDailyTime"),
(0x0006, "RemainingScreenTime"),
(0x0007, "BlockUnrated"),
(0x0008, "BlockChannelList"),
(0x0009, "BlockApplicationList"),
(0x000A, "BlockContentTimeWindow"),
]
}
#[derive(Debug, serde::Serialize)]
pub struct ResetPINResponse {
pub pin_code: Option<String>,
}
pub fn decode_reset_pin_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ResetPINResponse> {
if let tlv::TlvItemValue::List(_fields) = inp {
let item = tlv::TlvItem { tag: 0, value: inp.clone() };
Ok(ResetPINResponse {
pin_code: item.get_string_owned(&[0]),
})
} else {
Err(anyhow::anyhow!("Expected struct fields"))
}
}