Skip to content

Commit 5dae5a6

Browse files
committed
Listen to AccessPoints changes
1 parent 2f7adb8 commit 5dae5a6

File tree

8 files changed

+274
-6
lines changed

8 files changed

+274
-6
lines changed

rust/agama-network/src/action.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ pub enum Action {
5454
GetConnections(Responder<Vec<Connection>>),
5555
/// Gets all scanned access points
5656
GetAccessPoints(Responder<Vec<AccessPoint>>),
57+
/// Adds a new access point.
58+
AddAccessPoint(Box<AccessPoint>),
59+
/// Removes an access point by its hardware address.
60+
RemoveAccessPoint(String),
5761
/// Adds a new device.
5862
AddDevice(Box<Device>),
5963
/// Updates a device by its `name`.

rust/agama-network/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub enum NetworkStateError {
4040
CannotUpdateConnection(String),
4141
#[error("Unknown device '{0}'")]
4242
UnknownDevice(String),
43+
#[error("Unknown access point '{0}'")]
44+
UnknownAccessPoint(String),
4345
#[error("Invalid connection UUID: '{0}'")]
4446
InvalidUuid(String),
4547
#[error("Invalid IP address: '{0}'")]

rust/agama-network/src/model.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,32 @@ impl NetworkState {
343343
Ok(())
344344
}
345345

346+
pub fn add_access_point(&mut self, ap: AccessPoint) -> Result<(), NetworkStateError> {
347+
if let Some(position) = self
348+
.access_points
349+
.iter()
350+
.position(|a| a.hw_address == ap.hw_address)
351+
{
352+
self.access_points.remove(position);
353+
}
354+
self.access_points.push(ap);
355+
356+
Ok(())
357+
}
358+
359+
pub fn remove_access_point(&mut self, hw_address: &str) -> Result<(), NetworkStateError> {
360+
let Some(position) = self
361+
.access_points
362+
.iter()
363+
.position(|a| a.hw_address == hw_address)
364+
else {
365+
return Err(NetworkStateError::UnknownAccessPoint(hw_address.to_string()));
366+
};
367+
368+
self.access_points.remove(position);
369+
Ok(())
370+
}
371+
346372
/// Sets a controller's ports.
347373
///
348374
/// If the connection is not a controller, returns an error.
@@ -460,6 +486,57 @@ mod tests {
460486
assert!(matches!(error, NetworkStateError::UnknownConnection(_)));
461487
}
462488

489+
#[test]
490+
fn test_remove_device() {
491+
let mut state = NetworkState::default();
492+
let device = Device {
493+
name: "eth0".to_string(),
494+
..Default::default()
495+
};
496+
state.add_device(device).unwrap();
497+
state.remove_device("eth0").unwrap();
498+
assert!(state.get_device("eth0").is_none());
499+
}
500+
501+
#[test]
502+
fn test_add_access_point() {
503+
let mut state = NetworkState::default();
504+
let ap = AccessPoint {
505+
hw_address: "AA:BB:CC:DD:EE:FF".to_string(),
506+
ssid: SSID(b"test".to_vec()),
507+
..Default::default()
508+
};
509+
state.add_access_point(ap.clone()).unwrap();
510+
assert_eq!(state.access_points.len(), 1);
511+
assert_eq!(state.access_points[0].hw_address, "AA:BB:CC:DD:EE:FF");
512+
513+
// Adding same AP should replace it (in our implementation we remove and push)
514+
let mut ap2 = ap.clone();
515+
ap2.strength = 80;
516+
state.add_access_point(ap2).unwrap();
517+
assert_eq!(state.access_points.len(), 1);
518+
assert_eq!(state.access_points[0].strength, 80);
519+
}
520+
521+
#[test]
522+
fn test_remove_access_point() {
523+
let mut state = NetworkState::default();
524+
let ap = AccessPoint {
525+
hw_address: "AA:BB:CC:DD:EE:FF".to_string(),
526+
..Default::default()
527+
};
528+
state.add_access_point(ap).unwrap();
529+
state.remove_access_point("AA:BB:CC:DD:EE:FF").unwrap();
530+
assert_eq!(state.access_points.len(), 0);
531+
}
532+
533+
#[test]
534+
fn test_remove_unknown_access_point() {
535+
let mut state = NetworkState::default();
536+
let error = state.remove_access_point("unknown").unwrap_err();
537+
assert!(matches!(error, NetworkStateError::UnknownAccessPoint(_)));
538+
}
539+
463540
#[test]
464541
fn test_is_loopback() {
465542
let conn = Connection::new("eth0".to_string(), DeviceType::Ethernet);
@@ -1740,6 +1817,10 @@ pub enum NetworkChange {
17401817
id: String,
17411818
state: ConnectionState,
17421819
},
1820+
/// A new access point has been added.
1821+
AccessPointAdded(AccessPoint),
1822+
/// An access point has been removed.
1823+
AccessPointRemoved(String),
17431824
}
17441825

17451826
#[derive(Default, Debug, PartialEq, Clone, Deserialize, Serialize, utoipa::ToSchema)]

rust/agama-network/src/nm/builder.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ use crate::{
2525
nm::{
2626
dbus::connection_from_dbus,
2727
model::NmDeviceType,
28-
proxies::{ConnectionProxy, DeviceProxy, IP4ConfigProxy, IP6ConfigProxy},
28+
proxies::{AccessPointProxy, ConnectionProxy, DeviceProxy, IP4ConfigProxy, IP6ConfigProxy},
29+
},
30+
types::{
31+
AccessPoint, ConnectionFlags, Device, DeviceState, DeviceType, IpConfig, IpRoute,
32+
MacAddress, SSID,
2933
},
30-
types::{ConnectionFlags, Device, DeviceState, DeviceType, IpConfig, IpRoute, MacAddress},
3134
};
3235
use cidr::IpInet;
3336
use std::{collections::HashMap, net::IpAddr, str::FromStr};
@@ -44,6 +47,38 @@ pub struct DeviceFromProxyBuilder<'a> {
4447
proxy: &'a DeviceProxy<'a>,
4548
}
4649

50+
/// Builder to create an [AccessPoint] from its corresponding NetworkManager D-Bus representation.
51+
pub struct AccessPointFromProxyBuilder<'a> {
52+
device_name: String,
53+
proxy: &'a AccessPointProxy<'a>,
54+
}
55+
56+
impl<'a> AccessPointFromProxyBuilder<'a> {
57+
pub fn new(device_name: String, proxy: &'a AccessPointProxy<'a>) -> Self {
58+
Self { device_name, proxy }
59+
}
60+
61+
/// Creates an [AccessPoint] starting on the [AccessPointProxy].
62+
pub async fn build(&self) -> Result<AccessPoint, NmError> {
63+
let ssid = SSID(self.proxy.ssid().await?);
64+
let hw_address = self.proxy.hw_address().await?;
65+
let strength = self.proxy.strength().await?;
66+
let flags = self.proxy.flags().await?;
67+
let rsn_flags = self.proxy.rsn_flags().await?;
68+
let wpa_flags = self.proxy.wpa_flags().await?;
69+
70+
Ok(AccessPoint {
71+
device: self.device_name.clone(),
72+
ssid,
73+
hw_address,
74+
strength,
75+
flags,
76+
rsn_flags,
77+
wpa_flags,
78+
})
79+
}
80+
}
81+
4782
impl<'a> ConnectionFromProxyBuilder<'a> {
4883
pub fn new(_connection: &zbus::Connection, proxy: &'a ConnectionProxy<'a>) -> Self {
4984
Self { proxy }

rust/agama-network/src/nm/streams/common.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub enum NmChange {
3535
ActiveConnectionAdded(OwnedObjectPath),
3636
ActiveConnectionUpdated(OwnedObjectPath),
3737
ActiveConnectionRemoved(OwnedObjectPath),
38+
AccessPointAdded(OwnedObjectPath, OwnedObjectPath),
39+
AccessPointRemoved(OwnedObjectPath, OwnedObjectPath),
3840
}
3941

4042
pub async fn build_added_and_removed_stream(
@@ -63,3 +65,17 @@ pub async fn build_properties_changed_stream(
6365
let stream = MessageStream::for_match_rule(rule, connection, Some(1)).await?;
6466
Ok(stream)
6567
}
68+
69+
/// Returns a stream of wireless signals to be used by DeviceChangedStream.
70+
///
71+
/// It listens for AccessPointAdded and AccessPointRemoved signals.
72+
pub async fn build_wireless_signals_stream(
73+
connection: &zbus::Connection,
74+
) -> Result<MessageStream, NmError> {
75+
let rule = MatchRule::builder()
76+
.msg_type(MessageType::Signal)
77+
.interface("org.freedesktop.NetworkManager.Device.Wireless")?
78+
.build();
79+
let stream = MessageStream::for_match_rule(rule, connection, Some(1)).await?;
80+
Ok(stream)
81+
}

rust/agama-network/src/nm/streams/devices.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ use zbus::{
3333
Message, MessageStream,
3434
};
3535

36-
use super::common::{build_added_and_removed_stream, build_properties_changed_stream, NmChange};
36+
use super::common::{
37+
build_added_and_removed_stream, build_properties_changed_stream, build_wireless_signals_stream,
38+
NmChange,
39+
};
3740
use crate::nm::error::NmError;
3841

3942
/// Stream of device-related events.
@@ -64,6 +67,10 @@ impl DeviceChangedStream {
6467
"properties",
6568
build_properties_changed_stream(&connection).await?,
6669
);
70+
inner.insert(
71+
"wireless",
72+
build_wireless_signals_stream(&connection).await?,
73+
);
6774
Ok(Self { connection, inner })
6875
}
6976

@@ -161,6 +168,25 @@ impl DeviceChangedStream {
161168
return Self::handle_changed(changed);
162169
}
163170

171+
let header = message.header();
172+
let interface = header.interface()?;
173+
if interface == "org.freedesktop.NetworkManager.Device.Wireless" {
174+
let path = OwnedObjectPath::from(header.path()?.to_owned());
175+
let member = header.member()?;
176+
177+
match member.as_str() {
178+
"AccessPointAdded" => {
179+
let ap_path: OwnedObjectPath = message.body().deserialize().ok()?;
180+
return Some(NmChange::AccessPointAdded(path, ap_path));
181+
}
182+
"AccessPointRemoved" => {
183+
let ap_path: OwnedObjectPath = message.body().deserialize().ok()?;
184+
return Some(NmChange::AccessPointRemoved(path, ap_path));
185+
}
186+
_ => {}
187+
}
188+
}
189+
164190
None
165191
}
166192
}

0 commit comments

Comments
 (0)