1use anyhow::Result;
9
10use crate::tlv::{TlvItem, TlvItemValue};
11
12#[derive(Debug, Clone, PartialEq)]
15pub struct AttributePath {
16 pub endpoint: Option<u16>,
17 pub cluster: Option<u32>,
18 pub attribute: Option<u32>,
19}
20
21#[derive(Debug, Clone, PartialEq)]
23pub enum AttributeData {
24 Value(TlvItemValue),
25 Status { status: u8, cluster_status: Option<u8> },
26}
27
28#[derive(Debug, Clone, PartialEq)]
30pub struct AttributeReport {
31 pub path: AttributePath,
32 pub data: AttributeData,
33 pub data_version: Option<u32>,
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct EventReport {
39 pub endpoint: Option<u16>,
40 pub cluster: Option<u32>,
41 pub event: Option<u32>,
42 pub event_number: Option<u64>,
43 pub data: Option<TlvItemValue>,
44}
45
46#[derive(Debug, Clone, Default)]
48pub struct ReportData {
49 pub subscription_id: Option<u32>,
50 pub attribute_reports: Vec<AttributeReport>,
51 pub event_reports: Vec<EventReport>,
52 pub more_chunks: bool,
53 pub suppress_response: bool,
54}
55
56impl ReportData {
57 pub fn parse(tlv: &TlvItem) -> Result<ReportData> {
61 let mut out = ReportData {
62 subscription_id: tlv.get_u32(&[0]),
63 more_chunks: tlv.get_bool(&[3]).unwrap_or(false),
64 suppress_response: tlv.get_bool(&[4]).unwrap_or(false),
65 ..Default::default()
66 };
67
68 if let Some(reports) = tlv.get_item(&[1]) {
69 if let TlvItemValue::List(list) = &reports.value {
70 for ib in list {
71 if let Some(report) = parse_attribute_report_ib(ib) {
72 out.attribute_reports.push(report);
73 }
74 }
75 }
76 }
77
78 if let Some(reports) = tlv.get_item(&[2]) {
79 if let TlvItemValue::List(list) = &reports.value {
80 for ib in list {
81 out.event_reports.push(EventReport {
82 endpoint: ib.get_u16(&[1, 0, 1]),
83 cluster: ib.get_u32(&[1, 0, 2]),
84 event: ib.get_u32(&[1, 0, 3]),
85 event_number: ib.get_u64(&[1, 1]),
86 data: ib.get(&[1, 7]).cloned(),
87 });
88 }
89 }
90 }
91
92 Ok(out)
93 }
94
95 pub fn merge(&mut self, next: ReportData) {
97 if self.subscription_id.is_none() {
98 self.subscription_id = next.subscription_id;
99 }
100 self.attribute_reports.extend(next.attribute_reports);
101 self.event_reports.extend(next.event_reports);
102 self.more_chunks = next.more_chunks;
103 self.suppress_response = next.suppress_response;
104 }
105}
106
107fn parse_attribute_report_ib(ib: &TlvItem) -> Option<AttributeReport> {
108 if let Some(data) = ib.get(&[1, 2]) {
109 Some(AttributeReport {
110 path: AttributePath {
111 endpoint: ib.get_u16(&[1, 1, 2]),
112 cluster: ib.get_u32(&[1, 1, 3]),
113 attribute: ib.get_u32(&[1, 1, 4]),
114 },
115 data: AttributeData::Value(data.clone()),
116 data_version: ib.get_u32(&[1, 0]),
117 })
118 } else {
119 ib.get_u8(&[0, 1, 0]).map(|status| AttributeReport {
120 path: AttributePath {
121 endpoint: ib.get_u16(&[0, 0, 2]),
122 cluster: ib.get_u32(&[0, 0, 3]),
123 attribute: ib.get_u32(&[0, 0, 4]),
124 },
125 data: AttributeData::Status {
126 status,
127 cluster_status: ib.get_u8(&[0, 1, 1]),
128 },
129 data_version: None,
130 })
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct SubscribeResponse {
137 pub subscription_id: u32,
138 pub max_interval: u16,
139}
140
141impl SubscribeResponse {
142 pub fn parse(tlv: &TlvItem) -> Result<SubscribeResponse> {
144 Ok(SubscribeResponse {
145 subscription_id: tlv
146 .get_u32(&[0])
147 .ok_or_else(|| anyhow::anyhow!("subscribe response missing subscription id"))?,
148 max_interval: tlv.get_u16(&[2]).unwrap_or(0),
149 })
150 }
151}
152
153#[derive(Debug, Clone)]
156pub struct ReportUpdate {
157 pub subscription_id: u32,
158 pub attribute_reports: Vec<AttributeReport>,
159 pub event_reports: Vec<EventReport>,
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use crate::device_messages::{self, AttrReport};
166 use crate::messages::ProtocolMessageHeader;
167 use crate::tlv;
168
169 fn parse_payload(msg: &[u8]) -> ReportData {
170 let (_, rest) = ProtocolMessageHeader::decode(msg).unwrap();
171 let tlv = tlv::decode_tlv(&rest).unwrap();
172 ReportData::parse(&tlv).unwrap()
173 }
174
175 fn bool_value_tlv(value: bool) -> Vec<u8> {
176 let mut buf = tlv::TlvBuffer::new();
177 buf.write_bool(2, value).unwrap();
178 buf.data
179 }
180
181 #[test]
182 fn test_parse_data_report() {
183 let value_tlv = bool_value_tlv(true);
184 let msg = device_messages::im_report_data(
185 7,
186 &[AttrReport::Data { endpoint: 1, cluster: 6, attribute: 0, value_tlv }],
187 -1,
188 Some(0x1234),
189 false,
190 )
191 .unwrap();
192 let rd = parse_payload(&msg);
193 assert_eq!(rd.subscription_id, Some(0x1234));
194 assert!(!rd.more_chunks);
195 assert_eq!(rd.attribute_reports.len(), 1);
196 let rep = &rd.attribute_reports[0];
197 assert_eq!(rep.path.endpoint, Some(1));
198 assert_eq!(rep.path.cluster, Some(6));
199 assert_eq!(rep.path.attribute, Some(0));
200 assert_eq!(rep.data, AttributeData::Value(TlvItemValue::Bool(true)));
201 assert_eq!(rep.data_version, Some(0));
202 }
203
204 #[test]
205 fn test_parse_status_report() {
206 let msg = device_messages::im_report_data_status(7, 1, 6, 0, 0x86, -1).unwrap();
207 let rd = parse_payload(&msg);
208 assert_eq!(rd.attribute_reports.len(), 1);
209 let rep = &rd.attribute_reports[0];
210 assert_eq!(rep.path.endpoint, Some(1));
211 assert_eq!(
212 rep.data,
213 AttributeData::Status { status: 0x86, cluster_status: None }
214 );
215 }
216
217 #[test]
218 fn test_parse_mixed_reports_and_chunk_flag() {
219 let value_tlv = bool_value_tlv(false);
220 let msg = device_messages::im_report_data(
221 7,
222 &[
223 AttrReport::Data { endpoint: 1, cluster: 6, attribute: 0, value_tlv },
224 AttrReport::Status { endpoint: 2, cluster: 8, attribute: 3, status: 1 },
225 ],
226 -1,
227 None,
228 true,
229 )
230 .unwrap();
231 let rd = parse_payload(&msg);
232 assert_eq!(rd.subscription_id, None);
233 assert!(rd.more_chunks);
234 assert_eq!(rd.attribute_reports.len(), 2);
235 assert!(matches!(rd.attribute_reports[0].data, AttributeData::Value(_)));
236 assert!(matches!(rd.attribute_reports[1].data, AttributeData::Status { status: 1, .. }));
237 }
238
239 #[test]
240 fn test_parse_suppress_response() {
241 let mut buf = tlv::TlvBuffer::new();
242 buf.write_anon_struct().unwrap();
243 buf.write_array(1).unwrap();
244 buf.write_struct_end().unwrap();
245 buf.write_bool(4, true).unwrap();
246 buf.write_struct_end().unwrap();
247 let tlv = tlv::decode_tlv(&buf.data).unwrap();
248 let rd = ReportData::parse(&tlv).unwrap();
249 assert!(rd.suppress_response);
250 assert!(rd.attribute_reports.is_empty());
251 }
252
253 #[test]
254 fn test_parse_event_report() {
255 let mut buf = tlv::TlvBuffer::new();
256 buf.write_anon_struct().unwrap();
257 buf.write_uint32(0, 99).unwrap();
258 buf.write_array(2).unwrap();
259 buf.write_anon_struct().unwrap();
260 buf.write_struct(1).unwrap();
261 buf.write_list(0).unwrap();
262 buf.write_uint16(1, 1).unwrap();
263 buf.write_uint32(2, 0x101).unwrap();
264 buf.write_uint32(3, 2).unwrap();
265 buf.write_struct_end().unwrap();
266 buf.write_uint64(1, 42).unwrap();
267 buf.write_uint8(7, 5).unwrap();
268 buf.write_struct_end().unwrap();
269 buf.write_struct_end().unwrap();
270 buf.write_struct_end().unwrap();
271 let tlv = tlv::decode_tlv(&buf.data).unwrap();
272 let rd = ReportData::parse(&tlv).unwrap();
273 assert_eq!(rd.subscription_id, Some(99));
274 assert_eq!(rd.event_reports.len(), 1);
275 let ev = &rd.event_reports[0];
276 assert_eq!(ev.endpoint, Some(1));
277 assert_eq!(ev.cluster, Some(0x101));
278 assert_eq!(ev.event, Some(2));
279 assert_eq!(ev.event_number, Some(42));
280 assert_eq!(ev.data, Some(TlvItemValue::Int(5)));
281 }
282
283 #[test]
284 fn test_parse_missing_lists() {
285 let mut buf = tlv::TlvBuffer::new();
286 buf.write_anon_struct().unwrap();
287 buf.write_struct_end().unwrap();
288 let tlv = tlv::decode_tlv(&buf.data).unwrap();
289 let rd = ReportData::parse(&tlv).unwrap();
290 assert!(rd.attribute_reports.is_empty());
291 assert!(rd.event_reports.is_empty());
292 assert!(!rd.more_chunks);
293 assert!(!rd.suppress_response);
294 }
295
296 #[test]
297 fn test_merge() {
298 let v1 = bool_value_tlv(true);
299 let chunk1 = parse_payload(
300 &device_messages::im_report_data(
301 7,
302 &[AttrReport::Data { endpoint: 1, cluster: 6, attribute: 0, value_tlv: v1 }],
303 -1,
304 Some(5),
305 true,
306 )
307 .unwrap(),
308 );
309 let v2 = bool_value_tlv(false);
310 let chunk2 = parse_payload(
311 &device_messages::im_report_data(
312 7,
313 &[AttrReport::Data { endpoint: 2, cluster: 6, attribute: 0, value_tlv: v2 }],
314 -1,
315 None,
316 false,
317 )
318 .unwrap(),
319 );
320 let mut merged = chunk1;
321 assert!(merged.more_chunks);
322 merged.merge(chunk2);
323 assert!(!merged.more_chunks);
324 assert_eq!(merged.subscription_id, Some(5));
325 assert_eq!(merged.attribute_reports.len(), 2);
326 assert_eq!(merged.attribute_reports[1].path.endpoint, Some(2));
327 }
328
329 #[test]
330 fn test_parse_subscribe_response() {
331 let msg = device_messages::im_subscribe_response(77, 9, -1, 60).unwrap();
332 let (_, rest) = ProtocolMessageHeader::decode(&msg).unwrap();
333 let tlv = tlv::decode_tlv(&rest).unwrap();
334 let sr = SubscribeResponse::parse(&tlv).unwrap();
335 assert_eq!(sr.subscription_id, 77);
336 assert_eq!(sr.max_interval, 60);
337 }
338}