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}