Skip to main content

systemconfiguration/
dynamic_store.rs

1use std::{
2    ffi::c_void,
3    panic::AssertUnwindSafe,
4    sync::{Arc, Mutex},
5};
6
7use crate::{
8    bridge::{self, CStringArray},
9    error::Result,
10    ffi, PropertyList, SystemConfigurationError,
11};
12
13struct CallbackState {
14    callback: Box<dyn FnMut(Vec<String>) + Send>,
15}
16
17unsafe extern "C" fn dynamic_store_callback(
18    changed_keys_raw: bridge::RawHandle,
19    info: *mut c_void,
20) {
21    if info.is_null() {
22        return;
23    }
24
25    // SAFETY: `info` is `Arc::as_ptr(state).cast_mut().cast::<c_void>()` kept
26    // alive by `DynamicStore::_callback` for the entire lifetime of the store.
27    // This callback is only invoked while the store is alive.
28    let mutex = unsafe { &*info.cast::<Mutex<CallbackState>>() };
29    if let Ok(mut state) = mutex.lock() {
30        let keys = bridge::take_string_array(changed_keys_raw);
31        // Catch panics: unwinding across the Swift/C FFI boundary is UB.
32        let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
33            (state.callback)(keys);
34        }));
35    }
36}
37
38#[derive(Clone)]
39pub struct DynamicStore {
40    raw: bridge::OwnedHandle,
41    _callback: Option<Arc<Mutex<CallbackState>>>,
42}
43
44impl std::fmt::Debug for DynamicStore {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("DynamicStore").finish_non_exhaustive()
47    }
48}
49
50#[derive(Clone)]
51pub struct DynamicStoreRunLoopSource {
52    raw: bridge::OwnedHandle,
53}
54
55impl std::fmt::Debug for DynamicStoreRunLoopSource {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("DynamicStoreRunLoopSource")
58            .finish_non_exhaustive()
59    }
60}
61
62impl DynamicStore {
63    pub fn type_id() -> u64 {
64        unsafe { ffi::dynamic_store::sc_dynamic_store_get_type_id() }
65    }
66
67    pub fn new(name: &str) -> Result<Self> {
68        Self::create(name, None, false, None)
69    }
70
71    pub fn new_with_session_keys(name: &str) -> Result<Self> {
72        Self::create(name, None, true, None)
73    }
74
75    /// `options` must encode a dictionary accepted by `SCDynamicStoreCreateWithOptions`.
76    /// Use `new_with_session_keys` for the common session-keys configuration.
77    pub fn new_with_options(name: &str, options: &PropertyList) -> Result<Self> {
78        Self::create(name, Some(options), false, None)
79    }
80
81    pub fn new_with_callback<F>(name: &str, callback: F) -> Result<Self>
82    where
83        F: FnMut(Vec<String>) + Send + 'static,
84    {
85        let callback = Arc::new(Mutex::new(CallbackState {
86            callback: Box::new(callback),
87        }));
88        Self::create(name, None, false, Some(callback))
89    }
90
91    /// `options` must encode a dictionary accepted by `SCDynamicStoreCreateWithOptions`.
92    /// Use `new_with_session_keys_and_callback` for the common session-keys configuration.
93    pub fn new_with_options_and_callback<F>(
94        name: &str,
95        options: &PropertyList,
96        callback: F,
97    ) -> Result<Self>
98    where
99        F: FnMut(Vec<String>) + Send + 'static,
100    {
101        let callback = Arc::new(Mutex::new(CallbackState {
102            callback: Box::new(callback),
103        }));
104        Self::create(name, Some(options), false, Some(callback))
105    }
106
107    pub fn new_with_session_keys_and_callback<F>(name: &str, callback: F) -> Result<Self>
108    where
109        F: FnMut(Vec<String>) + Send + 'static,
110    {
111        let callback = Arc::new(Mutex::new(CallbackState {
112            callback: Box::new(callback),
113        }));
114        Self::create(name, None, true, Some(callback))
115    }
116
117    fn create(
118        name: &str,
119        options: Option<&PropertyList>,
120        use_session_keys: bool,
121        callback: Option<Arc<Mutex<CallbackState>>>,
122    ) -> Result<Self> {
123        let function = match (options.is_some(), callback.is_some()) {
124            (false, false) => "sc_dynamic_store_create",
125            (false, true) => "sc_dynamic_store_create_with_callback",
126            (true, false) => "sc_dynamic_store_create_with_options",
127            (true, true) => "sc_dynamic_store_create_with_options_and_callback",
128        };
129        let name = bridge::cstring(name, function)?;
130        let raw = unsafe {
131            match (options, callback.as_ref()) {
132                (Some(options), None) => ffi::dynamic_store::sc_dynamic_store_create_with_options(
133                    name.as_ptr(),
134                    options.as_ptr(),
135                ),
136                (Some(options), Some(state)) => {
137                    ffi::dynamic_store::sc_dynamic_store_create_with_options_and_callback(
138                        name.as_ptr(),
139                        options.as_ptr(),
140                        Some(dynamic_store_callback),
141                        Arc::as_ptr(state).cast_mut().cast::<c_void>(),
142                    )
143                }
144                (None, None) => ffi::dynamic_store::sc_dynamic_store_create(
145                    name.as_ptr(),
146                    u8::from(use_session_keys),
147                ),
148                (None, Some(state)) => ffi::dynamic_store::sc_dynamic_store_create_with_callback(
149                    name.as_ptr(),
150                    u8::from(use_session_keys),
151                    Some(dynamic_store_callback),
152                    Arc::as_ptr(state).cast_mut().cast::<c_void>(),
153                ),
154            }
155        };
156        let raw = bridge::owned_handle_or_last(function, raw)?;
157        Ok(Self {
158            raw,
159            _callback: callback,
160        })
161    }
162
163    pub fn copy_value(&self, key: &str) -> Result<Option<PropertyList>> {
164        let key = bridge::cstring(key, "sc_dynamic_store_copy_value")?;
165        let raw = unsafe {
166            ffi::dynamic_store::sc_dynamic_store_copy_value(self.raw.as_ptr(), key.as_ptr())
167        };
168        Ok(unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle))
169    }
170
171    pub fn copy_multiple<K, P>(&self, keys: &[K], patterns: &[P]) -> Result<Option<PropertyList>>
172    where
173        K: AsRef<str>,
174        P: AsRef<str>,
175    {
176        let keys = CStringArray::new(keys, "sc_dynamic_store_copy_multiple")?;
177        let patterns = CStringArray::new(patterns, "sc_dynamic_store_copy_multiple")?;
178        let raw = unsafe {
179            ffi::dynamic_store::sc_dynamic_store_copy_multiple(
180                self.raw.as_ptr(),
181                keys.as_ptr(),
182                keys.count(),
183                patterns.as_ptr(),
184                patterns.count(),
185            )
186        };
187        Ok(unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle))
188    }
189
190    pub fn add_value(&self, key: &str, value: &PropertyList) -> Result<()> {
191        let key = bridge::cstring(key, "sc_dynamic_store_add_value")?;
192        let ok = unsafe {
193            ffi::dynamic_store::sc_dynamic_store_add_value(
194                self.raw.as_ptr(),
195                key.as_ptr(),
196                value.as_ptr(),
197            )
198        };
199        bridge::bool_result("sc_dynamic_store_add_value", ok)
200    }
201
202    pub fn add_temporary_value(&self, key: &str, value: &PropertyList) -> Result<()> {
203        let key = bridge::cstring(key, "sc_dynamic_store_add_temporary_value")?;
204        let ok = unsafe {
205            ffi::dynamic_store::sc_dynamic_store_add_temporary_value(
206                self.raw.as_ptr(),
207                key.as_ptr(),
208                value.as_ptr(),
209            )
210        };
211        bridge::bool_result("sc_dynamic_store_add_temporary_value", ok)
212    }
213
214    pub fn set_value(&self, key: &str, value: &PropertyList) -> Result<()> {
215        let key = bridge::cstring(key, "sc_dynamic_store_set_value")?;
216        let ok = unsafe {
217            ffi::dynamic_store::sc_dynamic_store_set_value(
218                self.raw.as_ptr(),
219                key.as_ptr(),
220                value.as_ptr(),
221            )
222        };
223        bridge::bool_result("sc_dynamic_store_set_value", ok)
224    }
225
226    pub fn set_multiple<R, N>(
227        &self,
228        keys_to_set: Option<&PropertyList>,
229        keys_to_remove: &[R],
230        keys_to_notify: &[N],
231    ) -> Result<()>
232    where
233        R: AsRef<str>,
234        N: AsRef<str>,
235    {
236        let keys_to_remove = CStringArray::new(keys_to_remove, "sc_dynamic_store_set_multiple")?;
237        let keys_to_notify = CStringArray::new(keys_to_notify, "sc_dynamic_store_set_multiple")?;
238        let ok = unsafe {
239            ffi::dynamic_store::sc_dynamic_store_set_multiple(
240                self.raw.as_ptr(),
241                keys_to_set.map_or(std::ptr::null_mut(), PropertyList::as_ptr),
242                keys_to_remove.as_ptr(),
243                keys_to_remove.count(),
244                keys_to_notify.as_ptr(),
245                keys_to_notify.count(),
246            )
247        };
248        bridge::bool_result("sc_dynamic_store_set_multiple", ok)
249    }
250
251    pub fn remove_value(&self, key: &str) -> Result<()> {
252        let key = bridge::cstring(key, "sc_dynamic_store_remove_value")?;
253        let ok = unsafe {
254            ffi::dynamic_store::sc_dynamic_store_remove_value(self.raw.as_ptr(), key.as_ptr())
255        };
256        bridge::bool_result("sc_dynamic_store_remove_value", ok)
257    }
258
259    pub fn notify_value(&self, key: &str) -> Result<()> {
260        let key = bridge::cstring(key, "sc_dynamic_store_notify_value")?;
261        let ok = unsafe {
262            ffi::dynamic_store::sc_dynamic_store_notify_value(self.raw.as_ptr(), key.as_ptr())
263        };
264        bridge::bool_result("sc_dynamic_store_notify_value", ok)
265    }
266
267    pub fn copy_key_list(&self, pattern: &str) -> Result<Vec<String>> {
268        let pattern = bridge::cstring(pattern, "sc_dynamic_store_copy_key_list")?;
269        let raw = unsafe {
270            ffi::dynamic_store::sc_dynamic_store_copy_key_list(self.raw.as_ptr(), pattern.as_ptr())
271        };
272        Ok(bridge::take_string_array(raw))
273    }
274
275    pub fn set_notification_keys<K, P>(&self, keys: &[K], patterns: &[P]) -> Result<()>
276    where
277        K: AsRef<str>,
278        P: AsRef<str>,
279    {
280        let keys = CStringArray::new(keys, "sc_dynamic_store_set_notification_keys")?;
281        let patterns = CStringArray::new(patterns, "sc_dynamic_store_set_notification_keys")?;
282        let ok = unsafe {
283            ffi::dynamic_store::sc_dynamic_store_set_notification_keys(
284                self.raw.as_ptr(),
285                keys.as_ptr(),
286                keys.count(),
287                patterns.as_ptr(),
288                patterns.count(),
289            )
290        };
291        bridge::bool_result("sc_dynamic_store_set_notification_keys", ok)
292    }
293
294    pub fn create_run_loop_source(&self, order: isize) -> Result<DynamicStoreRunLoopSource> {
295        let raw = unsafe {
296            ffi::dynamic_store::sc_dynamic_store_create_run_loop_source(self.raw.as_ptr(), order)
297        };
298        let raw = bridge::owned_handle_or_last("sc_dynamic_store_create_run_loop_source", raw)?;
299        Ok(DynamicStoreRunLoopSource { raw })
300    }
301
302    pub fn set_dispatch_queue_global(&self) -> Result<()> {
303        let ok = unsafe {
304            ffi::dynamic_store::sc_dynamic_store_set_dispatch_queue_global(self.raw.as_ptr())
305        };
306        bridge::bool_result("sc_dynamic_store_set_dispatch_queue_global", ok)
307    }
308
309    pub fn clear_dispatch_queue(&self) -> Result<()> {
310        let ok =
311            unsafe { ffi::dynamic_store::sc_dynamic_store_clear_dispatch_queue(self.raw.as_ptr()) };
312        bridge::bool_result("sc_dynamic_store_clear_dispatch_queue", ok)
313    }
314
315    pub fn copy_notified_keys(&self) -> Vec<String> {
316        let raw =
317            unsafe { ffi::dynamic_store::sc_dynamic_store_copy_notified_keys(self.raw.as_ptr()) };
318        bridge::take_string_array(raw)
319    }
320
321    pub fn computer_name(&self) -> Option<String> {
322        bridge::take_optional_string(unsafe {
323            ffi::dynamic_store::sc_dynamic_store_copy_computer_name(self.raw.as_ptr())
324        })
325    }
326
327    pub fn local_host_name(&self) -> Option<String> {
328        bridge::take_optional_string(unsafe {
329            ffi::dynamic_store::sc_dynamic_store_copy_local_host_name(self.raw.as_ptr())
330        })
331    }
332
333    pub fn location(&self) -> Option<String> {
334        bridge::take_optional_string(unsafe {
335            ffi::dynamic_store::sc_dynamic_store_copy_location(self.raw.as_ptr())
336        })
337    }
338
339    pub fn proxies(&self) -> Option<PropertyList> {
340        let raw = unsafe { ffi::dynamic_store::sc_dynamic_store_copy_proxies(self.raw.as_ptr()) };
341        unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle)
342    }
343
344    pub fn dhcp_info(&self, service_id: Option<&str>) -> Result<Option<PropertyList>> {
345        let service_id = bridge::optional_cstring(service_id, "sc_dynamic_store_copy_dhcp_info")?;
346        let raw = unsafe {
347            ffi::dynamic_store::sc_dynamic_store_copy_dhcp_info(
348                self.raw.as_ptr(),
349                service_id
350                    .as_ref()
351                    .map_or(std::ptr::null(), |value| value.as_ptr()),
352            )
353        };
354        Ok(unsafe { bridge::OwnedHandle::from_raw(raw) }.map(PropertyList::from_owned_handle))
355    }
356
357    pub fn dhcp_option_data(info: &PropertyList, code: u8) -> Option<PropertyList> {
358        unsafe {
359            bridge::OwnedHandle::from_raw(ffi::dynamic_store::sc_dhcp_info_copy_option_data(
360                info.as_ptr(),
361                code,
362            ))
363        }
364        .map(PropertyList::from_owned_handle)
365    }
366
367    pub fn dhcp_lease_start_time(info: &PropertyList) -> Option<PropertyList> {
368        unsafe {
369            bridge::OwnedHandle::from_raw(ffi::dynamic_store::sc_dhcp_info_copy_lease_start_time(
370                info.as_ptr(),
371            ))
372        }
373        .map(PropertyList::from_owned_handle)
374    }
375
376    pub fn dhcp_lease_expiration_time(info: &PropertyList) -> Option<PropertyList> {
377        unsafe {
378            bridge::OwnedHandle::from_raw(
379                ffi::dynamic_store::sc_dhcp_info_copy_lease_expiration_time(info.as_ptr()),
380            )
381        }
382        .map(PropertyList::from_owned_handle)
383    }
384
385    pub fn key_create<A: AsRef<str>>(format: &str, arguments: &[A]) -> Result<String> {
386        let format = bridge::cstring(format, "sc_dynamic_store_key_create")?;
387        let arguments = CStringArray::new(arguments, "sc_dynamic_store_key_create")?;
388        bridge::take_optional_string(unsafe {
389            ffi::dynamic_store::sc_dynamic_store_key_create(
390                format.as_ptr(),
391                arguments.as_ptr(),
392                arguments.count(),
393            )
394        })
395        .ok_or_else(|| {
396            SystemConfigurationError::null(
397                "sc_dynamic_store_key_create",
398                "bridge returned null dynamic-store formatted key",
399            )
400        })
401    }
402
403    pub fn network_global_entity_key(domain: &str, entity: &str) -> Result<String> {
404        let domain = bridge::cstring(domain, "sc_dynamic_store_key_create_network_global_entity")?;
405        let entity = bridge::cstring(entity, "sc_dynamic_store_key_create_network_global_entity")?;
406        bridge::take_optional_string(unsafe {
407            ffi::dynamic_store::sc_dynamic_store_key_create_network_global_entity(
408                domain.as_ptr(),
409                entity.as_ptr(),
410            )
411        })
412        .ok_or_else(|| {
413            SystemConfigurationError::null(
414                "sc_dynamic_store_key_create_network_global_entity",
415                "bridge returned null dynamic-store global entity key",
416            )
417        })
418    }
419
420    pub fn network_interface_key(domain: &str) -> Result<String> {
421        let domain = bridge::cstring(domain, "sc_dynamic_store_key_create_network_interface")?;
422        bridge::take_optional_string(unsafe {
423            ffi::dynamic_store::sc_dynamic_store_key_create_network_interface(domain.as_ptr())
424        })
425        .ok_or_else(|| {
426            SystemConfigurationError::null(
427                "sc_dynamic_store_key_create_network_interface",
428                "bridge returned null dynamic-store interface key",
429            )
430        })
431    }
432
433    pub fn network_interface_entity_key(
434        domain: &str,
435        interface_name: &str,
436        entity: Option<&str>,
437    ) -> Result<String> {
438        let domain = bridge::cstring(
439            domain,
440            "sc_dynamic_store_key_create_network_interface_entity",
441        )?;
442        let interface_name = bridge::cstring(
443            interface_name,
444            "sc_dynamic_store_key_create_network_interface_entity",
445        )?;
446        let entity = bridge::optional_cstring(
447            entity,
448            "sc_dynamic_store_key_create_network_interface_entity",
449        )?;
450        bridge::take_optional_string(unsafe {
451            ffi::dynamic_store::sc_dynamic_store_key_create_network_interface_entity(
452                domain.as_ptr(),
453                interface_name.as_ptr(),
454                entity
455                    .as_ref()
456                    .map_or(std::ptr::null(), |value| value.as_ptr()),
457            )
458        })
459        .ok_or_else(|| {
460            SystemConfigurationError::null(
461                "sc_dynamic_store_key_create_network_interface_entity",
462                "bridge returned null dynamic-store interface-entity key",
463            )
464        })
465    }
466
467    pub fn network_service_entity_key(
468        domain: &str,
469        service_id: &str,
470        entity: Option<&str>,
471    ) -> Result<String> {
472        let domain = bridge::cstring(domain, "sc_dynamic_store_key_create_network_service_entity")?;
473        let service_id = bridge::cstring(
474            service_id,
475            "sc_dynamic_store_key_create_network_service_entity",
476        )?;
477        let entity =
478            bridge::optional_cstring(entity, "sc_dynamic_store_key_create_network_service_entity")?;
479        bridge::take_optional_string(unsafe {
480            ffi::dynamic_store::sc_dynamic_store_key_create_network_service_entity(
481                domain.as_ptr(),
482                service_id.as_ptr(),
483                entity
484                    .as_ref()
485                    .map_or(std::ptr::null(), |value| value.as_ptr()),
486            )
487        })
488        .ok_or_else(|| {
489            SystemConfigurationError::null(
490                "sc_dynamic_store_key_create_network_service_entity",
491                "bridge returned null dynamic-store service-entity key",
492            )
493        })
494    }
495
496    pub fn computer_name_key() -> Result<String> {
497        bridge::take_optional_string(unsafe {
498            ffi::dynamic_store::sc_dynamic_store_key_create_computer_name()
499        })
500        .ok_or_else(|| {
501            SystemConfigurationError::null(
502                "sc_dynamic_store_key_create_computer_name",
503                "bridge returned null computer-name notification key",
504            )
505        })
506    }
507
508    pub fn console_user_key() -> Result<String> {
509        bridge::take_optional_string(unsafe {
510            ffi::dynamic_store::sc_dynamic_store_key_create_console_user()
511        })
512        .ok_or_else(|| {
513            SystemConfigurationError::null(
514                "sc_dynamic_store_key_create_console_user",
515                "bridge returned null console-user notification key",
516            )
517        })
518    }
519
520    pub fn host_names_key() -> Result<String> {
521        bridge::take_optional_string(unsafe {
522            ffi::dynamic_store::sc_dynamic_store_key_create_host_names()
523        })
524        .ok_or_else(|| {
525            SystemConfigurationError::null(
526                "sc_dynamic_store_key_create_host_names",
527                "bridge returned null host-names notification key",
528            )
529        })
530    }
531
532    pub fn location_key() -> Result<String> {
533        bridge::take_optional_string(unsafe {
534            ffi::dynamic_store::sc_dynamic_store_key_create_location()
535        })
536        .ok_or_else(|| {
537            SystemConfigurationError::null(
538                "sc_dynamic_store_key_create_location",
539                "bridge returned null location notification key",
540            )
541        })
542    }
543
544    pub fn proxies_key() -> Result<String> {
545        bridge::take_optional_string(unsafe {
546            ffi::dynamic_store::sc_dynamic_store_key_create_proxies()
547        })
548        .ok_or_else(|| {
549            SystemConfigurationError::null(
550                "sc_dynamic_store_key_create_proxies",
551                "bridge returned null proxies notification key",
552            )
553        })
554    }
555}
556
557impl DynamicStoreRunLoopSource {
558    pub fn schedule_current_default_mode(&self) -> Result<()> {
559        let ok = unsafe {
560            ffi::dynamic_store::sc_run_loop_source_schedule_current_default_mode(self.raw.as_ptr())
561        };
562        bridge::bool_result("sc_run_loop_source_schedule_current_default_mode", ok)
563    }
564
565    pub fn unschedule_current_default_mode(&self) -> Result<()> {
566        let ok = unsafe {
567            ffi::dynamic_store::sc_run_loop_source_unschedule_current_default_mode(
568                self.raw.as_ptr(),
569            )
570        };
571        bridge::bool_result("sc_run_loop_source_unschedule_current_default_mode", ok)
572    }
573}