use futures::Stream;
use std::pin::Pin;
#[derive(Debug, Clone)]
pub enum NetworkEvent {
Connected,
}
const NM_STATE_CONNECTED_GLOBAL: u32 = 70;
fn decode_state_changed(msg: &zbus::Message) -> Option<u32> {
match msg.body().deserialize::<(u32,)>() {
Ok(body) => {
tracing::debug!("NetworkManager state changed: {}", body.0);
Some(body.0)
}
Err(e) => {
tracing::debug!("NetworkManager message decode error: {}", e);
None
}
}
}
pub fn network_stream() -> Pin<Box<dyn Stream<Item = NetworkEvent> + Send>> {
Box::pin(async_stream::stream! {
let Ok(connection) = zbus::Connection::system().await else {
tracing::warn!("Could not connect to system D-Bus, network monitoring disabled");
std::future::pending::<()>().await;
return;
};
let rule = "type='signal',\
sender='org.freedesktop.NetworkManager',\
interface='org.freedesktop.NetworkManager',\
member='StateChanged',\
path='/org/freedesktop/NetworkManager'";
if let Err(e) = connection
.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"AddMatch",
&rule,
)
.await
{
tracing::warn!("Failed to subscribe to NetworkManager signals: {}", e);
std::future::pending::<()>().await;
return;
}
tracing::info!("Listening for NetworkManager connectivity changes");
let mut stream = zbus::MessageStream::from(&connection);
use futures::StreamExt;
while let Some(item) = stream.next().await {
match item {
Err(e) => {
tracing::debug!("NetworkManager stream error: {}", e);
continue;
}
Ok(msg) => {
let header = msg.header();
if header.member().is_none_or(|m| m != "StateChanged")
|| header
.interface()
.is_none_or(|i| i != "org.freedesktop.NetworkManager")
{
continue;
}
if let Some(state) = decode_state_changed(&msg) {
if state == NM_STATE_CONNECTED_GLOBAL {
tracing::info!("Network connectivity restored");
yield NetworkEvent::Connected;
}
}
}
}
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_state_changed_returns_none_on_wrong_body_shape() {
let msg = zbus::Message::signal(
"/org/freedesktop/NetworkManager",
"org.freedesktop.NetworkManager",
"StateChanged",
)
.expect("valid signal builder")
.build(&("oops".to_string(),))
.expect("valid message build");
assert_eq!(decode_state_changed(&msg), None);
}
#[test]
fn decode_state_changed_returns_some_on_well_formed_body() {
let msg = zbus::Message::signal(
"/org/freedesktop/NetworkManager",
"org.freedesktop.NetworkManager",
"StateChanged",
)
.expect("valid signal builder")
.build(&(70u32,))
.expect("valid message build");
assert_eq!(decode_state_changed(&msg), Some(70));
}
}