Skip to main content

endpoint_sec/
client.rs

1//! Expose a wrapper around [`es_client_t`]: [`Client`]
2use std::ffi::OsStr;
3#[cfg(doc)]
4use std::ffi::OsString;
5use std::marker::PhantomData;
6use std::os::unix::prelude::OsStrExt;
7use std::panic::{catch_unwind, RefUnwindSafe};
8use std::ptr::NonNull;
9
10use endpoint_sec_sys::*;
11
12use crate::utils::convert_byte_slice_to_cow_cstr;
13use crate::{AuditToken, Message};
14#[cfg(feature = "macos_12_0_0")]
15use crate::{MutedPath, MutedProcess};
16
17/// Wrapper around the opaque type that stores the ES client state.
18///
19/// Note: this implementation ignores the return value of [`es_delete_client`] if you use [`Drop`],
20/// use [`Client::delete()`] instead if you want to check it.
21///
22/// This type is neither [`Send`] nor [`Sync`] because the client must be released on the same
23/// thread it was created.
24#[doc(alias = "es_client_t")]
25pub struct Client<'b> {
26    /// Pointer to the client, internal state is managed by Apple, we just keep it to pass it in
27    /// functions.
28    ///
29    /// Once constructed, it must never be `null`.
30    inner: NonNull<es_client_t>,
31
32    /// Ensure the client cannot outlive its message handling closure.
33    block_lifetime: PhantomData<&'b ()>,
34}
35
36#[cfg(feature = "static_assertions")]
37static_assertions::assert_not_impl_any!(Client: Send, Sync);
38
39/// Helper macro for functions that give us memory we need to free
40macro_rules! to_vec_and_free {
41    ($this:expr, $wrapped_function:ident) => {{
42        let mut count = 0;
43        let mut data = ::std::ptr::null_mut();
44
45        // Safety:
46        // - `self.as_mut()` is a valid client by construction
47        // - `count` is mutable
48        // - `data` is a mutable pointer
49        // - result is checked
50        // - `data` is freed below, since Apple says in its docs we have ownership of the memory
51        unsafe { $wrapped_function($this.as_mut(), &mut count, &mut data) }.ok()?;
52
53        let vec_data = if count > 0 && !data.is_null() {
54            // Safety:
55            // - `count > 0` so we won't create a zero-length slice
56            // - `data` is aligned and not null
57            unsafe { ::std::slice::from_raw_parts(data, count) }.to_vec()
58        } else {
59            ::std::vec::Vec::new()
60        };
61
62        if !data.is_null() {
63            // Safety: We have copied the data into a heap-allocated Vec,
64            // we can free it without losing data now and we have checked it
65            // is not null
66            unsafe { ::libc::free(data.cast()) };
67        }
68
69        Ok(vec_data)
70    }};
71    // Only calls the `$wrapped_function`, useful when the data structure has a custom function
72    // for deallocation
73    ($this:expr, $wrapped_function:ident with custom free) => {{
74        let mut data = ::std::ptr::null_mut();
75
76        // Safety:
77        // - `self.as_mut()` is a valid client by construction
78        // - `data` is a mutable pointer
79        // - result is checked
80        // - `data` is freed below the macro call, with the custom function provided by Apple
81        unsafe { $wrapped_function($this.as_mut(), &mut data) }.ok()?;
82
83        data
84    }};
85}
86
87/// Public bindings to the underlying [`es_client_t`] API.
88impl Client<'_> {
89    /// Creates a new [`Client`].
90    ///
91    /// Callers must respect the following requirement if they want this function to succeed:
92    ///
93    /// - Have the necessary entitlement for Endpoint Security
94    /// - Have the user's approval (TCC)
95    /// - Be running as root when launching the client (and while it is active)
96    /// - Not have previously reached the maximum number of connected clients
97    ///
98    /// See [`es_new_client()`].
99    #[doc(alias = "es_new_client")]
100    pub fn new<'b, F>(handler: F) -> Result<Client<'b>, NewClientError>
101    where
102        F: Fn(&mut Client<'_>, Message) + RefUnwindSafe + 'b,
103    {
104        let mut client = std::ptr::null_mut();
105
106        let block_handler = block2::RcBlock::new(
107            move |client: NonNull<es_client_t>, message: NonNull<es_message_t>| {
108                let _err = catch_unwind(|| {
109                    // Safety: Apple guarantees the received message is non-null and valid
110                    let message = unsafe { Message::from_raw(message) };
111                    let mut client = Client {
112                        inner: client,
113                        block_lifetime: PhantomData,
114                    };
115
116                    handler(&mut client, message);
117                    // Forget the client, else it would be double-dropped
118                    std::mem::forget(client);
119                });
120            },
121        );
122
123        // Safety:
124        // - `handler` is 'b so we can keep a ref through it in `block_handler`
125        //   without trouble.
126        // - `block_handler` is passed as an `RcBlock`, so `es_new_client`'s
127        //   taking ownership of it through `_Block_copy` will simply increment
128        //   its reference count instead of copying its stack bits, meaning our
129        //   dropping it below will not actually release it, but simply
130        //   decrement its RC and let it live until the ES runtime drops it
131        //   itself, finally reaching an RC of 0 and therefore actually
132        //   releasing it from memory, which should only happen when we
133        //   explicitly release the client in our `Drop` implementation.
134        // - The result is checked with `.ok()` below.
135        unsafe { es_new_client(&mut client, &block_handler) }.ok()?;
136
137        // Safety: Apple guarantees the received client is non-null and valid since we have checked
138        // the result of `es_new_client`.
139        Ok(Client {
140            inner: unsafe { NonNull::new_unchecked(client) },
141            block_lifetime: PhantomData,
142        })
143    }
144
145    /// Subscribe the client to `events`, without removing previous subscriptions.
146    ///
147    /// # Panics
148    ///
149    /// `events` can contain at most `u32::MAX` elements. This is a limitation of Apple's API.
150    ///
151    /// See [`es_subscribe`].
152    #[doc(alias = "es_subscribe")]
153    #[inline(always)]
154    pub fn subscribe(&mut self, events: &[es_event_type_t]) -> Result<(), ReturnError> {
155        assert!(events.len() < u32::MAX as usize);
156
157        // Safety:
158        // - `self.as_mut()` is a valid client by construction
159        // - `events` is a slice for which we have checked the length, `.as_ptr()` and `.len() as
160        //   u32` are both valid
161        // - the result is checked with `.ok()`
162        unsafe { es_subscribe(self.as_mut(), events.as_ptr(), events.len() as u32) }.ok()
163    }
164
165    /// Unsubscribe the client from `events`, without removing other subscriptions.
166    ///
167    /// # Panics
168    ///
169    /// `events` can contain at most `u32::MAX` elements. This is a limitation of Apple's API.
170    ///
171    /// See [`es_unsubscribe`].
172    #[doc(alias = "es_unsubscribe")]
173    #[inline(always)]
174    pub fn unsubscribe(&mut self, events: &[es_event_type_t]) -> Result<(), ReturnError> {
175        assert!(events.len() < u32::MAX as usize);
176
177        // Safety:
178        // - `self.as_mut()` is a valid client by construction
179        // - `events` is a slice for which we have checked the length, `.as_ptr()` and `.len() as
180        //   u32` are both valid
181        // - the result is checked with `.ok()`
182        unsafe { es_unsubscribe(self.as_mut(), events.as_ptr(), events.len() as u32) }.ok()
183    }
184
185    /// Unsubscribe the client from all its current subscriptions.
186    ///
187    /// See [`es_unsubscribe_all`].
188    #[doc(alias = "es_unsubscribe_all")]
189    #[inline(always)]
190    pub fn unsubscribe_all(&mut self) -> Result<(), ReturnError> {
191        // Safety:
192        // - `self.as_mut()` is a valid client by construction
193        // - the result is checked with `.ok()`
194        unsafe { es_unsubscribe_all(self.as_mut()) }.ok()
195    }
196
197    /// List current subscriptions of client.
198    ///
199    /// See [`es_subscriptions`].
200    #[doc(alias = "es_subscriptions")]
201    pub fn subscriptions(&mut self) -> Result<Vec<es_event_type_t>, ReturnError> {
202        to_vec_and_free!(self, es_subscriptions)
203    }
204
205    /// Respond to an auth event.
206    ///
207    /// See [`es_respond_auth_result`]
208    #[doc(alias = "es_respond_auth_result")]
209    #[inline(always)]
210    pub fn respond_auth_result(
211        &mut self,
212        msg: &Message,
213        resp: es_auth_result_t,
214        cache: bool,
215    ) -> Result<(), RespondError> {
216        // Safety:
217        // - `self.as_mut()` is a valid client by construction
218        // - `msg` is a ref to a valid message
219        // - the result is checked with `.ok()`
220        unsafe { es_respond_auth_result(self.as_mut(), msg.get_raw_ref(), resp, cache) }.ok()
221    }
222
223    /// Respong to an auth event that needs a flag response.
224    ///
225    /// See [`es_respond_flags_result`]
226    #[doc(alias = "es_respond_flags_result")]
227    #[inline(always)]
228    pub fn respond_flags_result(
229        &mut self,
230        msg: &Message,
231        authorized_flags: u32,
232        cache: bool,
233    ) -> Result<(), RespondError> {
234        // Safety:
235        // - `self.as_mut()` is a valid client by construction
236        // - `msg` is a ref to a valid message
237        // - the result is checked with `.ok()`
238        unsafe { es_respond_flags_result(self.as_mut(), msg.get_raw_ref(), authorized_flags, cache) }.ok()
239    }
240
241    /// Fully mute the given process.
242    ///
243    /// See [`es_mute_process`].
244    #[doc(alias = "es_mute_process")]
245    #[inline(always)]
246    pub fn mute_process(&mut self, process: &AuditToken) -> Result<(), ReturnError> {
247        // Safety:
248        // - `self.as_mut()` is a valid client by construction
249        // - `process` is valid (not-null, aligned) since its a reference cast to a pointer
250        unsafe { es_mute_process(self.as_mut(), process.get_raw_ref()) }.ok()
251    }
252
253    /// Mute only some events for the given process.
254    ///
255    /// See [`es_mute_process_events`].
256    ///
257    /// Only available on macOS 12.0+.
258    #[doc(alias = "es_mute_process_events")]
259    #[cfg(feature = "macos_12_0_0")]
260    pub fn mute_process_events(&mut self, process: &AuditToken, events: &[es_event_type_t]) -> Result<(), ReturnError> {
261        if crate::version::is_version_or_more(12, 0, 0) == false {
262            return Err(ReturnError::ApiUnavailable);
263        }
264
265        // Safety:
266        // - `self.as_mut()` is a valid client by construction
267        // - `process` is valid (non-null, aligned) since its a reference cast to a pointer
268        // - size and data of `events` are correctly passed in conjunction
269        unsafe {
270            es_mute_process_events(
271                self.as_mut(),
272                process.get_raw_ref(),
273                events.as_ptr(),
274                events.len(),
275            )
276        }
277        .ok()
278    }
279
280    /// Fully unmute the given process.
281    ///
282    /// See [`es_unmute_process`].
283    #[doc(alias = "es_unmute_process")]
284    #[inline(always)]
285    pub fn unmute_process(&mut self, process: &AuditToken) -> Result<(), ReturnError> {
286        // Safety:
287        // - `self.as_mut()` is a valid client by construction
288        // - `process` is valid (not-null, aligned) since its a reference cast to a pointer
289        unsafe { es_unmute_process(self.as_mut(), process.get_raw_ref()) }.ok()
290    }
291
292    /// Unmute only some events for the given process.
293    ///
294    /// See [`es_unmute_process_events`].
295    ///
296    /// Only available on macOS 12.0+.
297    #[doc(alias = "es_unmute_process_events")]
298    #[cfg(feature = "macos_12_0_0")]
299    pub fn unmute_process_events(
300        &mut self,
301        process: &AuditToken,
302        events: &[es_event_type_t],
303    ) -> Result<(), ReturnError> {
304        if crate::version::is_version_or_more(12, 0, 0) == false {
305            return Err(ReturnError::ApiUnavailable);
306        }
307
308        // Safety:
309        // - `self.as_mut()` is a valid client by construction
310        // - `process` is valid (non-null, aligned) since its a reference cast to a pointer
311        // - size and data of `events` are correctly passed in conjunction
312        unsafe {
313            es_unmute_process_events(
314                self.as_mut(),
315                process.get_raw_ref(),
316                events.as_ptr(),
317                events.len(),
318            )
319        }
320        .ok()
321    }
322
323    /// List muted processes.
324    ///
325    /// The returned [`AuditToken`] are in the same state as they were passed in to
326    /// [`Self::mute_process()`] and may not accuretly reflect the current state of the respective processes.
327    ///
328    /// See [`es_muted_processes`].
329    ///
330    /// Deprecated in macOS 12.0+
331    #[doc(alias = "es_muted_processes")]
332    pub fn muted_processes(&mut self) -> Result<Vec<AuditToken>, ReturnError> {
333        let raw_result = to_vec_and_free!(self, es_muted_processes);
334
335        match raw_result {
336            Ok(raw_result) => Ok(raw_result.into_iter().map(AuditToken::new).collect()),
337            Err(error) => Err(error),
338        }
339    }
340
341    /// List muted processes with additional informations
342    ///
343    /// See [`es_muted_processes_events`].
344    ///
345    /// Only available on macOS 12.0+.
346    #[doc(alias = "es_muted_processes_events")]
347    #[doc(alias = "es_release_muted_processes")]
348    #[cfg(feature = "macos_12_0_0")]
349    pub fn muted_processes_events(&mut self) -> Result<Vec<MutedProcess>, ReturnError> {
350        if crate::version::is_version_or_more(12, 0, 0) == false {
351            return Err(ReturnError::ApiUnavailable);
352        }
353        let data = to_vec_and_free!(
354            self,
355            es_muted_processes_events
356            with custom free
357        );
358
359        let muted_processes = if data.is_null() {
360            Vec::new()
361        } else {
362            // Safety: `data` is non-null we checked, let's hope Apple didn't ignore alignment
363            let sl = unsafe { (*data).processes() };
364
365            let mut muted_processes = Vec::with_capacity(sl.len());
366            for muted_process in sl {
367                muted_processes.push(MutedProcess {
368                    audit_token: AuditToken::new(muted_process.audit_token),
369                    // Safety: if we were not lied to by ES, this is okay: the pointer is null if
370                    // the size is zero, and valid if the size is not zero
371                    events: unsafe { muted_process.events() }.into(),
372                });
373            }
374
375            // Safety:
376            // - `data` is non null and hopefully valid for this
377            // - there is no return to check
378            unsafe { es_release_muted_processes(data) };
379
380            muted_processes
381        };
382
383        Ok(muted_processes)
384    }
385
386    /// Mute a path for all event types.
387    ///
388    #[cfg_attr(feature = "macos_12_0_0", doc = "See [`es_mute_path`].")]
389    #[cfg_attr(not(feature = "macos_12_0_0"), doc = "See `es_mute_path`.")]
390    ///
391    /// # Note
392    ///
393    /// The C function takes a `const char * _Nonnull path`, which means it expects a nul-
394    /// terminated string. Since the functions to gather such paths give [`OsString`]s (ex:
395    #[cfg_attr(
396        feature = "macos_12_0_0",
397        doc = "[`Self::muted_paths_events`]), this method will truncate the given `path` to the first `\0`"
398    )]
399    #[cfg_attr(
400        not(feature = "macos_12_0_0"),
401        doc = "`Self::muted_paths_events`), this method will truncate the given `path` to the first `\0`"
402    )]
403    /// if it has one or add it itself if it does not (in which case there will be an allocation).
404    ///
405    #[cfg_attr(
406        feature = "macos_12_0_0",
407        doc = "- If called on macOS 12.0+: uses [`es_mute_path()`]."
408    )]
409    #[cfg_attr(
410        not(feature = "macos_12_0_0"),
411        doc = "- If called on macOS 12.0+: uses `es_mute_path()`."
412    )]
413    /// - If called on macOS 10.15 or 11: uses [`es_mute_path_prefix()`] and [`es_mute_path_literal()`] accordingly.
414    #[doc(alias = "es_mute_path")]
415    #[doc(alias = "es_mute_path_prefix")]
416    #[doc(alias = "es_mute_path_literal")]
417    pub fn mute_path(&mut self, path: &OsStr, ty: es_mute_path_type_t) -> Result<(), ReturnError> {
418        let cow = convert_byte_slice_to_cow_cstr(path.as_bytes());
419
420        let res = versioned_call!(if cfg!(feature = "macos_12_0_0") && version >= (12, 0, 0) {
421            // Safety: `cow` has a nul at the end
422            unsafe { es_mute_path(self.as_mut(), cow.as_ptr(), ty) }
423        } else {
424            match ty {
425                // Safety: `cow` has a nul at the end
426                es_mute_path_type_t::ES_MUTE_PATH_TYPE_LITERAL => unsafe {
427                    es_mute_path_literal(self.as_mut(), cow.as_ptr())
428                },
429                // Safety: `cow` has a nul at the end
430                es_mute_path_type_t::ES_MUTE_PATH_TYPE_PREFIX => unsafe {
431                    es_mute_path_prefix(self.as_mut(), cow.as_ptr())
432                },
433                _ => return Err(ReturnError::ApiUnavailable),
434            }
435        });
436
437        res.ok()
438    }
439
440    /// Mute a path for a subset of event types.
441    ///
442    /// See [`es_mute_path_events`].
443    ///
444    /// # Note
445    ///
446    /// The C function takes a `const char * _Nonnull path`, which means it expects a nul-
447    /// terminated string. Since the functions to gather such paths give [`OsString`]s (ex:
448    /// [`Self::muted_paths_events`]), this method will truncate the given `path` to the first `\0`
449    /// if it has one or add it itself if it does not (in which case there will be an allocation).
450    ///
451    /// Only available on macOS 12.0+.
452    #[doc(alias = "es_mute_path_events")]
453    #[cfg(feature = "macos_12_0_0")]
454    pub fn mute_path_events(
455        &mut self,
456        path: &OsStr,
457        ty: es_mute_path_type_t,
458        events: &[es_event_type_t],
459    ) -> Result<(), ReturnError> {
460        if crate::version::is_version_or_more(12, 0, 0) == false {
461            return Err(ReturnError::ApiUnavailable);
462        }
463
464        let cow = convert_byte_slice_to_cow_cstr(path.as_bytes());
465
466        // Safety:
467        // - `cow` has a nul at the end
468        // - `.as_ptr()` and `.len()` are both called on `events` so they are in sync
469        unsafe {
470            es_mute_path_events(
471                self.as_mut(),
472                cow.as_ptr(),
473                ty,
474                events.as_ptr(),
475                events.len(),
476            )
477        }
478        .ok()
479    }
480
481    /// Unmute all paths for all events types.
482    ///
483    /// See [`es_unmute_all_paths()`].
484    #[doc(alias = "es_unmute_all_paths")]
485    #[inline(always)]
486    pub fn unmute_all_paths(&mut self) -> Result<(), ReturnError> {
487        // Safety: safe to call
488        unsafe { es_unmute_all_paths(self.as_mut()) }.ok()
489    }
490
491    /// Unmute all target paths.
492    ///
493    /// See [`es_unmute_all_target_paths()`].
494    ///
495    /// Only available on macOS 13.0+.
496    #[doc(alias = "es_unmute_all_target_paths")]
497    #[cfg(feature = "macos_13_0_0")]
498    #[inline(always)]
499    pub fn unmute_all_target_paths(&mut self) -> Result<(), ReturnError> {
500        if crate::version::is_version_or_more(13, 0, 0) == false {
501            return Err(ReturnError::ApiUnavailable);
502        }
503
504        // Safety: safe to call
505        unsafe { es_unmute_all_target_paths(self.as_mut()) }.ok()
506    }
507
508    /// Unmute a path for all event types.
509    ///
510    /// See [`es_unmute_path`].
511    ///
512    /// # Note
513    ///
514    /// The C function takes a `const char * _Nonnull path`, which means it expects a nul-terminated
515    /// string. Since the functions to gather such paths give [`OsString`]s (ex: [`Self::muted_paths_events`]),
516    /// this method will truncate the given `path` to the first `\0` if it has one or add it itself
517    /// if it does not (in which case there will be an allocation).
518    ///
519    /// Only available on macOS 12.0+.
520    #[doc(alias = "es_unmute_path")]
521    #[cfg(feature = "macos_12_0_0")]
522    #[inline(always)]
523    pub fn unmute_path(&mut self, path: &OsStr, ty: es_mute_path_type_t) -> Result<(), ReturnError> {
524        if crate::version::is_version_or_more(12, 0, 0) == false {
525            return Err(ReturnError::ApiUnavailable);
526        }
527
528        let cow = convert_byte_slice_to_cow_cstr(path.as_bytes());
529
530        // Safety: `cow` has a nul at the end
531        unsafe { es_unmute_path(self.as_mut(), cow.as_ptr(), ty) }.ok()
532    }
533
534    /// Unmute a path for a subset of event types.
535    ///
536    /// See [`es_unmute_path_events`].
537    ///
538    /// # Note
539    ///
540    /// The C function takes a `const char * _Nonnull path`, which means it expects a nul-terminated
541    /// string. Since the functions to gather such paths give [`OsString`]s (ex: [`Self::muted_paths_events`]),
542    /// this method will truncate the given `path` to the first `\0` if it has one or add it itself
543    /// if it does not (in which case there will be an allocation).
544    ///
545    /// Only available on macOS 12.0+.
546    #[doc(alias = "es_unmute_path_events")]
547    #[cfg(feature = "macos_12_0_0")]
548    #[inline(always)]
549    pub fn unmute_path_events(
550        &mut self,
551        path: &OsStr,
552        ty: es_mute_path_type_t,
553        events: &[es_event_type_t],
554    ) -> Result<(), ReturnError> {
555        if crate::version::is_version_or_more(12, 0, 0) == false {
556            return Err(ReturnError::ApiUnavailable);
557        }
558
559        let cow = convert_byte_slice_to_cow_cstr(path.as_bytes());
560
561        // Safety:
562        // - `cow` has a nul at the end
563        // - `.as_ptr()` and `.len()` are both called on `events` so they are in sync
564        unsafe {
565            es_unmute_path_events(
566                self.as_mut(),
567                cow.as_ptr(),
568                ty,
569                events.as_ptr(),
570                events.len(),
571            )
572        }
573        .ok()
574    }
575
576    /// List all muted paths.
577    ///
578    /// See [`es_muted_paths_events`].
579    ///
580    /// Only available on macOS 12.0+.
581    #[doc(alias = "es_muted_paths_events")]
582    #[doc(alias = "es_release_muted_paths")]
583    #[cfg(feature = "macos_12_0_0")]
584    pub fn muted_paths_events(&mut self) -> Result<Vec<MutedPath>, ReturnError> {
585        if crate::version::is_version_or_more(12, 0, 0) == false {
586            return Err(ReturnError::ApiUnavailable);
587        }
588
589        let data = to_vec_and_free!(
590            self,
591            es_muted_paths_events
592            with custom free
593        );
594
595        let transformed = if data.is_null() {
596            Vec::new()
597        } else {
598            // Safety: `data` is non-null we checked, let's hope Apple didn't ignore alignment
599            let sl = unsafe { (*data).paths() };
600
601            let mut v = Vec::with_capacity(sl.len());
602            for mp in sl {
603                v.push(MutedPath {
604                    ty: mp.type_,
605                    // Safety: if we were not lied to by ES, this is okay: the pointer is null if
606                    // the size is zero, and valid if the size is not zero
607                    events: unsafe { mp.events() }.into(),
608                    // Safety: if we were not lied to by ES, this is okay: the pointer is null if
609                    // the size is zero, and valid if the size is not zero
610                    path: unsafe { mp.path.as_os_str() }.into(),
611                });
612            }
613
614            // Safety:
615            // - `data` is non null and hopefully valid for this
616            // - there is no return to check
617            unsafe { es_release_muted_paths(data) };
618
619            v
620        };
621
622        Ok(transformed)
623    }
624
625    /// Invert the mute state of a given mute dimension.
626    ///
627    /// See [`es_invert_muting()`]
628    ///
629    /// Only available on macOS 13.0+.
630    #[doc(alias = "es_invert_muting")]
631    #[cfg(feature = "macos_13_0_0")]
632    #[inline(always)]
633    pub fn invert_muting(&mut self, mute_type: es_mute_inversion_type_t) -> Result<(), ReturnError> {
634        if crate::version::is_version_or_more(13, 0, 0) == false {
635            return Err(ReturnError::ApiUnavailable);
636        }
637
638        // Safety: safe to call
639        unsafe { es_invert_muting(self.as_mut(), mute_type) }.ok()
640    }
641
642    /// Query mute inversion state
643    ///
644    /// See [`es_muting_inverted()`]
645    ///
646    /// Only available on macOS 13.0+.
647    #[doc(alias = "es_muting_inverted")]
648    #[cfg(feature = "macos_13_0_0")]
649    #[inline(always)]
650    pub fn muting_inverted(&mut self, mute_type: es_mute_inversion_type_t) -> Result<MuteInvertedType, MuteTypeError> {
651        if crate::version::is_version_or_more(13, 0, 0) == false {
652            return Err(MuteTypeError::ApiUnavailable);
653        }
654
655        // Safety: safe to call
656        unsafe { es_muting_inverted(self.as_mut(), mute_type) }.ok()
657    }
658
659    /// Clear all cached results for **all** clients.
660    ///
661    /// See [`es_clear_cache()`].
662    #[doc(alias = "es_clear_cache")]
663    #[inline(always)]
664    pub fn clear_cache(&mut self) -> Result<(), ClearCacheError> {
665        // Safety: safe to call, our client is valid by construction
666        unsafe { es_clear_cache(self.as_mut()) }.ok()
667    }
668
669    /// Delete a client and returns the result, whereas [`Drop`] ignores it.
670    ///
671    /// See [`es_delete_client()`].
672    #[doc(alias = "es_delete_client")]
673    #[inline(always)]
674    pub fn delete(mut self) -> Result<(), ReturnError> {
675        // Safety:
676        // - We took ownership, this will only run once
677        // - By construction our client is valid
678        // - The result is checked
679        let res = unsafe { es_delete_client(self.as_mut()) }.ok();
680
681        // Avoid the double free since `self` would normally be dropped here
682        std::mem::forget(self);
683
684        res
685    }
686}
687
688/// Private helper methods
689impl Client<'_> {
690    /// Mutable access to the inner client
691    fn as_mut(&mut self) -> &mut es_client_t {
692        // Safety: `inner` is valid by construction
693        unsafe { self.inner.as_mut() }
694    }
695}
696
697impl Drop for Client<'_> {
698    /// Note: this implementation ignores the return value of [`es_delete_client`], use
699    /// [`Client::delete()`] if you want to check it
700    #[doc(alias = "es_delete_client")]
701    #[inline(always)]
702    fn drop(&mut self) {
703        // Safety: Our client is non-null and valid by construction, and we are in `Drop` which will
704        // only run once so no double free.
705        let _ = unsafe { es_delete_client(self.as_mut()) };
706    }
707}