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}