matc/clusters/codec/
content_control.rs

1//! Matter TLV encoders and decoders for Content Control Cluster
2//! Cluster ID: 0x050F
3//!
4//! This file is automatically generated from ContentControl.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Enum definitions
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[repr(u8)]
15pub enum StatusCode {
16    /// Provided PIN Code does not match the current PIN code.
17    Invalidpincode = 2,
18    /// Provided Rating is out of scope of the corresponding Rating list.
19    Invalidrating = 3,
20    /// Provided Channel(s) is invalid.
21    Invalidchannel = 4,
22    /// Provided Channel(s) already exists.
23    Channelalreadyexist = 5,
24    /// Provided Channel(s) doesn't exist in BlockChannelList attribute.
25    Channelnotexist = 6,
26    /// Provided Application(s) is not identified.
27    Unidentifiableapplication = 7,
28    /// Provided Application(s) already exists.
29    Applicationalreadyexist = 8,
30    /// Provided Application(s) doesn't exist in BlockApplicationList attribute.
31    Applicationnotexist = 9,
32    /// Provided time Window already exists in BlockContentTimeWindow attribute.
33    Timewindowalreadyexist = 10,
34    /// Provided time window doesn't exist in BlockContentTimeWindow attribute.
35    Timewindownotexist = 11,
36}
37
38impl StatusCode {
39    /// Convert from u8 value
40    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    /// Convert to u8 value
57    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
68// Bitmap definitions
69
70/// DayOfWeek bitmap type
71pub type DayOfWeek = u8;
72
73/// Constants for DayOfWeek
74pub mod dayofweek {
75    /// Sunday
76    pub const SUNDAY: u8 = 0x01;
77    /// Monday
78    pub const MONDAY: u8 = 0x02;
79    /// Tuesday
80    pub const TUESDAY: u8 = 0x04;
81    /// Wednesday
82    pub const WEDNESDAY: u8 = 0x08;
83    /// Thursday
84    pub const THURSDAY: u8 = 0x10;
85    /// Friday
86    pub const FRIDAY: u8 = 0x20;
87    /// Saturday
88    pub const SATURDAY: u8 = 0x40;
89}
90
91// Struct definitions
92
93#[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
128// Command encoders
129
130/// Encode UpdatePIN command (0x00)
131pub 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
142/// Encode AddBonusTime command (0x05)
143pub 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
154/// Encode SetScreenDailyTime command (0x06)
155pub 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
165/// Encode SetOnDemandRatingThreshold command (0x09)
166pub 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
176/// Encode SetScheduledContentRatingThreshold command (0x0A)
177pub 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
187/// Encode AddBlockChannels command (0x0B)
188pub 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
205/// Encode RemoveBlockChannels command (0x0C)
206pub 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
216/// Encode AddBlockApplications command (0x0D)
217pub 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
232/// Encode RemoveBlockApplications command (0x0E)
233pub 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
248/// Encode SetBlockContentTimeWindow command (0x0F)
249pub fn encode_set_block_content_time_window(time_window: TimeWindow) -> anyhow::Result<Vec<u8>> {
250            // Encode struct TimeWindowStruct
251            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
274/// Encode RemoveBlockContentTimeWindow command (0x10)
275pub 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
285// Attribute decoders
286
287/// Decode Enabled attribute (0x0000)
288pub 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
296/// Decode OnDemandRatings attribute (0x0001)
297pub 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
310/// Decode OnDemandRatingThreshold attribute (0x0002)
311pub 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
319/// Decode ScheduledContentRatings attribute (0x0003)
320pub 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
333/// Decode ScheduledContentRatingThreshold attribute (0x0004)
334pub 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
342/// Decode ScreenDailyTime attribute (0x0005)
343pub 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
351/// Decode RemainingScreenTime attribute (0x0006)
352pub 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
360/// Decode BlockUnrated attribute (0x0007)
361pub 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
369/// Decode BlockChannelList attribute (0x0008)
370pub 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
385/// Decode BlockApplicationList attribute (0x0009)
386pub 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
399/// Decode BlockContentTimeWindow attribute (0x000A)
400pub 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
430// JSON dispatcher function
431
432/// Decode attribute value and return as JSON string
433///
434/// # Parameters
435/// * `cluster_id` - The cluster identifier
436/// * `attribute_id` - The attribute identifier
437/// * `tlv_value` - The TLV value to decode
438///
439/// # Returns
440/// JSON string representation of the decoded value or error
441pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
442    // Verify this is the correct cluster
443    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
518/// Get list of all attributes supported by this cluster
519///
520/// # Returns
521/// Vector of tuples containing (attribute_id, attribute_name)
522pub 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
543// Command response decoders
544
545/// Decode ResetPINResponse command response (02)
546pub 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