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