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 StatusCode {
16 Invalidpincode = 2,
18 Invalidrating = 3,
20 Invalidchannel = 4,
22 Channelalreadyexist = 5,
24 Channelnotexist = 6,
26 Unidentifiableapplication = 7,
28 Applicationalreadyexist = 8,
30 Applicationnotexist = 9,
32 Timewindowalreadyexist = 10,
34 Timewindownotexist = 11,
36}
37
38impl StatusCode {
39 pub fn from_u8(value: u8) -> Option<Self> {
41 match value {
42 2 => Some(StatusCode::Invalidpincode),
43 3 => Some(StatusCode::Invalidrating),
44 4 => Some(StatusCode::Invalidchannel),
45 5 => Some(StatusCode::Channelalreadyexist),
46 6 => Some(StatusCode::Channelnotexist),
47 7 => Some(StatusCode::Unidentifiableapplication),
48 8 => Some(StatusCode::Applicationalreadyexist),
49 9 => Some(StatusCode::Applicationnotexist),
50 10 => Some(StatusCode::Timewindowalreadyexist),
51 11 => Some(StatusCode::Timewindownotexist),
52 _ => None,
53 }
54 }
55
56 pub fn to_u8(self) -> u8 {
58 self as u8
59 }
60}
61
62impl From<StatusCode> for u8 {
63 fn from(val: StatusCode) -> Self {
64 val as u8
65 }
66}
67
68pub type DayOfWeek = u8;
72
73pub mod dayofweek {
75 pub const SUNDAY: u8 = 0x01;
77 pub const MONDAY: u8 = 0x02;
79 pub const TUESDAY: u8 = 0x04;
81 pub const WEDNESDAY: u8 = 0x08;
83 pub const THURSDAY: u8 = 0x10;
85 pub const FRIDAY: u8 = 0x20;
87 pub const SATURDAY: u8 = 0x40;
89}
90
91#[derive(Debug, serde::Serialize)]
94pub struct AppInfo {
95 pub catalog_vendor_id: Option<u16>,
96 pub application_id: Option<String>,
97}
98
99#[derive(Debug, serde::Serialize)]
100pub struct BlockChannel {
101 pub block_channel_index: Option<u16>,
102 pub major_number: Option<u16>,
103 pub minor_number: Option<u16>,
104 pub identifier: Option<String>,
105}
106
107#[derive(Debug, serde::Serialize)]
108pub struct RatingName {
109 pub rating_name: Option<String>,
110 pub rating_name_desc: Option<String>,
111}
112
113#[derive(Debug, serde::Serialize)]
114pub struct TimePeriod {
115 pub start_hour: Option<u8>,
116 pub start_minute: Option<u8>,
117 pub end_hour: Option<u8>,
118 pub end_minute: Option<u8>,
119}
120
121#[derive(Debug, serde::Serialize)]
122pub struct TimeWindow {
123 pub time_window_index: Option<u16>,
124 pub day_of_week: Option<DayOfWeek>,
125 pub time_period: Option<Vec<TimePeriod>>,
126}
127
128pub fn encode_update_pin(old_pin: String, new_pin: String) -> anyhow::Result<Vec<u8>> {
132 let tlv = tlv::TlvItemEnc {
133 tag: 0,
134 value: tlv::TlvItemValueEnc::StructInvisible(vec![
135 (0, tlv::TlvItemValueEnc::String(old_pin)).into(),
136 (1, tlv::TlvItemValueEnc::String(new_pin)).into(),
137 ]),
138 };
139 Ok(tlv.encode()?)
140}
141
142pub fn encode_add_bonus_time(pin_code: String, bonus_time: u32) -> anyhow::Result<Vec<u8>> {
144 let tlv = tlv::TlvItemEnc {
145 tag: 0,
146 value: tlv::TlvItemValueEnc::StructInvisible(vec![
147 (0, tlv::TlvItemValueEnc::String(pin_code)).into(),
148 (1, tlv::TlvItemValueEnc::UInt32(bonus_time)).into(),
149 ]),
150 };
151 Ok(tlv.encode()?)
152}
153
154pub fn encode_set_screen_daily_time(screen_time: u32) -> anyhow::Result<Vec<u8>> {
156 let tlv = tlv::TlvItemEnc {
157 tag: 0,
158 value: tlv::TlvItemValueEnc::StructInvisible(vec![
159 (0, tlv::TlvItemValueEnc::UInt32(screen_time)).into(),
160 ]),
161 };
162 Ok(tlv.encode()?)
163}
164
165pub fn encode_set_on_demand_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
167 let tlv = tlv::TlvItemEnc {
168 tag: 0,
169 value: tlv::TlvItemValueEnc::StructInvisible(vec![
170 (0, tlv::TlvItemValueEnc::String(rating)).into(),
171 ]),
172 };
173 Ok(tlv.encode()?)
174}
175
176pub fn encode_set_scheduled_content_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
178 let tlv = tlv::TlvItemEnc {
179 tag: 0,
180 value: tlv::TlvItemValueEnc::StructInvisible(vec![
181 (0, tlv::TlvItemValueEnc::String(rating)).into(),
182 ]),
183 };
184 Ok(tlv.encode()?)
185}
186
187pub fn encode_add_block_channels(channels: Vec<BlockChannel>) -> anyhow::Result<Vec<u8>> {
189 let tlv = tlv::TlvItemEnc {
190 tag: 0,
191 value: tlv::TlvItemValueEnc::StructInvisible(vec![
192 (0, tlv::TlvItemValueEnc::Array(channels.into_iter().map(|v| {
193 let mut fields = Vec::new();
194 if let Some(x) = v.block_channel_index { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
195 if let Some(x) = v.major_number { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
196 if let Some(x) = v.minor_number { fields.push((2, tlv::TlvItemValueEnc::UInt16(x)).into()); }
197 if let Some(x) = v.identifier { fields.push((3, tlv::TlvItemValueEnc::String(x.clone())).into()); }
198 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
199 }).collect())).into(),
200 ]),
201 };
202 Ok(tlv.encode()?)
203}
204
205pub fn encode_remove_block_channels(channel_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
207 let tlv = tlv::TlvItemEnc {
208 tag: 0,
209 value: tlv::TlvItemValueEnc::StructInvisible(vec![
210 (0, tlv::TlvItemValueEnc::StructAnon(channel_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
211 ]),
212 };
213 Ok(tlv.encode()?)
214}
215
216pub fn encode_add_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
218 let tlv = tlv::TlvItemEnc {
219 tag: 0,
220 value: tlv::TlvItemValueEnc::StructInvisible(vec![
221 (0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
222 let mut fields = Vec::new();
223 if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
224 if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
225 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
226 }).collect())).into(),
227 ]),
228 };
229 Ok(tlv.encode()?)
230}
231
232pub fn encode_remove_block_applications(applications: Vec<AppInfo>) -> anyhow::Result<Vec<u8>> {
234 let tlv = tlv::TlvItemEnc {
235 tag: 0,
236 value: tlv::TlvItemValueEnc::StructInvisible(vec![
237 (0, tlv::TlvItemValueEnc::Array(applications.into_iter().map(|v| {
238 let mut fields = Vec::new();
239 if let Some(x) = v.catalog_vendor_id { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
240 if let Some(x) = v.application_id { fields.push((1, tlv::TlvItemValueEnc::String(x.clone())).into()); }
241 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
242 }).collect())).into(),
243 ]),
244 };
245 Ok(tlv.encode()?)
246}
247
248pub fn encode_set_block_content_time_window(time_window: TimeWindow) -> anyhow::Result<Vec<u8>> {
250 let mut time_window_fields = Vec::new();
252 if let Some(x) = time_window.time_window_index { time_window_fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
253 if let Some(x) = time_window.day_of_week { time_window_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
254 if let Some(listv) = time_window.time_period {
255 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
256 let mut nested_fields = Vec::new();
257 if let Some(x) = inner.start_hour { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x)).into()); }
258 if let Some(x) = inner.start_minute { nested_fields.push((1, tlv::TlvItemValueEnc::UInt8(x)).into()); }
259 if let Some(x) = inner.end_hour { nested_fields.push((2, tlv::TlvItemValueEnc::UInt8(x)).into()); }
260 if let Some(x) = inner.end_minute { nested_fields.push((3, tlv::TlvItemValueEnc::UInt8(x)).into()); }
261 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
262 }).collect();
263 time_window_fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
264 }
265 let tlv = tlv::TlvItemEnc {
266 tag: 0,
267 value: tlv::TlvItemValueEnc::StructInvisible(vec![
268 (0, tlv::TlvItemValueEnc::StructInvisible(time_window_fields)).into(),
269 ]),
270 };
271 Ok(tlv.encode()?)
272}
273
274pub fn encode_remove_block_content_time_window(time_window_indexes: Vec<u16>) -> anyhow::Result<Vec<u8>> {
276 let tlv = tlv::TlvItemEnc {
277 tag: 0,
278 value: tlv::TlvItemValueEnc::StructInvisible(vec![
279 (0, tlv::TlvItemValueEnc::StructAnon(time_window_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into(),
280 ]),
281 };
282 Ok(tlv.encode()?)
283}
284
285pub fn decode_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
289 if let tlv::TlvItemValue::Bool(v) = inp {
290 Ok(*v)
291 } else {
292 Err(anyhow::anyhow!("Expected Bool"))
293 }
294}
295
296pub fn decode_on_demand_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
298 let mut res = Vec::new();
299 if let tlv::TlvItemValue::List(v) = inp {
300 for item in v {
301 res.push(RatingName {
302 rating_name: item.get_string_owned(&[0]),
303 rating_name_desc: item.get_string_owned(&[1]),
304 });
305 }
306 }
307 Ok(res)
308}
309
310pub fn decode_on_demand_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
312 if let tlv::TlvItemValue::String(v) = inp {
313 Ok(v.clone())
314 } else {
315 Err(anyhow::anyhow!("Expected String"))
316 }
317}
318
319pub fn decode_scheduled_content_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
321 let mut res = Vec::new();
322 if let tlv::TlvItemValue::List(v) = inp {
323 for item in v {
324 res.push(RatingName {
325 rating_name: item.get_string_owned(&[0]),
326 rating_name_desc: item.get_string_owned(&[1]),
327 });
328 }
329 }
330 Ok(res)
331}
332
333pub fn decode_scheduled_content_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
335 if let tlv::TlvItemValue::String(v) = inp {
336 Ok(v.clone())
337 } else {
338 Err(anyhow::anyhow!("Expected String"))
339 }
340}
341
342pub fn decode_screen_daily_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
344 if let tlv::TlvItemValue::Int(v) = inp {
345 Ok(*v as u32)
346 } else {
347 Err(anyhow::anyhow!("Expected UInt32"))
348 }
349}
350
351pub fn decode_remaining_screen_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u32> {
353 if let tlv::TlvItemValue::Int(v) = inp {
354 Ok(*v as u32)
355 } else {
356 Err(anyhow::anyhow!("Expected UInt32"))
357 }
358}
359
360pub fn decode_block_unrated(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
362 if let tlv::TlvItemValue::Bool(v) = inp {
363 Ok(*v)
364 } else {
365 Err(anyhow::anyhow!("Expected Bool"))
366 }
367}
368
369pub fn decode_block_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<BlockChannel>> {
371 let mut res = Vec::new();
372 if let tlv::TlvItemValue::List(v) = inp {
373 for item in v {
374 res.push(BlockChannel {
375 block_channel_index: item.get_int(&[0]).map(|v| v as u16),
376 major_number: item.get_int(&[1]).map(|v| v as u16),
377 minor_number: item.get_int(&[2]).map(|v| v as u16),
378 identifier: item.get_string_owned(&[3]),
379 });
380 }
381 }
382 Ok(res)
383}
384
385pub fn decode_block_application_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AppInfo>> {
387 let mut res = Vec::new();
388 if let tlv::TlvItemValue::List(v) = inp {
389 for item in v {
390 res.push(AppInfo {
391 catalog_vendor_id: item.get_int(&[0]).map(|v| v as u16),
392 application_id: item.get_string_owned(&[1]),
393 });
394 }
395 }
396 Ok(res)
397}
398
399pub fn decode_block_content_time_window(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeWindow>> {
401 let mut res = Vec::new();
402 if let tlv::TlvItemValue::List(v) = inp {
403 for item in v {
404 res.push(TimeWindow {
405 time_window_index: item.get_int(&[0]).map(|v| v as u16),
406 day_of_week: item.get_int(&[1]).map(|v| v as u8),
407 time_period: {
408 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
409 let mut items = Vec::new();
410 for list_item in l {
411 items.push(TimePeriod {
412 start_hour: list_item.get_int(&[0]).map(|v| v as u8),
413 start_minute: list_item.get_int(&[1]).map(|v| v as u8),
414 end_hour: list_item.get_int(&[2]).map(|v| v as u8),
415 end_minute: list_item.get_int(&[3]).map(|v| v as u8),
416 });
417 }
418 Some(items)
419 } else {
420 None
421 }
422 },
423 });
424 }
425 }
426 Ok(res)
427}
428
429
430pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
442 if cluster_id != 0x050F {
444 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050F, got {}\"}}", cluster_id);
445 }
446
447 match attribute_id {
448 0x0000 => {
449 match decode_enabled(tlv_value) {
450 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
451 Err(e) => format!("{{\"error\": \"{}\"}}", e),
452 }
453 }
454 0x0001 => {
455 match decode_on_demand_ratings(tlv_value) {
456 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
457 Err(e) => format!("{{\"error\": \"{}\"}}", e),
458 }
459 }
460 0x0002 => {
461 match decode_on_demand_rating_threshold(tlv_value) {
462 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
463 Err(e) => format!("{{\"error\": \"{}\"}}", e),
464 }
465 }
466 0x0003 => {
467 match decode_scheduled_content_ratings(tlv_value) {
468 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
469 Err(e) => format!("{{\"error\": \"{}\"}}", e),
470 }
471 }
472 0x0004 => {
473 match decode_scheduled_content_rating_threshold(tlv_value) {
474 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
475 Err(e) => format!("{{\"error\": \"{}\"}}", e),
476 }
477 }
478 0x0005 => {
479 match decode_screen_daily_time(tlv_value) {
480 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
481 Err(e) => format!("{{\"error\": \"{}\"}}", e),
482 }
483 }
484 0x0006 => {
485 match decode_remaining_screen_time(tlv_value) {
486 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
487 Err(e) => format!("{{\"error\": \"{}\"}}", e),
488 }
489 }
490 0x0007 => {
491 match decode_block_unrated(tlv_value) {
492 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
493 Err(e) => format!("{{\"error\": \"{}\"}}", e),
494 }
495 }
496 0x0008 => {
497 match decode_block_channel_list(tlv_value) {
498 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
499 Err(e) => format!("{{\"error\": \"{}\"}}", e),
500 }
501 }
502 0x0009 => {
503 match decode_block_application_list(tlv_value) {
504 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
505 Err(e) => format!("{{\"error\": \"{}\"}}", e),
506 }
507 }
508 0x000A => {
509 match decode_block_content_time_window(tlv_value) {
510 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
511 Err(e) => format!("{{\"error\": \"{}\"}}", e),
512 }
513 }
514 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
515 }
516}
517
518pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
523 vec![
524 (0x0000, "Enabled"),
525 (0x0001, "OnDemandRatings"),
526 (0x0002, "OnDemandRatingThreshold"),
527 (0x0003, "ScheduledContentRatings"),
528 (0x0004, "ScheduledContentRatingThreshold"),
529 (0x0005, "ScreenDailyTime"),
530 (0x0006, "RemainingScreenTime"),
531 (0x0007, "BlockUnrated"),
532 (0x0008, "BlockChannelList"),
533 (0x0009, "BlockApplicationList"),
534 (0x000A, "BlockContentTimeWindow"),
535 ]
536}
537
538#[derive(Debug, serde::Serialize)]
539pub struct ResetPINResponse {
540 pub pin_code: Option<String>,
541}
542
543pub fn decode_reset_pin_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ResetPINResponse> {
547 if let tlv::TlvItemValue::List(_fields) = inp {
548 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
549 Ok(ResetPINResponse {
550 pin_code: item.get_string_owned(&[0]),
551 })
552 } else {
553 Err(anyhow::anyhow!("Expected struct fields"))
554 }
555}
556