matc/clusters/codec/
content_control.rs

1//! Generated 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// Struct definitions
12
13#[derive(Debug, serde::Serialize)]
14pub struct AppInfo {
15    pub catalog_vendor_id: Option<u16>,
16    pub application_id: Option<String>,
17}
18
19#[derive(Debug, serde::Serialize)]
20pub struct BlockChannel {
21    pub block_channel_index: Option<u16>,
22    pub major_number: Option<u16>,
23    pub minor_number: Option<u16>,
24    pub identifier: Option<String>,
25}
26
27#[derive(Debug, serde::Serialize)]
28pub struct RatingName {
29    pub rating_name: Option<String>,
30    pub rating_name_desc: Option<String>,
31}
32
33#[derive(Debug, serde::Serialize)]
34pub struct TimePeriod {
35    pub start_hour: Option<u8>,
36    pub start_minute: Option<u8>,
37    pub end_hour: Option<u8>,
38    pub end_minute: Option<u8>,
39}
40
41#[derive(Debug, serde::Serialize)]
42pub struct TimeWindow {
43    pub time_window_index: Option<u16>,
44    pub day_of_week: Option<u8>,
45    pub time_period: Option<Vec<TimePeriod>>,
46}
47
48// Command encoders
49
50/// Encode UpdatePIN command (0x00)
51pub fn encode_update_pin(old_pin: String, new_pin: String) -> anyhow::Result<Vec<u8>> {
52    let tlv = tlv::TlvItemEnc {
53        tag: 0,
54        value: tlv::TlvItemValueEnc::StructInvisible(vec![
55        (0, tlv::TlvItemValueEnc::String(old_pin)).into(),
56        (1, tlv::TlvItemValueEnc::String(new_pin)).into(),
57        ]),
58    };
59    Ok(tlv.encode()?)
60}
61
62/// Encode AddBonusTime command (0x05)
63pub fn encode_add_bonus_time(pin_code: String, bonus_time: u8) -> anyhow::Result<Vec<u8>> {
64    let tlv = tlv::TlvItemEnc {
65        tag: 0,
66        value: tlv::TlvItemValueEnc::StructInvisible(vec![
67        (0, tlv::TlvItemValueEnc::String(pin_code)).into(),
68        (1, tlv::TlvItemValueEnc::UInt8(bonus_time)).into(),
69        ]),
70    };
71    Ok(tlv.encode()?)
72}
73
74/// Encode SetScreenDailyTime command (0x06)
75pub fn encode_set_screen_daily_time(screen_time: u8) -> anyhow::Result<Vec<u8>> {
76    let tlv = tlv::TlvItemEnc {
77        tag: 0,
78        value: tlv::TlvItemValueEnc::StructInvisible(vec![
79        (0, tlv::TlvItemValueEnc::UInt8(screen_time)).into(),
80        ]),
81    };
82    Ok(tlv.encode()?)
83}
84
85/// Encode SetOnDemandRatingThreshold command (0x09)
86pub fn encode_set_on_demand_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
87    let tlv = tlv::TlvItemEnc {
88        tag: 0,
89        value: tlv::TlvItemValueEnc::StructInvisible(vec![
90        (0, tlv::TlvItemValueEnc::String(rating)).into(),
91        ]),
92    };
93    Ok(tlv.encode()?)
94}
95
96/// Encode SetScheduledContentRatingThreshold command (0x0A)
97pub fn encode_set_scheduled_content_rating_threshold(rating: String) -> anyhow::Result<Vec<u8>> {
98    let tlv = tlv::TlvItemEnc {
99        tag: 0,
100        value: tlv::TlvItemValueEnc::StructInvisible(vec![
101        (0, tlv::TlvItemValueEnc::String(rating)).into(),
102        ]),
103    };
104    Ok(tlv.encode()?)
105}
106
107/// Encode AddBlockChannels command (0x0B)
108pub fn encode_add_block_channels(channels: Vec<u8>) -> anyhow::Result<Vec<u8>> {
109    let tlv = tlv::TlvItemEnc {
110        tag: 0,
111        value: tlv::TlvItemValueEnc::StructInvisible(vec![
112        (0, tlv::TlvItemValueEnc::StructAnon(channels.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
113        ]),
114    };
115    Ok(tlv.encode()?)
116}
117
118/// Encode RemoveBlockChannels command (0x0C)
119pub fn encode_remove_block_channels(channel_indexes: Vec<u8>) -> anyhow::Result<Vec<u8>> {
120    let tlv = tlv::TlvItemEnc {
121        tag: 0,
122        value: tlv::TlvItemValueEnc::StructInvisible(vec![
123        (0, tlv::TlvItemValueEnc::StructAnon(channel_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
124        ]),
125    };
126    Ok(tlv.encode()?)
127}
128
129/// Encode AddBlockApplications command (0x0D)
130pub fn encode_add_block_applications(applications: Vec<u8>) -> anyhow::Result<Vec<u8>> {
131    let tlv = tlv::TlvItemEnc {
132        tag: 0,
133        value: tlv::TlvItemValueEnc::StructInvisible(vec![
134        (0, tlv::TlvItemValueEnc::StructAnon(applications.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
135        ]),
136    };
137    Ok(tlv.encode()?)
138}
139
140/// Encode RemoveBlockApplications command (0x0E)
141pub fn encode_remove_block_applications(applications: Vec<u8>) -> anyhow::Result<Vec<u8>> {
142    let tlv = tlv::TlvItemEnc {
143        tag: 0,
144        value: tlv::TlvItemValueEnc::StructInvisible(vec![
145        (0, tlv::TlvItemValueEnc::StructAnon(applications.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
146        ]),
147    };
148    Ok(tlv.encode()?)
149}
150
151/// Encode SetBlockContentTimeWindow command (0x0F)
152pub fn encode_set_block_content_time_window(time_window: u8) -> anyhow::Result<Vec<u8>> {
153    let tlv = tlv::TlvItemEnc {
154        tag: 0,
155        value: tlv::TlvItemValueEnc::StructInvisible(vec![
156        (0, tlv::TlvItemValueEnc::UInt8(time_window)).into(),
157        ]),
158    };
159    Ok(tlv.encode()?)
160}
161
162/// Encode RemoveBlockContentTimeWindow command (0x10)
163pub fn encode_remove_block_content_time_window(time_window_indexes: Vec<u8>) -> anyhow::Result<Vec<u8>> {
164    let tlv = tlv::TlvItemEnc {
165        tag: 0,
166        value: tlv::TlvItemValueEnc::StructInvisible(vec![
167        (0, tlv::TlvItemValueEnc::StructAnon(time_window_indexes.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt8(v)).into()).collect())).into(),
168        ]),
169    };
170    Ok(tlv.encode()?)
171}
172
173// Attribute decoders
174
175/// Decode Enabled attribute (0x0000)
176pub fn decode_enabled(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
177    if let tlv::TlvItemValue::Bool(v) = inp {
178        Ok(*v)
179    } else {
180        Err(anyhow::anyhow!("Expected Bool"))
181    }
182}
183
184/// Decode OnDemandRatings attribute (0x0001)
185pub fn decode_on_demand_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
186    let mut res = Vec::new();
187    if let tlv::TlvItemValue::List(v) = inp {
188        for item in v {
189            res.push(RatingName {
190                rating_name: item.get_string_owned(&[0]),
191                rating_name_desc: item.get_string_owned(&[1]),
192            });
193        }
194    }
195    Ok(res)
196}
197
198/// Decode OnDemandRatingThreshold attribute (0x0002)
199pub fn decode_on_demand_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
200    if let tlv::TlvItemValue::String(v) = inp {
201        Ok(v.clone())
202    } else {
203        Err(anyhow::anyhow!("Expected String"))
204    }
205}
206
207/// Decode ScheduledContentRatings attribute (0x0003)
208pub fn decode_scheduled_content_ratings(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<RatingName>> {
209    let mut res = Vec::new();
210    if let tlv::TlvItemValue::List(v) = inp {
211        for item in v {
212            res.push(RatingName {
213                rating_name: item.get_string_owned(&[0]),
214                rating_name_desc: item.get_string_owned(&[1]),
215            });
216        }
217    }
218    Ok(res)
219}
220
221/// Decode ScheduledContentRatingThreshold attribute (0x0004)
222pub fn decode_scheduled_content_rating_threshold(inp: &tlv::TlvItemValue) -> anyhow::Result<String> {
223    if let tlv::TlvItemValue::String(v) = inp {
224        Ok(v.clone())
225    } else {
226        Err(anyhow::anyhow!("Expected String"))
227    }
228}
229
230/// Decode ScreenDailyTime attribute (0x0005)
231pub fn decode_screen_daily_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
232    if let tlv::TlvItemValue::Int(v) = inp {
233        Ok(*v as u8)
234    } else {
235        Err(anyhow::anyhow!("Expected Integer"))
236    }
237}
238
239/// Decode RemainingScreenTime attribute (0x0006)
240pub fn decode_remaining_screen_time(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
241    if let tlv::TlvItemValue::Int(v) = inp {
242        Ok(*v as u8)
243    } else {
244        Err(anyhow::anyhow!("Expected Integer"))
245    }
246}
247
248/// Decode BlockUnrated attribute (0x0007)
249pub fn decode_block_unrated(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
250    if let tlv::TlvItemValue::Bool(v) = inp {
251        Ok(*v)
252    } else {
253        Err(anyhow::anyhow!("Expected Bool"))
254    }
255}
256
257/// Decode BlockChannelList attribute (0x0008)
258pub fn decode_block_channel_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<BlockChannel>> {
259    let mut res = Vec::new();
260    if let tlv::TlvItemValue::List(v) = inp {
261        for item in v {
262            res.push(BlockChannel {
263                block_channel_index: item.get_int(&[0]).map(|v| v as u16),
264                major_number: item.get_int(&[1]).map(|v| v as u16),
265                minor_number: item.get_int(&[2]).map(|v| v as u16),
266                identifier: item.get_string_owned(&[3]),
267            });
268        }
269    }
270    Ok(res)
271}
272
273/// Decode BlockApplicationList attribute (0x0009)
274pub fn decode_block_application_list(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AppInfo>> {
275    let mut res = Vec::new();
276    if let tlv::TlvItemValue::List(v) = inp {
277        for item in v {
278            res.push(AppInfo {
279                catalog_vendor_id: item.get_int(&[0]).map(|v| v as u16),
280                application_id: item.get_string_owned(&[1]),
281            });
282        }
283    }
284    Ok(res)
285}
286
287/// Decode BlockContentTimeWindow attribute (0x000A)
288pub fn decode_block_content_time_window(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeWindow>> {
289    let mut res = Vec::new();
290    if let tlv::TlvItemValue::List(v) = inp {
291        for item in v {
292            res.push(TimeWindow {
293                time_window_index: item.get_int(&[0]).map(|v| v as u16),
294                day_of_week: item.get_int(&[1]).map(|v| v as u8),
295                time_period: {
296                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
297                        let mut items = Vec::new();
298                        for list_item in l {
299                            items.push(TimePeriod {
300                start_hour: list_item.get_int(&[0]).map(|v| v as u8),
301                start_minute: list_item.get_int(&[1]).map(|v| v as u8),
302                end_hour: list_item.get_int(&[2]).map(|v| v as u8),
303                end_minute: list_item.get_int(&[3]).map(|v| v as u8),
304                            });
305                        }
306                        Some(items)
307                    } else {
308                        None
309                    }
310                },
311            });
312        }
313    }
314    Ok(res)
315}
316
317
318// JSON dispatcher function
319
320/// Decode attribute value and return as JSON string
321/// 
322/// # Parameters
323/// * `cluster_id` - The cluster identifier
324/// * `attribute_id` - The attribute identifier
325/// * `tlv_value` - The TLV value to decode
326/// 
327/// # Returns
328/// JSON string representation of the decoded value or error
329pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
330    // Verify this is the correct cluster
331    if cluster_id != 0x050F {
332        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x050F, got {}\"}}", cluster_id);
333    }
334    
335    match attribute_id {
336        0x0000 => {
337            match decode_enabled(tlv_value) {
338                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
339                Err(e) => format!("{{\"error\": \"{}\"}}", e),
340            }
341        }
342        0x0001 => {
343            match decode_on_demand_ratings(tlv_value) {
344                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
345                Err(e) => format!("{{\"error\": \"{}\"}}", e),
346            }
347        }
348        0x0002 => {
349            match decode_on_demand_rating_threshold(tlv_value) {
350                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
351                Err(e) => format!("{{\"error\": \"{}\"}}", e),
352            }
353        }
354        0x0003 => {
355            match decode_scheduled_content_ratings(tlv_value) {
356                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
357                Err(e) => format!("{{\"error\": \"{}\"}}", e),
358            }
359        }
360        0x0004 => {
361            match decode_scheduled_content_rating_threshold(tlv_value) {
362                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
363                Err(e) => format!("{{\"error\": \"{}\"}}", e),
364            }
365        }
366        0x0005 => {
367            match decode_screen_daily_time(tlv_value) {
368                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
369                Err(e) => format!("{{\"error\": \"{}\"}}", e),
370            }
371        }
372        0x0006 => {
373            match decode_remaining_screen_time(tlv_value) {
374                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
375                Err(e) => format!("{{\"error\": \"{}\"}}", e),
376            }
377        }
378        0x0007 => {
379            match decode_block_unrated(tlv_value) {
380                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
381                Err(e) => format!("{{\"error\": \"{}\"}}", e),
382            }
383        }
384        0x0008 => {
385            match decode_block_channel_list(tlv_value) {
386                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
387                Err(e) => format!("{{\"error\": \"{}\"}}", e),
388            }
389        }
390        0x0009 => {
391            match decode_block_application_list(tlv_value) {
392                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
393                Err(e) => format!("{{\"error\": \"{}\"}}", e),
394            }
395        }
396        0x000A => {
397            match decode_block_content_time_window(tlv_value) {
398                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
399                Err(e) => format!("{{\"error\": \"{}\"}}", e),
400            }
401        }
402        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
403    }
404}
405
406/// Get list of all attributes supported by this cluster
407/// 
408/// # Returns
409/// Vector of tuples containing (attribute_id, attribute_name)
410pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
411    vec![
412        (0x0000, "Enabled"),
413        (0x0001, "OnDemandRatings"),
414        (0x0002, "OnDemandRatingThreshold"),
415        (0x0003, "ScheduledContentRatings"),
416        (0x0004, "ScheduledContentRatingThreshold"),
417        (0x0005, "ScreenDailyTime"),
418        (0x0006, "RemainingScreenTime"),
419        (0x0007, "BlockUnrated"),
420        (0x0008, "BlockChannelList"),
421        (0x0009, "BlockApplicationList"),
422        (0x000A, "BlockContentTimeWindow"),
423    ]
424}
425