1use alloc::{
5 collections::{BTreeSet, VecDeque},
6 string::String,
7 sync::Arc,
8 vec::Vec,
9};
10
11use ebus_mqtt::{Instant, Link, Message};
12use sha2::{Digest, Sha256};
13
14use super::key::{DevKey, KeyRef};
15use super::replay;
16use super::types::{Props, Slot};
17use super::{Controller, Device, Discovery, Event, Handled, Id, PropertyAddr, Value, prop_decl};
18use super::{Origin, PropertyView};
19use crate::datatype::check_value;
20use crate::description::{self, DescriptionError, DeviceDescription, Ignored};
21use crate::payload::{self, Received};
22use crate::state::{DeviceState, effective_state};
23use crate::topic::{self, TopicKind};
24use rpds::RedBlackTreeMapSync;
25
26pub(super) fn addr(key: &DevKey, slot: &Slot) -> PropertyAddr {
28 PropertyAddr {
29 device: key.id.clone(),
30 node: slot.node.clone(),
31 property: slot.prop.clone(),
32 }
33}
34
35pub(super) fn put_slot(props: &mut Props, slot: Slot) {
37 if let Some(s) = props
38 .get_mut(slot.node.as_str())
39 .and_then(|m| m.get_mut(slot.prop.as_str()))
40 {
41 *s = slot;
42 }
43}
44
45fn rebuild_slots(dev: &mut Device, desc: &DeviceDescription) -> usize {
49 let before: usize = dev.view.props.values().map(RedBlackTreeMapSync::size).sum();
50 let mut props = dev.view.props.clone();
51 let mut nodes: BTreeSet<&str> = BTreeSet::new();
52 let (mut total, mut present, mut kept) = (0u32, 0u32, 0usize);
53 for (ni, (nid, node)) in desc.nodes.iter().enumerate() {
54 let Some(node_id) = Id::new(nid) else {
55 continue;
56 };
57 nodes.insert(nid.as_str());
58 let old_map = props.get(nid.as_str()).cloned();
59 let mut map = old_map
60 .clone()
61 .unwrap_or_else(RedBlackTreeMapSync::new_sync);
62 let declared: BTreeSet<&str> = node.properties.iter().map(|(p, _)| p.as_str()).collect();
63 let gone: Vec<Id> = map
64 .keys()
65 .filter(|p| !declared.contains(p.as_str()))
66 .cloned()
67 .collect();
68 for p in gone {
69 map.remove_mut(p.as_str());
70 }
71 for (pi, (pid, p)) in node.properties.iter().enumerate() {
72 let Some(prop_id) = Id::new(pid) else {
73 continue;
74 };
75 let old = map.get(pid.as_str());
76 kept += usize::from(old.is_some());
77 let mut slot = old.cloned().unwrap_or_else(|| Slot {
78 view: PropertyView::default(),
79 node: node_id.clone(),
80 prop: prop_id.clone(),
81 node_idx: ni,
82 prop_idx: pi,
83 retained: p.retained,
84 });
85 slot.node_idx = ni;
86 slot.prop_idx = pi;
87 slot.retained = p.retained;
88 if !p.retained {
89 slot.view.value = None;
90 slot.view.last_valid = None;
91 }
92 if let Some(v) = &mut slot.view.value {
93 v.check = check_value(&v.raw, p);
94 }
95 if let Some(v) = &mut slot.view.target {
96 v.check = check_value(&v.raw, p);
97 }
98 slot.view.last_valid = match &slot.view.value {
99 Some(v) if v.check.is_ok() => Some(v.raw.clone()),
100 _ => slot
101 .view
102 .last_valid
103 .take()
104 .filter(|lv| check_value(lv, p).is_ok()),
105 };
106 if p.retained {
107 total += 1;
108 if slot.view.value.is_some() {
109 present += 1;
110 }
111 }
112 if old != Some(&slot) {
113 map.insert_mut(prop_id, slot);
114 }
115 }
116 if !old_map.is_some_and(|m| m.ptr_eq(&map)) {
117 props.insert_mut(node_id, map);
118 }
119 }
120 let gone: Vec<Id> = props
121 .keys()
122 .filter(|n| !nodes.contains(n.as_str()))
123 .cloned()
124 .collect();
125 for n in gone {
126 props.remove_mut(n.as_str());
127 }
128 dev.marks.retain(|n, m| match props.get(n.as_str()) {
129 Some(pm) => {
130 m.retain(|p, _| pm.contains_key(p.as_str()));
131 !m.is_empty()
132 }
133 None => false,
134 });
135 dev.view.props = props;
136 dev.retained_total = total;
137 dev.retained_present = present;
138 before.saturating_sub(kept)
139}
140
141impl Controller {
142 pub fn handle_message(&mut self, now: Instant, msg: Message<'_>) -> Handled {
148 if self.shut_down || !self.wants(msg.topic) {
149 return Handled::Ignored;
150 }
151 let Ok(t) = topic::parse(msg.topic) else {
152 crate::log::debug!("invalidTopic", topic = msg.topic);
153 return Handled::Ignored;
154 };
155 let Some(device) = t.device else {
156 return Handled::Ignored;
157 };
158 if matches!(
159 t.kind,
160 TopicKind::PropertySet { .. } | TopicKind::Broadcast { .. }
161 ) {
162 return Handled::Ignored;
163 }
164 if msg.retain.is_none() && !self.no_retain_bit {
165 self.no_retain_bit = true;
166 for d in self.devices.values_mut() {
167 replay::abandon(d);
168 }
169 }
170 if !self.connected {
171 crate::log::warn!("implicitLinkUp", topic = msg.topic);
172 self.link(now, Link::up(false), true);
173 }
174 match t.kind {
175 TopicKind::DeviceState => self.on_state(now, t.domain, device, msg.payload),
176 TopicKind::DeviceDescription => self.on_description(now, t.domain, device, msg),
177 TopicKind::PropertyValue { node, property } => {
178 self.on_value(now, t.domain, device, node, property, msg, false)
179 }
180 TopicKind::PropertyTarget { node, property } => {
181 self.on_value(now, t.domain, device, node, property, msg, true)
182 }
183 TopicKind::DeviceAlert { alert_id } => {
184 self.on_alert(t.domain, device, alert_id, msg.payload)
185 }
186 TopicKind::DeviceLog { level } => {
187 let Some(dev) = self.devices.get_mut(&(device, t.domain) as &dyn KeyRef) else {
188 return Handled::Ignored;
189 };
190 dev.touch();
191 crate::log::debug!("deviceLog", device = device, level = level);
192 Handled::Consumed
193 }
194 TopicKind::PropertySet { .. } | TopicKind::Broadcast { .. } => Handled::Ignored,
195 }
196 }
197
198 fn at_capacity(&self) -> bool {
199 self.cfg
200 .max_devices
201 .is_some_and(|m| self.devices.len() >= m)
202 }
203
204 fn on_state(&mut self, now: Instant, domain: &str, id: &str, raw: &[u8]) -> Handled {
207 let received = match payload::classify(raw) {
208 Ok(r) => r,
209 Err(_) => {
210 crate::log::warn!("invalidState", device = id);
211 return Handled::Ignored;
212 }
213 };
214 let key = match self.key_of(id, domain) {
215 Some(k) => k,
216 None => match self.adopt(now, domain, id, received) {
217 Some(Ok(k)) => k,
218 Some(Err(h)) => return h,
219 None => return Handled::Ignored,
220 },
221 };
222 match received {
223 Received::Deleted => {
224 self.remove_device(now, &key);
225 }
226 Received::Text(text) => match DeviceState::parse(text) {
227 Some(state) => self.apply_state(now, &key, state),
228 None => {
229 crate::log::warn!("invalidState", device = id, state = text);
230 if let Some(d) = self.devices.get_mut(&key) {
231 d.touch();
232 }
233 }
234 },
235 }
236 Handled::Consumed
237 }
238
239 fn adopt(
242 &mut self,
243 now: Instant,
244 domain: &str,
245 id: &str,
246 received: Received<'_>,
247 ) -> Option<Result<DevKey, Handled>> {
248 let Received::Text(text) = received else {
249 return self.aux_state(domain, id, None);
250 };
251 let Some(state) = DeviceState::parse(text) else {
252 crate::log::warn!("invalidState", device = id, state = text);
253 return None;
254 };
255 let new_key = |c: &Controller| -> Option<DevKey> {
256 let domain: Arc<str> = match &c.named {
257 Some(d) => d.clone(),
258 None => Arc::from(domain),
259 };
260 Some(DevKey {
261 id: Id::new(id)?,
262 domain,
263 })
264 };
265 match &self.cfg.discovery {
266 Discovery::Wildcard => {
267 if self.at_capacity() {
268 crate::log::warn!("deviceLimit", device = id);
269 return None;
270 }
271 let key = new_key(self)?;
272 crate::log::info!("deviceDiscovered", device = id, state = state);
273 self.devices.insert(key.clone(), Device::new(&key));
274 self.request_subscription(now, &key, true, false);
275 Some(Ok(key))
276 }
277 Discovery::Tree { root } => {
278 let is_root = root.as_str() == id;
279 let declared = is_root || self.children.values().any(|s| s.contains(id));
280 if !declared || self.at_capacity() {
281 return None;
282 }
283 let key = new_key(self)?;
284 crate::log::info!("removedDeviceReturned", device = id);
285 self.devices.insert(key.clone(), Device::new(&key));
286 self.request_subscription(now, &key, !is_root, false);
287 Some(Ok(key))
288 }
289 Discovery::Device(dev_id) => {
290 if dev_id.as_str() != id {
291 return self.aux_state(domain, id, Some(state));
292 }
293 let key = new_key(self)?;
294 crate::log::info!("removedDeviceReturned", device = id);
295 self.devices.insert(key.clone(), Device::new(&key));
296 self.request_subscription(now, &key, false, false);
297 Some(Ok(key))
298 }
299 }
300 }
301
302 fn aux_state(
304 &mut self,
305 domain: &str,
306 id: &str,
307 state: Option<DeviceState>,
308 ) -> Option<Result<DevKey, Handled>> {
309 let Discovery::Device(dev_id) = &self.cfg.discovery else {
310 return None;
311 };
312 let dev_key = self.named_key(dev_id)?;
313 let is_root = self
314 .devices
315 .get(&dev_key)
316 .and_then(|d| d.root_filter.as_ref())
317 .is_some_and(|(r, _)| r == id);
318 if !is_root {
319 return None;
320 }
321 let root = Id::new(id)?;
322 match state {
323 Some(s) => {
324 self.aux_states.insert(root, s);
325 }
326 None => {
327 self.aux_states.remove(id);
328 }
329 }
330 let domain: Arc<str> = Arc::from(domain);
331 self.propagate_root(&domain, id, state);
332 Some(Err(Handled::Consumed))
333 }
334
335 fn apply_state(&mut self, now: Instant, key: &DevKey, new: DeviceState) {
336 let Some(dev) = self.devices.get_mut(key) else {
337 return;
338 };
339 dev.touch();
340 let old = dev.view.state;
341 let changed = old != Some(new);
342 if changed {
343 dev.view.state = Some(new);
344 let effective = self.effective_of(key).unwrap_or(new);
345 if let Some(dev) = self.devices.get_mut(key) {
346 dev.view.effective = Some(effective);
347 }
348 match old {
349 None => {
350 self.events.push_back(Event::DeviceDiscovered {
351 device: key.id.clone(),
352 state: new,
353 });
354 }
355 Some(o) => {
356 crate::log::info!("deviceStateChanged", device = key.id, old = o, new = new);
357 }
358 }
359 self.events.push_back(Event::StateChanged {
360 device: key.id.clone(),
361 old,
362 new,
363 effective,
364 });
365 self.propagate_root(&key.domain, key.id.as_str(), Some(new));
366 if new == DeviceState::Ready {
367 self.announce(key, false);
368 }
369 }
370 if changed
371 && new == DeviceState::Ready
372 && matches!(self.cfg.discovery, Discovery::Tree { .. })
373 {
374 self.reconcile(now, key);
375 }
376 self.note_progress(now, key);
377 }
378
379 fn announce(&mut self, key: &DevKey, while_ready: bool) {
382 let Some(dev) = self.devices.get_mut(key) else {
383 return;
384 };
385 let while_sleeping = core::mem::take(&mut dev.changed_while_sleeping);
386 let (Some(desc), Some(hash)) = (&dev.view.description, dev.content_hash) else {
387 return;
388 };
389 if dev.announced == Some(hash) {
390 return;
391 }
392 let while_ready = (while_ready || while_sleeping) && dev.announced.is_some();
393 if while_ready {
394 crate::log::warn!("descriptionChangedWhileReady", device = key.id);
395 }
396 dev.announced = Some(hash);
397 self.events.push_back(Event::DescriptionChanged {
398 device: key.id.clone(),
399 description: desc.clone(),
400 ignored: dev.ignored.clone(),
401 while_ready,
402 });
403 }
404
405 fn propagate_root(&mut self, domain: &Arc<str>, root: &str, root_state: Option<DeviceState>) {
408 for (k, d) in self.devices.iter_mut() {
409 if k.domain != *domain || d.root_id() != Some(root) {
410 continue;
411 }
412 let Some(own) = d.view.state else {
413 continue;
414 };
415 let new = effective_state(root_state, own);
416 if d.view.effective == Some(new) {
417 continue;
418 }
419 if let Some(old) = d.view.effective.replace(new) {
420 self.events.push_back(Event::EffectiveStateChanged {
421 device: k.id.clone(),
422 old,
423 new,
424 });
425 }
426 }
427 }
428
429 fn refresh_effective(&mut self, key: &DevKey) {
431 let new = self.effective_of(key);
432 let Some(dev) = self.devices.get_mut(key) else {
433 return;
434 };
435 if dev.view.effective == new {
436 return;
437 }
438 if let (Some(old), Some(new)) = (core::mem::replace(&mut dev.view.effective, new), new) {
439 self.events.push_back(Event::EffectiveStateChanged {
440 device: key.id.clone(),
441 old,
442 new,
443 });
444 }
445 }
446
447 fn remove_device(&mut self, now: Instant, key: &DevKey) {
452 let Some(dev) = self.devices.remove(key) else {
453 return;
454 };
455 self.forget(now, key);
456 match self.cfg.discovery {
457 Discovery::Wildcard => self.release_all(key),
458 _ => self.release_props(key),
459 }
460 if let Some((root, filter)) = &dev.root_filter {
461 if let Some(h) = self.holds.get_mut(key) {
462 h.attrs.retain(|f| f != filter);
463 }
464 let cmd = self.ledger.unsubscribe(alloc::vec![filter.clone()]);
465 self.push(cmd);
466 self.aux_states.remove(root.as_str());
467 }
468 self.dropped.remove(key);
469 if dev.view.state.is_some() {
470 crate::log::info!("deviceRemoved", device = key.id);
471 self.events.push_back(Event::DeviceRemoved {
472 device: key.id.clone(),
473 });
474 }
475 self.propagate_root(&key.domain, key.id.as_str(), None);
476 self.reevaluate_trees(key, dev.root_id());
477 }
478
479 fn reconcile(&mut self, now: Instant, key: &DevKey) {
484 let Some(desc) = self
485 .devices
486 .get(key)
487 .and_then(|d| d.view.description.clone())
488 else {
489 return;
490 };
491 let mut declared: Vec<Id> = Vec::new();
492 for c in &desc.children {
493 if c.as_str() == key.id.as_str() || declared.iter().any(|d| d == c.as_str()) {
494 continue;
495 }
496 if let Some(id) = Id::new(c) {
497 declared.push(id);
498 }
499 }
500 let current = self.children.get(key).cloned().unwrap_or_default();
501 for c in &declared {
502 let ckey = DevKey {
503 id: c.clone(),
504 domain: key.domain.clone(),
505 };
506 let known = self.devices.contains_key(&ckey);
507 if known && current.contains(c) {
508 continue;
509 }
510 if !known {
511 if self.at_capacity() {
512 crate::log::warn!("deviceLimit", device = c);
513 continue;
514 }
515 self.devices.insert(ckey.clone(), Device::new(&ckey));
516 }
517 self.children
518 .entry(key.clone())
519 .or_default()
520 .insert(c.clone());
521 if known {
522 continue;
524 }
525 crate::log::info!("treeRootedAddDescendant", parent = key.id, child = c);
526 self.request_subscription(now, &ckey, true, false);
527 }
528 if let Some(set) = self.children.get_mut(key) {
529 set.retain(|c| declared.contains(c));
530 }
531 let undeclared: Vec<&Id> = current.iter().filter(|c| !declared.contains(c)).collect();
532 if !undeclared.is_empty() {
533 let reachable = self.reachable(&key.domain);
534 for c in undeclared {
535 let ckey = DevKey {
536 id: c.clone(),
537 domain: key.domain.clone(),
538 };
539 if reachable.contains(c.as_str()) {
540 crate::log::info!("treeRootedKeepDescendant", parent = key.id, child = c);
541 continue;
542 }
543 crate::log::info!("treeRootedRemoveDescendant", parent = key.id, child = c);
544 let mut visited = BTreeSet::new();
545 self.drop_tree(now, &ckey, &reachable, &mut visited);
546 }
547 }
548 self.reevaluate_trees(key, None);
549 }
550
551 fn reachable(&self, domain: &Arc<str>) -> BTreeSet<Id> {
554 let mut seen = BTreeSet::new();
555 let Discovery::Tree { root } = &self.cfg.discovery else {
556 return seen;
557 };
558 let mut queue: VecDeque<Id> = VecDeque::from([root.clone()]);
559 while let Some(id) = queue.pop_front() {
560 if !seen.insert(id.clone()) {
561 continue;
562 }
563 let k = DevKey {
564 id,
565 domain: domain.clone(),
566 };
567 if let Some(set) = self.children.get(&k) {
568 queue.extend(set.iter().cloned());
569 }
570 }
571 seen
572 }
573
574 fn drop_tree(
577 &mut self,
578 now: Instant,
579 key: &DevKey,
580 reachable: &BTreeSet<Id>,
581 visited: &mut BTreeSet<DevKey>,
582 ) {
583 if reachable.contains(key.id.as_str()) {
584 return;
585 }
586 if let Discovery::Tree { root } = &self.cfg.discovery {
587 if *root == key.id {
588 return;
589 }
590 }
591 if !visited.insert(key.clone()) {
592 return;
593 }
594 for c in self.children.remove(key).unwrap_or_default() {
595 let ckey = DevKey {
596 id: c,
597 domain: key.domain.clone(),
598 };
599 self.drop_tree(now, &ckey, reachable, visited);
600 }
601 for set in self.children.values_mut() {
602 set.remove(key.id.as_str());
603 }
604 let dev = self.devices.remove(key);
605 self.forget(now, key);
606 self.release_all(key);
607 self.dropped.remove(key);
608 if dev.is_some_and(|d| d.view.state.is_some()) {
609 crate::log::info!("deviceRemoved", device = key.id);
610 self.events.push_back(Event::DeviceRemoved {
611 device: key.id.clone(),
612 });
613 }
614 }
615
616 pub(super) fn tree_complete(&self, root: &DevKey) -> bool {
618 let Some(desc) = self
619 .devices
620 .get(root)
621 .and_then(|r| r.view.description.as_ref())
622 else {
623 return false;
624 };
625 let mut seen: BTreeSet<&str> = BTreeSet::new();
626 seen.insert(root.id.as_str());
627 let mut queue: VecDeque<&str> = desc.children.iter().map(String::as_str).collect();
628 while let Some(c) = queue.pop_front() {
629 if !seen.insert(c) {
630 continue;
631 }
632 let Some(cd) = self
633 .devices
634 .get(&(c, &*root.domain) as &dyn KeyRef)
635 .and_then(|d| d.view.description.as_ref())
636 else {
637 return false;
638 };
639 queue.extend(cd.children.iter().map(String::as_str));
640 }
641 true
642 }
643
644 fn reevaluate_tree(&mut self, root: &DevKey) {
645 let complete = self.tree_complete(root);
646 let Some(d) = self.devices.get_mut(root) else {
647 return;
648 };
649 let was = core::mem::replace(&mut d.tree_complete, complete);
650 if complete && !was {
651 crate::log::info!("treeComplete", root = root.id);
652 self.events.push_back(Event::TreeComplete {
653 root: root.id.clone(),
654 });
655 }
656 }
657
658 fn reevaluate_trees(&mut self, key: &DevKey, declared_root: Option<&str>) {
661 let root = match declared_root.or_else(|| self.devices.get(key).and_then(Device::root_id)) {
662 Some(r) => Id::new(r).map(|id| DevKey {
663 id,
664 domain: key.domain.clone(),
665 }),
666 None => Some(key.clone()),
667 };
668 if let Some(r) = &root {
669 self.reevaluate_tree(r);
670 }
671 if let Discovery::Tree { root: tree_root } = &self.cfg.discovery {
672 if let Some(tk) = self.named_key(tree_root) {
673 if root.as_ref() != Some(&tk) {
674 self.reevaluate_tree(&tk);
675 }
676 }
677 }
678 }
679
680 fn on_description(
683 &mut self,
684 now: Instant,
685 domain: &str,
686 id: &str,
687 msg: Message<'_>,
688 ) -> Handled {
689 let Some(key) = self.key_of(id, domain) else {
690 return Handled::Ignored;
691 };
692 let marker = msg.retain != Some(false) && self.draining.contains_key(&key);
693 if msg.payload.is_empty() {
694 if let Some(d) = self.devices.get_mut(&key) {
695 d.touch();
696 }
697 crate::log::debug!("descriptionCleared", device = id);
698 if marker {
699 self.release_slot(now, &key);
700 }
701 return Handled::Consumed;
702 }
703 if let Some(a) = self.awaiting.get_mut(&key) {
704 a.refresh = false;
705 }
706 let hash: [u8; 32] = Sha256::digest(msg.payload).into();
707 let (same, was_rejected) = {
708 let Some(dev) = self.devices.get_mut(&key) else {
709 return Handled::Ignored;
710 };
711 dev.touch();
712 let was_rejected = dev.raw_hash.is_some() && dev.view.description.is_none();
713 (dev.raw_hash.replace(hash) == Some(hash), was_rejected)
714 };
715 if msg.retain == Some(true) {
716 self.note_replay(&key, msg.topic);
717 }
718 if marker {
719 self.release_slot(now, &key);
720 }
721 if !same {
722 let parsed = match core::str::from_utf8(msg.payload) {
723 Ok(text) => description::parse_with_ignored(text),
724 Err(_) => Err(DescriptionError::InvalidJson),
725 };
726 match parsed {
727 Ok((desc, ignored)) => {
728 self.apply_description(now, &key, desc, ignored);
729 if was_rejected {
730 self.replay_props(&key);
731 }
732 }
733 Err(e) => self.reject_description(now, &key, e),
734 }
735 }
736 self.note_progress(now, &key);
737 Handled::Consumed
738 }
739
740 fn replay_props(&mut self, key: &DevKey) {
743 if self.pending_entries.contains_key(key) || self.draining.contains_key(key) {
744 return;
745 }
746 let props = self
747 .holds
748 .get(key)
749 .map(|h| h.props.clone())
750 .unwrap_or_default();
751 if props.is_empty() {
752 return;
753 }
754 crate::log::info!("modelRestoredResubscribe", device = key.id);
755 let window = self
756 .devices
757 .get(key)
758 .filter(|_| !self.no_retain_bit)
759 .and_then(|d| {
760 let nodes = match &d.coverage {
761 super::Coverage::Nodes(s) => Some(s.iter().cloned().collect::<Vec<_>>()),
762 _ => None,
763 };
764 let alerts =
765 nodes.is_none() || props.contains(&Self::device_filter(key, super::ALERTS));
766 replay::holds_any(d, nodes.as_deref(), alerts).then_some((nodes, alerts))
767 });
768 match window {
769 Some((nodes, alerts)) => {
770 let mut filters = props;
771 filters.push(Self::device_filter(key, "$description"));
772 self.resubscribe(&filters);
773 self.open_window(key, nodes, alerts);
774 }
775 None => self.resubscribe(&props),
776 }
777 }
778
779 fn reject_description(&mut self, now: Instant, key: &DevKey, error: DescriptionError) {
780 let Some(dev) = self.devices.get_mut(key) else {
781 return;
782 };
783 crate::log::warn!("descriptionRejected", device = key.id, error = error);
784 let declared_root = dev.root_id().map(String::from);
785 dev.view.description = None;
786 dev.content_hash = None;
787 dev.announced = None;
788 dev.ignored.clear();
789 if !dev.view.props.is_empty() {
790 dev.view.props = Props::new_sync();
791 }
792 dev.marks.clear();
793 dev.retained_total = 0;
794 dev.retained_present = 0;
795 dev.view.synced = false;
796 dev.sync_at = None;
797 dev.sync_expired = false;
798 dev.early.clear();
799 dev.changed_while_sleeping = false;
800 replay::abandon(dev);
801 self.events.push_back(Event::DescriptionRejected {
802 device: key.id.clone(),
803 error,
804 });
805 self.forget(now, key);
806 self.update_root_filter(key);
807 self.refresh_effective(key);
808 self.reevaluate_trees(key, declared_root.as_deref());
809 }
810
811 fn apply_description(
812 &mut self,
813 now: Instant,
814 key: &DevKey,
815 desc: DeviceDescription,
816 ignored: Vec<Ignored>,
817 ) {
818 let content = desc.content_hash();
819 let sync_ms = self.cfg.sync_timeout_ms;
820 let Some(dev) = self.devices.get_mut(key) else {
821 return;
822 };
823 if dev.content_hash == Some(content) {
824 dev.ignored = ignored;
825 return;
826 }
827 let desc = Arc::new(desc);
828 let dropped = rebuild_slots(dev, &desc);
829 if dropped > 0 {
830 crate::log::debug!(
831 "vanishedPropertiesDropped",
832 device = key.id,
833 count = dropped
834 );
835 }
836 dev.view.description = Some(desc);
837 dev.content_hash = Some(content);
838 dev.ignored = ignored;
839 dev.view.synced = false;
840 dev.sync_at = Some(now.add_ms(sync_ms));
841 dev.sync_expired = false;
842 let early = core::mem::take(&mut dev.early);
843 let ready = dev.view.state == Some(DeviceState::Ready);
844 if dev.view.state == Some(DeviceState::Sleeping) && dev.announced.is_some() {
845 crate::log::warn!("descriptionChangedWhileSleeping", device = key.id);
846 dev.changed_while_sleeping = true;
847 }
848 crate::log::info!("descriptionReceived", device = key.id);
849 self.floor_sync(now.add_ms(sync_ms));
850 if ready {
851 self.announce(key, true);
852 }
853 self.prune_coverage(key);
854 self.update_root_filter(key);
855 self.refresh_effective(key);
856 self.recount_burst(now, key);
857 for ((node, prop), e) in early {
858 if let Some((text, retain)) = e.value {
859 self.store_value(key, &node, &prop, Some(&text), retain, false);
860 }
861 if let Some((text, retain)) = e.target {
862 self.store_value(key, &node, &prop, Some(&text), retain, true);
863 }
864 }
865 if ready && matches!(self.cfg.discovery, Discovery::Tree { .. }) {
866 self.reconcile(now, key);
867 }
868 self.reevaluate_trees(key, None);
869 self.check_sync(now, key);
870 }
871
872 fn prune_coverage(&mut self, key: &DevKey) {
875 let Some(dev) = self.devices.get_mut(key) else {
876 return;
877 };
878 let super::Coverage::Nodes(set) = &mut dev.coverage else {
879 return;
880 };
881 let props = &dev.view.props;
882 let gone: Vec<Id> = set
883 .iter()
884 .filter(|n| !props.contains_key(n.as_str()))
885 .cloned()
886 .collect();
887 if gone.is_empty() {
888 return;
889 }
890 for n in &gone {
891 set.remove(n);
892 }
893 if set.is_empty() {
894 dev.coverage = super::Coverage::None;
895 }
896 let filters = Self::prop_filters(key, Some(&gone));
897 if let Some(h) = self.holds.get_mut(key) {
898 h.props.retain(|f| !filters.contains(f));
899 }
900 crate::log::debug!("vanishedNodesReleased", device = key.id, nodes = gone.len());
901 let cmd = self.ledger.unsubscribe(filters);
902 self.push(cmd);
903 }
904
905 fn update_root_filter(&mut self, key: &DevKey) {
907 if !matches!(self.cfg.discovery, Discovery::Device(_)) {
908 return;
909 }
910 let Some(dev) = self.devices.get_mut(key) else {
911 return;
912 };
913 let want = dev.root_id().map(|r| {
914 (
915 String::from(r),
916 Self::topic_filter(&key.domain, r, "$state"),
917 )
918 });
919 if dev.root_filter == want {
920 return;
921 }
922 let old = core::mem::replace(&mut dev.root_filter, want.clone());
923 if let Some((root, filter)) = old {
924 if let Some(h) = self.holds.get_mut(key) {
925 h.attrs.retain(|f| *f != filter);
926 }
927 let cmd = self.ledger.unsubscribe(alloc::vec![filter]);
928 self.push(cmd);
929 self.aux_states.remove(root.as_str());
930 }
931 if let Some((_, filter)) = want {
932 self.hold(key, alloc::vec![filter], Vec::new(), false);
933 }
934 }
935
936 #[allow(clippy::too_many_arguments)]
939 fn on_value(
940 &mut self,
941 now: Instant,
942 domain: &str,
943 id: &str,
944 node: &str,
945 prop: &str,
946 msg: Message<'_>,
947 target: bool,
948 ) -> Handled {
949 let max_early = self.cfg.max_early_values;
950 let Some((key, dev)) = self.devices.get_key_value(&(id, domain) as &dyn KeyRef) else {
951 return Handled::Ignored;
952 };
953 let key = key.clone();
954 let has_model = dev.view.description.is_some();
955 let rejected = dev.raw_hash.is_some() && !has_model;
956 let text = match payload::classify(msg.payload) {
957 Ok(Received::Deleted) => None,
958 Ok(Received::Text(t)) => Some(t),
959 Err(_) => {
960 crate::log::warn!("invalidUtf8", topic = msg.topic);
961 return Handled::Consumed;
962 }
963 };
964 let Some(dev) = self.devices.get_mut(&key) else {
965 return Handled::Ignored;
966 };
967 dev.touch();
968 if !has_model {
969 if rejected {
970 crate::log::debug!("valueWithoutModel", topic = msg.topic);
971 return Handled::Consumed;
972 }
973 let k = (String::from(node), String::from(prop));
974 match text {
975 None => {
976 if let Some(e) = dev.early.get_mut(&k) {
977 if target {
978 e.target = None;
979 } else {
980 e.value = None;
981 }
982 if e.value.is_none() && e.target.is_none() {
983 dev.early.remove(&k);
984 }
985 }
986 }
987 Some(t) => {
988 if !dev.early.contains_key(&k) && dev.early.len() >= max_early {
989 crate::log::debug!("earlyValueDropped", topic = msg.topic);
990 return Handled::Consumed;
991 }
992 let e = dev.early.entry(k).or_default();
993 let v = Some((String::from(t), msg.retain));
994 if target {
995 e.target = v;
996 } else {
997 e.value = v;
998 }
999 }
1000 }
1001 return Handled::Consumed;
1002 }
1003 let counted = self.store_value(&key, node, prop, text, msg.retain, target);
1004 if counted {
1005 let done = self.draining.get(&key).is_some_and(|b| b.owed() == 0);
1006 if done {
1007 self.release_slot(now, &key);
1008 } else if !self.pending.is_empty() {
1009 self.take_pending(now);
1010 }
1011 }
1012 self.check_sync(now, &key);
1013 Handled::Consumed
1014 }
1015
1016 fn store_value(
1020 &mut self,
1021 key: &DevKey,
1022 node: &str,
1023 prop: &str,
1024 text: Option<&str>,
1025 retain: Option<bool>,
1026 target: bool,
1027 ) -> bool {
1028 let Some(dev) = self.devices.get_mut(key) else {
1029 return false;
1030 };
1031 let Some(desc) = dev.view.description.as_deref() else {
1032 return false;
1033 };
1034 let Some(old) = dev.view.props.get(node).and_then(|m| m.get(prop)) else {
1035 crate::log::debug!(
1036 "undeclaredProperty",
1037 device = key.id,
1038 node = node,
1039 property = prop
1040 );
1041 return false;
1042 };
1043 let Some(decl) = prop_decl(desc, old) else {
1044 return false;
1045 };
1046 let bit = if target { 2 } else { 1 };
1047 let marks = dev
1048 .marks
1049 .entry(old.node.clone())
1050 .or_default()
1051 .entry(old.prop.clone())
1052 .or_default();
1053 marks.seen |= bit;
1054 let mut slot = old.clone();
1055 let events = &mut self.events;
1056 let mut counts = false;
1057 if target {
1058 match text {
1059 None => {
1060 if slot.view.target.take().is_some() {
1061 events.push_back(Event::TargetChanged {
1062 addr: addr(key, &slot),
1063 target: None,
1064 });
1065 }
1066 }
1067 Some(t) => {
1068 counts = decl.retained;
1069 if slot.view.target.as_ref().is_none_or(|v| &*v.raw != t) {
1070 let v = Value {
1071 raw: Arc::from(t),
1072 check: check_value(t, decl),
1073 };
1074 slot.view.target = Some(v.clone());
1075 events.push_back(Event::TargetChanged {
1076 addr: addr(key, &slot),
1077 target: Some(v),
1078 });
1079 }
1080 }
1081 }
1082 } else if !decl.retained {
1083 match text {
1084 None => {
1085 crate::log::debug!(
1086 "eventPropertyCleared",
1087 device = key.id,
1088 node = node,
1089 property = prop
1090 );
1091 }
1092 Some(t) => {
1093 if retain == Some(true) {
1094 crate::log::info!(
1095 "retainedEventProperty",
1096 device = key.id,
1097 node = node,
1098 property = prop
1099 );
1100 }
1101 events.push_back(Event::PropertyEvent {
1102 addr: addr(key, &slot),
1103 value: Value {
1104 raw: Arc::from(t),
1105 check: check_value(t, decl),
1106 },
1107 origin: Origin::of(retain),
1108 });
1109 }
1110 }
1111 } else {
1112 match text {
1113 None => {
1114 if slot.view.value.take().is_some() {
1115 slot.view.last_valid = None;
1116 dev.retained_present = dev.retained_present.saturating_sub(1);
1117 events.push_back(Event::PropertyCleared {
1118 addr: addr(key, &slot),
1119 });
1120 }
1121 }
1122 Some(t) => {
1123 counts = true;
1124 if slot.view.value.as_ref().is_none_or(|v| &*v.raw != t) {
1125 if slot.view.value.is_none() {
1126 dev.retained_present += 1;
1127 }
1128 let v = Value {
1129 raw: Arc::from(t),
1130 check: check_value(t, decl),
1131 };
1132 if v.check.is_ok() {
1133 slot.view.last_valid = Some(v.raw.clone());
1134 }
1135 slot.view.value = Some(v.clone());
1136 events.push_back(Event::PropertyChanged {
1137 addr: addr(key, &slot),
1138 value: v,
1139 origin: Origin::of(retain),
1140 });
1141 }
1142 }
1143 }
1144 }
1145 if slot != *old {
1146 put_slot(&mut dev.view.props, slot);
1147 }
1148 if !counts {
1149 return false;
1150 }
1151 let Some(b) = self.draining.get_mut(key) else {
1152 return false;
1153 };
1154 if !b.covers(node) || marks.burst & bit != 0 {
1155 return false;
1156 }
1157 marks.burst |= bit;
1158 b.received = b.received.saturating_add(1);
1159 true
1160 }
1161
1162 fn on_alert(&mut self, domain: &str, id: &str, alert: &str, raw: &[u8]) -> Handled {
1165 let max_alerts = self.cfg.max_alerts;
1166 let Some((key, _)) = self.devices.get_key_value(&(id, domain) as &dyn KeyRef) else {
1167 return Handled::Ignored;
1168 };
1169 let device = key.id.clone();
1170 let Some(dev) = self.devices.get_mut(&(id, domain) as &dyn KeyRef) else {
1171 return Handled::Ignored;
1172 };
1173 dev.touch();
1174 let Some(alert_id) = Id::new(alert) else {
1175 return Handled::Consumed;
1176 };
1177 match payload::classify(raw) {
1178 Ok(Received::Deleted) => {
1179 if dev.view.alerts.contains_key(alert) {
1181 dev.view.alerts.remove_mut(alert);
1182 self.events.push_back(Event::Alert {
1183 device,
1184 alert: alert_id,
1185 message: None,
1186 });
1187 }
1188 }
1189 Ok(Received::Text(t)) => {
1190 let known = dev.view.alerts.contains_key(alert);
1191 if !known && dev.view.alerts.size() >= max_alerts {
1192 crate::log::debug!("alertDropped", device = id, alert = alert);
1193 return Handled::Consumed;
1194 }
1195 if dev.windows.iter().any(|w| w.alerts) {
1196 dev.seen_alerts.insert(alert_id.clone());
1197 }
1198 if dev.view.alerts.get(alert).map(String::as_str) != Some(t) {
1199 dev.view
1200 .alerts
1201 .insert_mut(alert_id.clone(), String::from(t));
1202 self.events.push_back(Event::Alert {
1203 device,
1204 alert: alert_id,
1205 message: Some(String::from(t)),
1206 });
1207 }
1208 }
1209 Err(_) => {
1210 crate::log::warn!("invalidUtf8", device = id, alert = alert);
1211 }
1212 }
1213 Handled::Consumed
1214 }
1215
1216 pub(super) fn in_flight(&self, key: &DevKey) -> bool {
1219 self.awaiting.contains_key(key)
1220 || self.pending_entries.contains_key(key)
1221 || self.draining.contains_key(key)
1222 }
1223
1224 pub(super) fn check_sync(&mut self, now: Instant, key: &DevKey) {
1226 match self.devices.get(key) {
1227 Some(d) if !d.view.synced && d.view.state.is_some() && d.view.description.is_some() => {
1228 }
1229 _ => return,
1230 }
1231 if self.in_flight(key) {
1232 return;
1233 }
1234 let Some(dev) = self.devices.get_mut(key) else {
1235 return;
1236 };
1237 let missing = dev.missing();
1238 let expired = dev.sync_expired || dev.sync_at.is_some_and(|t| t <= now);
1239 if (missing > 0 || !dev.windows.is_empty()) && !expired {
1240 return;
1241 }
1242 dev.view.synced = true;
1243 dev.sync_at = None;
1244 dev.sync_expired = false;
1245 crate::log::info!("deviceSynced", device = key.id, missing = missing);
1246 self.events.push_back(Event::DeviceSynced {
1247 device: key.id.clone(),
1248 missing,
1249 });
1250 }
1251}