use core::cell::RefCell;
use std::{collections::VecDeque, rc::Rc};
use mdns_proto::{ServiceHandle, ServiceUpdate};
use crate::driver::EndpointInner;
pub(crate) const SERVICE_UPDATE_CAPACITY: usize = 16;
pub(crate) struct ServiceMailbox {
updates: VecDeque<ServiceUpdate>,
terminal: Option<ServiceUpdate>,
terminal_delivered: bool,
}
#[cfg_attr(test, derive(Debug))]
enum Drained {
Update(ServiceUpdate),
Ended,
Empty,
}
impl ServiceMailbox {
fn new() -> Self {
Self {
updates: VecDeque::new(),
terminal: None,
terminal_delivered: false,
}
}
pub(crate) fn push_update(&mut self, upd: ServiceUpdate) {
if upd.is_conflict() || upd.is_host_conflict() {
self.set_terminal(upd);
return;
}
if upd.is_renamed() {
self.updates.retain(|u| !u.is_renamed());
self.bounded_push_back(upd);
return;
}
if upd.is_established() {
self.updates.retain(|u| !u.is_established());
self.bounded_push_back(upd);
return;
}
self.bounded_push_back(upd);
}
fn bounded_push_back(&mut self, upd: ServiceUpdate) {
if self.updates.len() >= SERVICE_UPDATE_CAPACITY {
self.updates.pop_front();
}
self.updates.push_back(upd);
}
pub(crate) fn set_terminal(&mut self, terminal: ServiceUpdate) {
if self.terminal.is_none() && !self.terminal_delivered {
self.terminal = Some(terminal);
}
}
#[cfg(test)]
pub(crate) fn non_terminal_len(&self) -> usize {
self.updates.len()
}
#[cfg(test)]
pub(crate) fn has_terminal(&self) -> bool {
self.terminal.is_some()
}
#[cfg(test)]
pub(crate) fn drain_for_test(&mut self) -> Option<ServiceUpdate> {
match self.drain() {
Drained::Update(upd) => Some(upd),
Drained::Ended | Drained::Empty => None,
}
}
#[cfg(test)]
pub(crate) fn fill_non_terminal_to_cap_for_test(&mut self) {
use mdns_proto::event::ServiceRenamed;
self.updates.clear();
for i in 0..SERVICE_UPDATE_CAPACITY {
self
.updates
.push_back(ServiceUpdate::Renamed(ServiceRenamed::new(
mdns_proto::Name::try_from_str(&format!("fill-{i}._ipp._tcp.local.")).unwrap(),
)));
}
}
fn drain(&mut self) -> Drained {
if let Some(upd) = self.updates.pop_front() {
Drained::Update(upd)
} else if let Some(terminal) = self.terminal.take() {
self.terminal_delivered = true;
Drained::Update(terminal)
} else if self.terminal_delivered {
Drained::Ended
} else {
Drained::Empty
}
}
}
pub(crate) fn new_service_mailbox() -> Rc<RefCell<ServiceMailbox>> {
Rc::new(RefCell::new(ServiceMailbox::new()))
}
pub struct Service {
pub(crate) inner: Rc<EndpointInner>,
pub(crate) handle: ServiceHandle,
pub(crate) mailbox: Rc<RefCell<ServiceMailbox>>,
}
impl Service {
#[inline]
pub const fn handle(&self) -> ServiceHandle {
self.handle
}
pub async fn next(&self) -> Option<ServiceUpdate> {
loop {
match self.mailbox.borrow_mut().drain() {
Drained::Update(upd) => return Some(upd),
Drained::Ended => return None,
Drained::Empty => {}
}
self.inner.notify.listen().await;
}
}
}
impl Drop for Service {
fn drop(&mut self) {
{
let mut st = self.inner.state.borrow_mut();
st.flag_service_unregistered(self.handle);
}
self.inner.mark_dirty();
}
}
#[cfg(test)]
mod tests;