1use std::rc::Rc;
2use std::fmt;
3use std::net::Ipv4Addr;
4
5use errors::*;
6use dbus_nm::DBusNetworkManager;
7
8use wifi::{AccessPoint, AccessPointCredentials};
9use device::{get_active_connection_devices, Device};
10use ssid::{AsSsidSlice, Ssid};
11
12#[derive(Clone)]
13pub struct Connection {
14 dbus_manager: Rc<DBusNetworkManager>,
15 path: String,
16 settings: ConnectionSettings,
17}
18
19impl Connection {
20 fn init(dbus_manager: &Rc<DBusNetworkManager>, path: &str) -> Result<Self> {
21 let settings = dbus_manager.get_connection_settings(path)?;
22
23 Ok(Connection {
24 dbus_manager: Rc::clone(dbus_manager),
25 path: path.to_string(),
26 settings: settings,
27 })
28 }
29
30 pub fn settings(&self) -> &ConnectionSettings {
31 &self.settings
32 }
33
34 pub fn get_state(&self) -> Result<ConnectionState> {
35 let active_path_option = get_connection_active_path(&self.dbus_manager, &self.path)?;
36
37 if let Some(active_path) = active_path_option {
38 let state = self.dbus_manager.get_connection_state(&active_path)?;
39
40 Ok(state)
41 } else {
42 Ok(ConnectionState::Deactivated)
43 }
44 }
45
46 pub fn delete(&self) -> Result<()> {
47 self.dbus_manager.delete_connection(&self.path)
48 }
49
50 pub fn activate(&self) -> Result<ConnectionState> {
61 let state = self.get_state()?;
62
63 match state {
64 ConnectionState::Activated => Ok(ConnectionState::Activated),
65 ConnectionState::Activating => wait(
66 self,
67 &ConnectionState::Activated,
68 self.dbus_manager.method_timeout(),
69 ),
70 ConnectionState::Unknown => bail!(ErrorKind::NetworkManager(
71 "Unable to get connection state".into()
72 )),
73 _ => {
74 self.dbus_manager.activate_connection(&self.path)?;
75
76 wait(
77 self,
78 &ConnectionState::Activated,
79 self.dbus_manager.method_timeout(),
80 )
81 },
82 }
83 }
84
85 pub fn deactivate(&self) -> Result<ConnectionState> {
96 let state = self.get_state()?;
97
98 match state {
99 ConnectionState::Deactivated => Ok(ConnectionState::Deactivated),
100 ConnectionState::Deactivating => wait(
101 self,
102 &ConnectionState::Deactivated,
103 self.dbus_manager.method_timeout(),
104 ),
105 ConnectionState::Unknown => bail!(ErrorKind::NetworkManager(
106 "Unable to get connection state".into()
107 )),
108 _ => {
109 let active_path_option =
110 get_connection_active_path(&self.dbus_manager, &self.path)?;
111
112 if let Some(active_path) = active_path_option {
113 self.dbus_manager.deactivate_connection(&active_path)?;
114
115 wait(
116 self,
117 &ConnectionState::Deactivated,
118 self.dbus_manager.method_timeout(),
119 )
120 } else {
121 Ok(ConnectionState::Deactivated)
122 }
123 },
124 }
125 }
126
127 pub fn get_devices(&self) -> Result<Vec<Device>> {
128 let active_path_option = get_connection_active_path(&self.dbus_manager, &self.path)?;
129
130 if let Some(active_path) = active_path_option {
131 get_active_connection_devices(&self.dbus_manager, &active_path)
132 } else {
133 Ok(vec![])
134 }
135 }
136}
137
138impl Ord for Connection {
139 fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
140 i32::from(self).cmp(&i32::from(other))
141 }
142}
143
144impl PartialOrd for Connection {
145 fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
146 Some(self.cmp(other))
147 }
148}
149
150impl PartialEq for Connection {
151 fn eq(&self, other: &Connection) -> bool {
152 i32::from(self) == i32::from(other)
153 }
154}
155
156impl Eq for Connection {}
157
158impl fmt::Debug for Connection {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 write!(
161 f,
162 "Connection {{ path: {:?}, settings: {:?} }}",
163 self.path, self.settings
164 )
165 }
166}
167
168impl<'a> From<&'a Connection> for i32 {
169 fn from(val: &Connection) -> i32 {
170 val.clone()
171 .path
172 .rsplit('/')
173 .nth(0)
174 .unwrap()
175 .parse::<i32>()
176 .unwrap()
177 }
178}
179
180#[derive(Default, Debug, Clone, Eq, PartialEq)]
181pub struct ConnectionSettings {
182 pub kind: String, pub id: String,
184 pub uuid: String,
185 pub ssid: Ssid,
186 pub mode: String,
187}
188
189#[derive(Debug, Clone, Eq, PartialEq)]
190pub enum ConnectionState {
191 Unknown = 0,
192 Activating = 1,
193 Activated = 2,
194 Deactivating = 3,
195 Deactivated = 4,
196}
197
198impl From<i64> for ConnectionState {
199 fn from(state: i64) -> Self {
200 match state {
201 0 => ConnectionState::Unknown,
202 1 => ConnectionState::Activating,
203 2 => ConnectionState::Activated,
204 3 => ConnectionState::Deactivating,
205 4 => ConnectionState::Deactivated,
206 _ => {
207 warn!("Undefined connection state: {}", state);
208 ConnectionState::Unknown
209 },
210 }
211 }
212}
213
214pub fn get_connections(dbus_manager: &Rc<DBusNetworkManager>) -> Result<Vec<Connection>> {
215 let paths = dbus_manager.list_connections()?;
216
217 let mut connections = Vec::with_capacity(paths.len());
218
219 for path in &paths {
220 connections.push(Connection::init(dbus_manager, path)?)
221 }
222
223 connections.sort();
224
225 Ok(connections)
226}
227
228pub fn get_active_connections(dbus_manager: &Rc<DBusNetworkManager>) -> Result<Vec<Connection>> {
229 let active_paths = dbus_manager.get_active_connections()?;
230
231 let mut connections = Vec::with_capacity(active_paths.len());
232
233 for active_path in active_paths {
234 if let Some(path) = dbus_manager.get_active_connection_path(&active_path) {
235 connections.push(Connection::init(dbus_manager, &path)?)
236 }
237 }
238
239 connections.sort();
240
241 Ok(connections)
242}
243
244pub fn connect_to_access_point(
245 dbus_manager: &Rc<DBusNetworkManager>,
246 device_path: &str,
247 access_point: &AccessPoint,
248 credentials: &AccessPointCredentials,
249) -> Result<(Connection, ConnectionState)> {
250 let (path, _) = dbus_manager.connect_to_access_point(device_path, access_point, credentials)?;
251
252 let connection = Connection::init(dbus_manager, &path)?;
253
254 let state = wait(
255 &connection,
256 &ConnectionState::Activated,
257 dbus_manager.method_timeout(),
258 )?;
259
260 Ok((connection, state))
261}
262
263pub fn create_hotspot<S>(
264 dbus_manager: &Rc<DBusNetworkManager>,
265 device_path: &str,
266 interface: &str,
267 ssid: &S,
268 password: Option<&str>,
269 address: Option<Ipv4Addr>,
270) -> Result<(Connection, ConnectionState)>
271where
272 S: AsSsidSlice + ?Sized,
273{
274 let (path, _) = dbus_manager.create_hotspot(device_path, interface, ssid, password, address)?;
275
276 let connection = Connection::init(dbus_manager, &path)?;
277
278 let state = wait(
279 &connection,
280 &ConnectionState::Activated,
281 dbus_manager.method_timeout(),
282 )?;
283
284 Ok((connection, state))
285}
286
287fn get_connection_active_path(
288 dbus_manager: &DBusNetworkManager,
289 connection_path: &str,
290) -> Result<Option<String>> {
291 let active_paths = dbus_manager.get_active_connections()?;
292
293 for active_path in active_paths {
294 if let Some(settings_path) = dbus_manager.get_active_connection_path(&active_path) {
295 if connection_path == settings_path {
296 return Ok(Some(active_path));
297 }
298 }
299 }
300
301 Ok(None)
302}
303
304fn wait(
305 connection: &Connection,
306 target_state: &ConnectionState,
307 timeout: u64,
308) -> Result<ConnectionState> {
309 if timeout == 0 {
310 return connection.get_state();
311 }
312
313 debug!("Waiting for connection state: {:?}", target_state);
314
315 let mut total_time = 0;
316
317 loop {
318 ::std::thread::sleep(::std::time::Duration::from_secs(1));
319
320 let state = connection.get_state()?;
321
322 total_time += 1;
323
324 if state == *target_state {
325 debug!(
326 "Connection target state reached: {:?} / {}s elapsed",
327 state, total_time
328 );
329
330 return Ok(state);
331 } else if total_time >= timeout {
332 debug!(
333 "Timeout reached in waiting for connection state ({:?}): {:?} / {}s elapsed",
334 target_state, state, total_time
335 );
336
337 return Ok(state);
338 }
339
340 debug!(
341 "Still waiting for connection state ({:?}): {:?} / {}s elapsed",
342 target_state, state, total_time
343 );
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::super::NetworkManager;
350 use super::*;
351
352 #[test]
353 fn test_connection_enable_disable() {
354 let manager = NetworkManager::new();
355
356 let connections = manager.get_connections().unwrap();
357
358 let wifi_env_var = "TEST_WIFI_SSID";
361 let connection = match ::std::env::var(wifi_env_var) {
362 Ok(ssid) => connections
363 .iter()
364 .filter(|c| c.settings().ssid.as_str().unwrap() == ssid)
365 .nth(0)
366 .unwrap()
367 .clone(),
368 Err(e) => panic!(
369 "couldn't retrieve environment variable {}: {}",
370 wifi_env_var, e
371 ),
372 };
373
374 let state = connection.get_state().unwrap();
375
376 if state == ConnectionState::Activated {
377 let state = connection.deactivate().unwrap();
378 assert_eq!(ConnectionState::Deactivated, state);
379
380 ::std::thread::sleep(::std::time::Duration::from_secs(5));
381
382 let state = connection.activate().unwrap();
383 assert_eq!(ConnectionState::Activated, state);
384
385 ::std::thread::sleep(::std::time::Duration::from_secs(5));
386 } else {
387 let state = connection.activate().unwrap();
388 assert_eq!(ConnectionState::Activated, state);
389
390 ::std::thread::sleep(::std::time::Duration::from_secs(5));
391
392 let state = connection.deactivate().unwrap();
393 assert_eq!(ConnectionState::Deactivated, state);
394
395 ::std::thread::sleep(::std::time::Duration::from_secs(5));
396 }
397 }
398}