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 Granularity {
16 Notimegranularity = 0,
18 Minutesgranularity = 1,
20 Secondsgranularity = 2,
22 Millisecondsgranularity = 3,
24 Microsecondsgranularity = 4,
26}
27
28impl Granularity {
29 pub fn from_u8(value: u8) -> Option<Self> {
31 match value {
32 0 => Some(Granularity::Notimegranularity),
33 1 => Some(Granularity::Minutesgranularity),
34 2 => Some(Granularity::Secondsgranularity),
35 3 => Some(Granularity::Millisecondsgranularity),
36 4 => Some(Granularity::Microsecondsgranularity),
37 _ => None,
38 }
39 }
40
41 pub fn to_u8(self) -> u8 {
43 self as u8
44 }
45}
46
47impl From<Granularity> for u8 {
48 fn from(val: Granularity) -> Self {
49 val as u8
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54#[repr(u8)]
55pub enum StatusCode {
56 Timenotaccepted = 2,
58}
59
60impl StatusCode {
61 pub fn from_u8(value: u8) -> Option<Self> {
63 match value {
64 2 => Some(StatusCode::Timenotaccepted),
65 _ => None,
66 }
67 }
68
69 pub fn to_u8(self) -> u8 {
71 self as u8
72 }
73}
74
75impl From<StatusCode> for u8 {
76 fn from(val: StatusCode) -> Self {
77 val as u8
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
82#[repr(u8)]
83pub enum TimeSource {
84 None = 0,
86 Unknown = 1,
88 Admin = 2,
90 Nodetimecluster = 3,
92 Nonmattersntp = 4,
94 Nonmatterntp = 5,
96 Mattersntp = 6,
98 Matterntp = 7,
100 Mixedntp = 8,
102 Nonmattersntpnts = 9,
104 Nonmatterntpnts = 10,
106 Mattersntpnts = 11,
108 Matterntpnts = 12,
110 Mixedntpnts = 13,
112 Cloudsource = 14,
114 Ptp = 15,
116 Gnss = 16,
118}
119
120impl TimeSource {
121 pub fn from_u8(value: u8) -> Option<Self> {
123 match value {
124 0 => Some(TimeSource::None),
125 1 => Some(TimeSource::Unknown),
126 2 => Some(TimeSource::Admin),
127 3 => Some(TimeSource::Nodetimecluster),
128 4 => Some(TimeSource::Nonmattersntp),
129 5 => Some(TimeSource::Nonmatterntp),
130 6 => Some(TimeSource::Mattersntp),
131 7 => Some(TimeSource::Matterntp),
132 8 => Some(TimeSource::Mixedntp),
133 9 => Some(TimeSource::Nonmattersntpnts),
134 10 => Some(TimeSource::Nonmatterntpnts),
135 11 => Some(TimeSource::Mattersntpnts),
136 12 => Some(TimeSource::Matterntpnts),
137 13 => Some(TimeSource::Mixedntpnts),
138 14 => Some(TimeSource::Cloudsource),
139 15 => Some(TimeSource::Ptp),
140 16 => Some(TimeSource::Gnss),
141 _ => None,
142 }
143 }
144
145 pub fn to_u8(self) -> u8 {
147 self as u8
148 }
149}
150
151impl From<TimeSource> for u8 {
152 fn from(val: TimeSource) -> Self {
153 val as u8
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
158#[repr(u8)]
159pub enum TimeZoneDatabase {
160 Full = 0,
162 Partial = 1,
164 None = 2,
166}
167
168impl TimeZoneDatabase {
169 pub fn from_u8(value: u8) -> Option<Self> {
171 match value {
172 0 => Some(TimeZoneDatabase::Full),
173 1 => Some(TimeZoneDatabase::Partial),
174 2 => Some(TimeZoneDatabase::None),
175 _ => None,
176 }
177 }
178
179 pub fn to_u8(self) -> u8 {
181 self as u8
182 }
183}
184
185impl From<TimeZoneDatabase> for u8 {
186 fn from(val: TimeZoneDatabase) -> Self {
187 val as u8
188 }
189}
190
191#[derive(Debug, serde::Serialize)]
194pub struct DSTOffset {
195 pub offset: Option<i32>,
196 pub valid_starting: Option<u64>,
197 pub valid_until: Option<u64>,
198}
199
200#[derive(Debug, serde::Serialize)]
201pub struct FabricScopedTrustedTimeSource {
202 pub node_id: Option<u64>,
203 pub endpoint: Option<u16>,
204}
205
206#[derive(Debug, serde::Serialize)]
207pub struct TimeZone {
208 pub offset: Option<i32>,
209 pub valid_at: Option<u64>,
210 pub name: Option<String>,
211}
212
213#[derive(Debug, serde::Serialize)]
214pub struct TrustedTimeSource {
215 pub fabric_index: Option<u8>,
216 pub node_id: Option<u64>,
217 pub endpoint: Option<u16>,
218}
219
220pub fn encode_set_utc_time(utc_time: u64, granularity: Granularity, time_source: TimeSource) -> anyhow::Result<Vec<u8>> {
224 let tlv = tlv::TlvItemEnc {
225 tag: 0,
226 value: tlv::TlvItemValueEnc::StructInvisible(vec![
227 (0, tlv::TlvItemValueEnc::UInt64(utc_time)).into(),
228 (1, tlv::TlvItemValueEnc::UInt8(granularity.to_u8())).into(),
229 (2, tlv::TlvItemValueEnc::UInt8(time_source.to_u8())).into(),
230 ]),
231 };
232 Ok(tlv.encode()?)
233}
234
235pub fn encode_set_trusted_time_source(trusted_time_source: Option<FabricScopedTrustedTimeSource>) -> anyhow::Result<Vec<u8>> {
237 let trusted_time_source_enc = if let Some(s) = trusted_time_source {
239 let mut fields = Vec::new();
240 if let Some(x) = s.node_id { fields.push((0, tlv::TlvItemValueEnc::UInt64(x)).into()); }
241 if let Some(x) = s.endpoint { fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
242 tlv::TlvItemValueEnc::StructInvisible(fields)
243 } else {
244 tlv::TlvItemValueEnc::StructInvisible(Vec::new())
245 };
246 let tlv = tlv::TlvItemEnc {
247 tag: 0,
248 value: tlv::TlvItemValueEnc::StructInvisible(vec![
249 (0, trusted_time_source_enc).into(),
250 ]),
251 };
252 Ok(tlv.encode()?)
253}
254
255pub fn encode_set_time_zone(time_zone: Vec<TimeZone>) -> anyhow::Result<Vec<u8>> {
257 let tlv = tlv::TlvItemEnc {
258 tag: 0,
259 value: tlv::TlvItemValueEnc::StructInvisible(vec![
260 (0, tlv::TlvItemValueEnc::Array(time_zone.into_iter().map(|v| {
261 let mut fields = Vec::new();
262 if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
263 if let Some(x) = v.valid_at { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
264 if let Some(x) = v.name { fields.push((2, tlv::TlvItemValueEnc::String(x.clone())).into()); }
265 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
266 }).collect())).into(),
267 ]),
268 };
269 Ok(tlv.encode()?)
270}
271
272pub fn encode_set_dst_offset(dst_offset: Vec<DSTOffset>) -> anyhow::Result<Vec<u8>> {
274 let tlv = tlv::TlvItemEnc {
275 tag: 0,
276 value: tlv::TlvItemValueEnc::StructInvisible(vec![
277 (0, tlv::TlvItemValueEnc::Array(dst_offset.into_iter().map(|v| {
278 let mut fields = Vec::new();
279 if let Some(x) = v.offset { fields.push((0, tlv::TlvItemValueEnc::Int32(x)).into()); }
280 if let Some(x) = v.valid_starting { fields.push((1, tlv::TlvItemValueEnc::UInt64(x)).into()); }
281 if let Some(x) = v.valid_until { fields.push((2, tlv::TlvItemValueEnc::UInt64(x)).into()); }
282 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
283 }).collect())).into(),
284 ]),
285 };
286 Ok(tlv.encode()?)
287}
288
289pub fn encode_set_default_ntp(default_ntp: Option<String>) -> anyhow::Result<Vec<u8>> {
291 let tlv = tlv::TlvItemEnc {
292 tag: 0,
293 value: tlv::TlvItemValueEnc::StructInvisible(vec![
294 (0, tlv::TlvItemValueEnc::String(default_ntp.unwrap_or("".to_string()))).into(),
295 ]),
296 };
297 Ok(tlv.encode()?)
298}
299
300pub fn decode_utc_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
304 if let tlv::TlvItemValue::Int(v) = inp {
305 Ok(Some(*v))
306 } else {
307 Ok(None)
308 }
309}
310
311pub fn decode_granularity(inp: &tlv::TlvItemValue) -> anyhow::Result<Granularity> {
313 if let tlv::TlvItemValue::Int(v) = inp {
314 Granularity::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
315 } else {
316 Err(anyhow::anyhow!("Expected Integer"))
317 }
318}
319
320pub fn decode_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeSource> {
322 if let tlv::TlvItemValue::Int(v) = inp {
323 TimeSource::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
324 } else {
325 Err(anyhow::anyhow!("Expected Integer"))
326 }
327}
328
329pub fn decode_trusted_time_source(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<TrustedTimeSource>> {
331 if let tlv::TlvItemValue::List(_fields) = inp {
332 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
334 Ok(Some(TrustedTimeSource {
335 fabric_index: item.get_int(&[0]).map(|v| v as u8),
336 node_id: item.get_int(&[1]),
337 endpoint: item.get_int(&[2]).map(|v| v as u16),
338 }))
339 } else {
343 Ok(None)
344 }
346}
347
348pub fn decode_default_ntp(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<String>> {
350 if let tlv::TlvItemValue::String(v) = inp {
351 Ok(Some(v.clone()))
352 } else {
353 Ok(None)
354 }
355}
356
357pub fn decode_time_zone(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<TimeZone>> {
359 let mut res = Vec::new();
360 if let tlv::TlvItemValue::List(v) = inp {
361 for item in v {
362 res.push(TimeZone {
363 offset: item.get_int(&[0]).map(|v| v as i32),
364 valid_at: item.get_int(&[1]),
365 name: item.get_string_owned(&[2]),
366 });
367 }
368 }
369 Ok(res)
370}
371
372pub fn decode_dst_offset(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<DSTOffset>> {
374 let mut res = Vec::new();
375 if let tlv::TlvItemValue::List(v) = inp {
376 for item in v {
377 res.push(DSTOffset {
378 offset: item.get_int(&[0]).map(|v| v as i32),
379 valid_starting: item.get_int(&[1]),
380 valid_until: item.get_int(&[2]),
381 });
382 }
383 }
384 Ok(res)
385}
386
387pub fn decode_local_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
389 if let tlv::TlvItemValue::Int(v) = inp {
390 Ok(Some(*v))
391 } else {
392 Ok(None)
393 }
394}
395
396pub fn decode_time_zone_database(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneDatabase> {
398 if let tlv::TlvItemValue::Int(v) = inp {
399 TimeZoneDatabase::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
400 } else {
401 Err(anyhow::anyhow!("Expected Integer"))
402 }
403}
404
405pub fn decode_ntp_server_available(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
407 if let tlv::TlvItemValue::Bool(v) = inp {
408 Ok(*v)
409 } else {
410 Err(anyhow::anyhow!("Expected Bool"))
411 }
412}
413
414pub fn decode_time_zone_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
416 if let tlv::TlvItemValue::Int(v) = inp {
417 Ok(*v as u8)
418 } else {
419 Err(anyhow::anyhow!("Expected UInt8"))
420 }
421}
422
423pub fn decode_dst_offset_list_max_size(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
425 if let tlv::TlvItemValue::Int(v) = inp {
426 Ok(*v as u8)
427 } else {
428 Err(anyhow::anyhow!("Expected UInt8"))
429 }
430}
431
432pub fn decode_supports_dns_resolve(inp: &tlv::TlvItemValue) -> anyhow::Result<bool> {
434 if let tlv::TlvItemValue::Bool(v) = inp {
435 Ok(*v)
436 } else {
437 Err(anyhow::anyhow!("Expected Bool"))
438 }
439}
440
441
442pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
454 if cluster_id != 0x0038 {
456 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0038, got {}\"}}", cluster_id);
457 }
458
459 match attribute_id {
460 0x0000 => {
461 match decode_utc_time(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 0x0001 => {
467 match decode_granularity(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 0x0002 => {
473 match decode_time_source(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 0x0003 => {
479 match decode_trusted_time_source(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 0x0004 => {
485 match decode_default_ntp(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 0x0005 => {
491 match decode_time_zone(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 0x0006 => {
497 match decode_dst_offset(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 0x0007 => {
503 match decode_local_time(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 0x0008 => {
509 match decode_time_zone_database(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 0x0009 => {
515 match decode_ntp_server_available(tlv_value) {
516 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
517 Err(e) => format!("{{\"error\": \"{}\"}}", e),
518 }
519 }
520 0x000A => {
521 match decode_time_zone_list_max_size(tlv_value) {
522 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
523 Err(e) => format!("{{\"error\": \"{}\"}}", e),
524 }
525 }
526 0x000B => {
527 match decode_dst_offset_list_max_size(tlv_value) {
528 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
529 Err(e) => format!("{{\"error\": \"{}\"}}", e),
530 }
531 }
532 0x000C => {
533 match decode_supports_dns_resolve(tlv_value) {
534 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
535 Err(e) => format!("{{\"error\": \"{}\"}}", e),
536 }
537 }
538 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
539 }
540}
541
542pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
547 vec![
548 (0x0000, "UTCTime"),
549 (0x0001, "Granularity"),
550 (0x0002, "TimeSource"),
551 (0x0003, "TrustedTimeSource"),
552 (0x0004, "DefaultNTP"),
553 (0x0005, "TimeZone"),
554 (0x0006, "DSTOffset"),
555 (0x0007, "LocalTime"),
556 (0x0008, "TimeZoneDatabase"),
557 (0x0009, "NTPServerAvailable"),
558 (0x000A, "TimeZoneListMaxSize"),
559 (0x000B, "DSTOffsetListMaxSize"),
560 (0x000C, "SupportsDNSResolve"),
561 ]
562}
563
564#[derive(Debug, serde::Serialize)]
565pub struct SetTimeZoneResponse {
566 pub dst_offset_required: Option<bool>,
567}
568
569pub fn decode_set_time_zone_response(inp: &tlv::TlvItemValue) -> anyhow::Result<SetTimeZoneResponse> {
573 if let tlv::TlvItemValue::List(_fields) = inp {
574 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
575 Ok(SetTimeZoneResponse {
576 dst_offset_required: item.get_bool(&[0]),
577 })
578 } else {
579 Err(anyhow::anyhow!("Expected struct fields"))
580 }
581}
582
583#[derive(Debug, serde::Serialize)]
584pub struct DSTStatusEvent {
585 pub dst_offset_active: Option<bool>,
586}
587
588#[derive(Debug, serde::Serialize)]
589pub struct TimeZoneStatusEvent {
590 pub offset: Option<i32>,
591 pub name: Option<String>,
592}
593
594pub fn decode_dst_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<DSTStatusEvent> {
598 if let tlv::TlvItemValue::List(_fields) = inp {
599 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
600 Ok(DSTStatusEvent {
601 dst_offset_active: item.get_bool(&[0]),
602 })
603 } else {
604 Err(anyhow::anyhow!("Expected struct fields"))
605 }
606}
607
608pub fn decode_time_zone_status_event(inp: &tlv::TlvItemValue) -> anyhow::Result<TimeZoneStatusEvent> {
610 if let tlv::TlvItemValue::List(_fields) = inp {
611 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
612 Ok(TimeZoneStatusEvent {
613 offset: item.get_int(&[0]).map(|v| v as i32),
614 name: item.get_string_owned(&[1]),
615 })
616 } else {
617 Err(anyhow::anyhow!("Expected struct fields"))
618 }
619}
620