Skip to main content

systemconfiguration/
network_connection.rs

1use std::{
2    ffi::c_void,
3    panic::AssertUnwindSafe,
4    sync::{Arc, Mutex},
5};
6
7use crate::{bridge, error::Result, ffi, PropertyList, ReachabilityFlags};
8
9/// Alias for `SCNetworkConnectionFlags` values.
10pub type NetworkConnectionFlags = ReachabilityFlags;
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13/// Wraps `SCNetworkConnectionStatus`.
14pub enum NetworkConnectionStatus {
15    /// Wraps the `Invalid` `SCNetworkConnectionStatus` value.
16    Invalid,
17    /// Wraps the `Disconnected` `SCNetworkConnectionStatus` value.
18    Disconnected,
19    /// Wraps the `Connecting` `SCNetworkConnectionStatus` value.
20    Connecting,
21    /// Wraps the `Connected` `SCNetworkConnectionStatus` value.
22    Connected,
23    /// Wraps the `Disconnecting` `SCNetworkConnectionStatus` value.
24    Disconnecting,
25    /// Wraps an unknown `SCNetworkConnectionStatus` value.
26    Unknown(i32),
27}
28
29impl NetworkConnectionStatus {
30    /// Wraps conversion from raw `SCNetworkConnectionStatus` values.
31    pub const fn from_raw(raw: i32) -> Self {
32        match raw {
33            -1 => Self::Invalid,
34            0 => Self::Disconnected,
35            1 => Self::Connecting,
36            2 => Self::Connected,
37            3 => Self::Disconnecting,
38            other => Self::Unknown(other),
39        }
40    }
41
42    /// Wraps conversion to raw `SCNetworkConnectionStatus` values.
43    pub const fn raw_value(self) -> i32 {
44        match self {
45            Self::Invalid => -1,
46            Self::Disconnected => 0,
47            Self::Connecting => 1,
48            Self::Connected => 2,
49            Self::Disconnecting => 3,
50            Self::Unknown(raw) => raw,
51        }
52    }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56/// Wraps `SCNetworkConnectionPPPStatus`.
57pub enum NetworkConnectionPppStatus {
58    /// Wraps the `Disconnected` `SCNetworkConnectionPPPStatus` value.
59    Disconnected,
60    /// Wraps the `Initializing` `SCNetworkConnectionPPPStatus` value.
61    Initializing,
62    /// Wraps the `ConnectingLink` `SCNetworkConnectionPPPStatus` value.
63    ConnectingLink,
64    /// Wraps the `DialOnTraffic` `SCNetworkConnectionPPPStatus` value.
65    DialOnTraffic,
66    /// Wraps the `NegotiatingLink` `SCNetworkConnectionPPPStatus` value.
67    NegotiatingLink,
68    /// Wraps the `Authenticating` `SCNetworkConnectionPPPStatus` value.
69    Authenticating,
70    /// Wraps the `WaitingForCallback` `SCNetworkConnectionPPPStatus` value.
71    WaitingForCallback,
72    /// Wraps the `NegotiatingNetwork` `SCNetworkConnectionPPPStatus` value.
73    NegotiatingNetwork,
74    /// Wraps the `Connected` `SCNetworkConnectionPPPStatus` value.
75    Connected,
76    /// Wraps the `Terminating` `SCNetworkConnectionPPPStatus` value.
77    Terminating,
78    /// Wraps the `DisconnectingLink` `SCNetworkConnectionPPPStatus` value.
79    DisconnectingLink,
80    /// Wraps the `HoldingLinkOff` `SCNetworkConnectionPPPStatus` value.
81    HoldingLinkOff,
82    /// Wraps the `Suspended` `SCNetworkConnectionPPPStatus` value.
83    Suspended,
84    /// Wraps the `WaitingForRedial` `SCNetworkConnectionPPPStatus` value.
85    WaitingForRedial,
86    /// Wraps an unknown `SCNetworkConnectionPPPStatus` value.
87    Unknown(i32),
88}
89
90impl NetworkConnectionPppStatus {
91    /// Wraps conversion from raw `SCNetworkConnectionPPPStatus` values.
92    pub const fn from_raw(raw: i32) -> Self {
93        match raw {
94            0 => Self::Disconnected,
95            1 => Self::Initializing,
96            2 => Self::ConnectingLink,
97            3 => Self::DialOnTraffic,
98            4 => Self::NegotiatingLink,
99            5 => Self::Authenticating,
100            6 => Self::WaitingForCallback,
101            7 => Self::NegotiatingNetwork,
102            8 => Self::Connected,
103            9 => Self::Terminating,
104            10 => Self::DisconnectingLink,
105            11 => Self::HoldingLinkOff,
106            12 => Self::Suspended,
107            13 => Self::WaitingForRedial,
108            other => Self::Unknown(other),
109        }
110    }
111
112    /// Wraps conversion to raw `SCNetworkConnectionPPPStatus` values.
113    pub const fn raw_value(self) -> i32 {
114        match self {
115            Self::Disconnected => 0,
116            Self::Initializing => 1,
117            Self::ConnectingLink => 2,
118            Self::DialOnTraffic => 3,
119            Self::NegotiatingLink => 4,
120            Self::Authenticating => 5,
121            Self::WaitingForCallback => 6,
122            Self::NegotiatingNetwork => 7,
123            Self::Connected => 8,
124            Self::Terminating => 9,
125            Self::DisconnectingLink => 10,
126            Self::HoldingLinkOff => 11,
127            Self::Suspended => 12,
128            Self::WaitingForRedial => 13,
129            Self::Unknown(raw) => raw,
130        }
131    }
132}
133
134#[derive(Clone, Debug)]
135/// Wraps `SCNetworkConnectionCopyUserPreferences` results.
136pub struct NetworkConnectionUserPreferences {
137    /// Wraps the service identifier returned by `SCNetworkConnectionCopyUserPreferences`.
138    pub service_id: String,
139    /// Wraps the user options returned by `SCNetworkConnectionCopyUserPreferences`.
140    pub user_options: Option<PropertyList>,
141}
142
143struct CallbackState {
144    callback: Box<dyn FnMut(NetworkConnectionStatus) + Send>,
145}
146
147unsafe extern "C" fn network_connection_callback(status: i32, info: *mut c_void) {
148    if info.is_null() {
149        return;
150    }
151
152    let mutex = &*info.cast::<Mutex<CallbackState>>();
153    if let Ok(mut state) = mutex.lock() {
154        // Catch panics: unwinding across the Swift/C FFI boundary is UB.
155        let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
156            (state.callback)(NetworkConnectionStatus::from_raw(status));
157        }));
158    }
159}
160
161#[derive(Clone)]
162/// Wraps `SCNetworkConnectionRef`.
163pub struct NetworkConnection {
164    raw: bridge::OwnedHandle,
165    _callback: Option<Arc<Mutex<CallbackState>>>,
166}
167
168impl std::fmt::Debug for NetworkConnection {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("NetworkConnection").finish_non_exhaustive()
171    }
172}
173
174impl NetworkConnection {
175    /// Wraps `SCNetworkConnectionGetTypeID`.
176    pub fn type_id() -> u64 {
177        unsafe { ffi::network_connection::sc_network_connection_get_type_id() }
178    }
179
180    /// Wraps `SCNetworkConnectionCreateWithServiceID`.
181    pub fn with_service_id(service_id: &str) -> Result<Self> {
182        Self::create(service_id, None)
183    }
184
185    /// Wraps `SCNetworkConnectionCreateWithServiceID` with an `SCNetworkConnectionCallBack`.
186    pub fn with_service_id_and_callback<F>(service_id: &str, callback: F) -> Result<Self>
187    where
188        F: FnMut(NetworkConnectionStatus) + Send + 'static,
189    {
190        let state = Arc::new(Mutex::new(CallbackState {
191            callback: Box::new(callback),
192        }));
193        Self::create(service_id, Some(state))
194    }
195
196    fn create(service_id: &str, callback: Option<Arc<Mutex<CallbackState>>>) -> Result<Self> {
197        let service_id =
198            bridge::cstring(service_id, "sc_network_connection_create_with_service_id")?;
199        let raw = unsafe {
200            ffi::network_connection::sc_network_connection_create_with_service_id(
201                service_id.as_ptr(),
202                callback
203                    .as_ref()
204                    .map(|_| network_connection_callback as unsafe extern "C" fn(i32, *mut c_void)),
205                callback.as_ref().map_or(std::ptr::null_mut(), |state| {
206                    Arc::as_ptr(state).cast_mut().cast::<c_void>()
207                }),
208            )
209        };
210        let raw =
211            bridge::owned_handle_or_last("sc_network_connection_create_with_service_id", raw)?;
212        Ok(Self {
213            raw,
214            _callback: callback,
215        })
216    }
217
218    /// Wraps `SCNetworkConnectionCopyUserPreferences`.
219    pub fn copy_user_preferences() -> Result<NetworkConnectionUserPreferences> {
220        let service_id = bridge::take_optional_string(unsafe {
221            ffi::network_connection::sc_network_connection_copy_user_preferences_service_id()
222        })
223        .ok_or_else(|| {
224            crate::SystemConfigurationError::last(
225                "sc_network_connection_copy_user_preferences_service_id",
226            )
227        })?;
228        let user_options = unsafe {
229            bridge::OwnedHandle::from_raw(
230                ffi::network_connection::sc_network_connection_copy_user_preferences_user_options(),
231            )
232        }
233        .map(PropertyList::from_owned_handle);
234        Ok(NetworkConnectionUserPreferences {
235            service_id,
236            user_options,
237        })
238    }
239
240    /// Wraps `SCNetworkConnectionCopyServiceID`.
241    pub fn service_id(&self) -> Result<Option<String>> {
242        Ok(bridge::take_optional_string(unsafe {
243            ffi::network_connection::sc_network_connection_copy_service_id(self.raw.as_ptr())
244        }))
245    }
246
247    /// Wraps `SCNetworkConnectionGetStatus`.
248    pub fn status(&self) -> NetworkConnectionStatus {
249        NetworkConnectionStatus::from_raw(unsafe {
250            ffi::network_connection::sc_network_connection_get_status(self.raw.as_ptr())
251        })
252    }
253
254    /// Wraps `SCNetworkConnectionCopyExtendedStatus`.
255    pub fn extended_status(&self) -> Option<PropertyList> {
256        unsafe {
257            bridge::OwnedHandle::from_raw(
258                ffi::network_connection::sc_network_connection_copy_extended_status(
259                    self.raw.as_ptr(),
260                ),
261            )
262        }
263        .map(PropertyList::from_owned_handle)
264    }
265
266    /// Wraps `SCNetworkConnectionCopyStatistics`.
267    pub fn statistics(&self) -> Option<PropertyList> {
268        unsafe {
269            bridge::OwnedHandle::from_raw(
270                ffi::network_connection::sc_network_connection_copy_statistics(self.raw.as_ptr()),
271            )
272        }
273        .map(PropertyList::from_owned_handle)
274    }
275
276    /// Wraps `SCNetworkConnectionCopyUserOptions`.
277    pub fn user_options(&self) -> Option<PropertyList> {
278        unsafe {
279            bridge::OwnedHandle::from_raw(
280                ffi::network_connection::sc_network_connection_copy_user_options(self.raw.as_ptr()),
281            )
282        }
283        .map(PropertyList::from_owned_handle)
284    }
285
286    /// Wraps `SCNetworkConnectionStart`.
287    pub fn start(&self, user_options: Option<&PropertyList>, linger: bool) -> Result<()> {
288        let ok = unsafe {
289            ffi::network_connection::sc_network_connection_start(
290                self.raw.as_ptr(),
291                user_options.map_or(std::ptr::null_mut(), PropertyList::as_ptr),
292                u8::from(linger),
293            )
294        };
295        bridge::bool_result("sc_network_connection_start", ok)
296    }
297
298    /// Wraps `SCNetworkConnectionStop`.
299    pub fn stop(&self, force_disconnect: bool) -> Result<()> {
300        let ok = unsafe {
301            ffi::network_connection::sc_network_connection_stop(
302                self.raw.as_ptr(),
303                u8::from(force_disconnect),
304            )
305        };
306        bridge::bool_result("sc_network_connection_stop", ok)
307    }
308
309    /// Wraps `SCNetworkConnectionScheduleWithRunLoopCurrent`.
310    pub fn schedule_with_run_loop_current(&self) -> Result<()> {
311        let ok = unsafe {
312            ffi::network_connection::sc_network_connection_schedule_with_run_loop_current(
313                self.raw.as_ptr(),
314            )
315        };
316        bridge::bool_result("sc_network_connection_schedule_with_run_loop_current", ok)
317    }
318
319    /// Wraps `SCNetworkConnectionUnscheduleFromRunLoopCurrent`.
320    pub fn unschedule_from_run_loop_current(&self) -> Result<()> {
321        let ok = unsafe {
322            ffi::network_connection::sc_network_connection_unschedule_from_run_loop_current(
323                self.raw.as_ptr(),
324            )
325        };
326        bridge::bool_result("sc_network_connection_unschedule_from_run_loop_current", ok)
327    }
328
329    /// Wraps `SCNetworkConnectionSetDispatchQueueGlobal`.
330    pub fn set_dispatch_queue_global(&self) -> Result<()> {
331        let ok = unsafe {
332            ffi::network_connection::sc_network_connection_set_dispatch_queue_global(
333                self.raw.as_ptr(),
334            )
335        };
336        bridge::bool_result("sc_network_connection_set_dispatch_queue_global", ok)
337    }
338
339    /// Wraps `SCNetworkConnectionClearDispatchQueue`.
340    pub fn clear_dispatch_queue(&self) -> Result<()> {
341        let ok = unsafe {
342            ffi::network_connection::sc_network_connection_clear_dispatch_queue(self.raw.as_ptr())
343        };
344        bridge::bool_result("sc_network_connection_clear_dispatch_queue", ok)
345    }
346}