1use std::ffi::CStr;
11
12use crossbeam::channel;
13
14use block::ConcreteBlock;
15
16mod sys {
17 #![allow(non_upper_case_globals)]
18 #![allow(non_camel_case_types)]
19 #![allow(non_snake_case)]
20 #![allow(unused)]
21 #![allow(clippy::all)]
22 include!("./sys.rs");
23}
24
25#[allow(unused)]
26mod bsm;
27
28macro_rules! es_string_to_string {
29 ($ex: expr) => {
30 CStr::from_ptr($ex).to_string_lossy().to_string()
31 };
32}
33
34macro_rules! es_string_to_opt_string {
35 ($ex: expr) => {
36 if $ex.is_null() {
37 None
38 } else {
39 Some(CStr::from_ptr($ex).to_string_lossy().to_string())
40 }
41 };
42}
43
44#[derive(Debug)]
46pub enum EsClientCreateError {
47 InvalidArgument = 1,
49 Internal,
51 NotEntitled,
53 NotPermited,
55 NotPrivileged,
57 TooManyClients,
59}
60
61impl EsClientCreateError {
62 fn from_u32(code: u32) -> Option<EsClientCreateError> {
63 match code {
64 1 => Some(EsClientCreateError::InvalidArgument),
65 2 => Some(EsClientCreateError::Internal),
66 3 => Some(EsClientCreateError::NotEntitled),
67 4 => Some(EsClientCreateError::NotPermited),
68 5 => Some(EsClientCreateError::NotPrivileged),
69 6 => Some(EsClientCreateError::TooManyClients),
70 _ => None,
71 }
72 }
73}
74
75impl std::fmt::Display for EsClientCreateError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 let err_str = match self {
78 EsClientCreateError::InvalidArgument => "Error: Invalid Arguments were provided.",
79 EsClientCreateError::Internal => "Error: Communication with ES subsystem failed.",
80 EsClientCreateError::NotEntitled => "Error: Caller is not properly entitled to connect.",
81 EsClientCreateError::NotPermited => "Error: Caller lacks Transparency, Consent, and Control (TCC) approval from the user.",
82 EsClientCreateError::NotPrivileged => "Error: Must be run as root",
83 EsClientCreateError::TooManyClients => "Error: Too many connected clients",
84 };
85
86 f.write_str(err_str)
87 }
88}
89
90impl std::error::Error for EsClientCreateError {}
91
92#[derive(Copy, Clone, Debug, Eq, PartialEq)]
97#[repr(u32)]
98pub enum EsEventType {
99 AuthExec,
100 AuthOpen,
101 AuthKExtLoad,
102 AuthMMap,
103 AuthMProtect,
104 AuthMount,
105 AuthRename,
106 AuthSignal,
107 AuthUnlink,
108 NotifyExec,
109 NotifyOpen,
110 NotifyFork,
111 NotifyClose,
112 NotifyCreate,
113 NotifyExchangeData,
114 NotifyExit,
115 NotifyGetTask,
116 NotifyKExtLoad,
117 NotifyKExtUnload,
118 NotifyLink,
119 NotifyMMap,
120 NotifyMProtect,
121 NotifyMount,
122 NotifyUnmount,
123 NotifyIOKitOpen,
124 NotifyRename,
125 NOtifySetAttrList,
126 NotifySetExtAttr,
127 NotifySetFlags,
128 NotifySetMode,
129 NotifySetOwner,
130 NotifySignal,
131 NotifyUnlink,
132 NotifyWrite,
133 AuthFileProviderMaterialize,
134 NotifyFileProviderMaterialize,
135 AuthFileProviderUpdate,
136 NotifyFileProviderUpdate,
137 AuthReadLink,
138 NotifyReadLink,
139 AuthTruncate,
140 NotifyTruncate,
141 AuthLink,
142 NotifyLookup,
143 AuthCreate,
144 AuthSetAttrList,
145 AuthSetExtAttr,
146 AuthSetFlags,
147 AuthSetMode,
148 AuthSetOwner,
149 AuthChdir,
150 NotifyChdir,
151 AuthGetAttrList,
152 NotifyGetAttrList,
153 NotifyStat,
154 NotifyAccess,
155 AuthChroot,
156 NotifyChroot,
157 AuthUtimes,
158 NotifyUtimes,
159 AuthClone,
160 NotifyClone,
161 NotifyFcntl,
162 AuthGetExtAttr,
163 NotifyGetExtAttr,
164 AuthListenExtAttr,
165 NotifyListenExtAttr,
166 AuthReadDir,
167 NotifyReadDir,
168 AuthDeleteExtAttr,
169 NotifyDeleteExtAttr,
170 AuthFsGetPath,
171 NotifyFsGetPath,
172 NotifyDup,
173 AuthSetTime,
174 NotifySetTime,
175 NotifyUIPCBind,
176 AuthUIPCBind,
177 NotifyUIPCConnect,
178 AuthUIPCConnect,
179 AuthExchangeData,
180 AuthSetACL,
181 NotifySetACL,
182 NotifyPTYGrant,
183 NotifyPTYClose,
184 AuthProcCheck,
185 NotifyProcCheck,
186 AuthGetTask,
187 AuthSearchFs,
188 NotifySearchFs,
189 AuthFcntl,
190 AuthIOKitOpen,
191 AuthProcSuspendResume,
192 NotifyProcSuspendResume,
193 NotifyCsInvalidDate,
194 NotifyGetTaskName,
195 NotfiyTrace,
196 NotifyRemoteThreadCreate,
197 AuthRemount,
198 NotifyRemount,
199 AuthGetTaskRead,
200 NotifyGetTaskRead,
201 NotifyGetTaskInspect,
202 NotifySetUid,
203 NotifySetGid,
204 NotifySetEUid,
205 NotifySetEGuid,
206 NotifySetREUid,
207 NotifySetREGuid,
208 AuthCopyFile,
209 NotifyCopyFile,
210 NotifyAuthentication,
211 NotifyXPMalwareDetected,
212 NotifyXPMalwareRemediated,
213 NotifyLWSessionLogin,
214 NotifyLWSessionLogout,
215 NotifyLWSessionLock,
216 NotifyLWSessionUnlock,
217 NotifyScreenSharingAttach,
218 AuthScreenSharingAttach,
219 NotifyOpenSSHLogin,
220 NotifyOpenSSHLogout,
221 NotifyBTMLaunchItemAdd,
222 NotifyBTMLaunchItemRemove,
223 Last,
224}
225
226#[derive(Debug, PartialEq)]
228pub enum EsActionType {
229 Auth,
230 Notify,
231}
232
233#[repr(u32)]
234pub enum EsMutePath {
235 Prefix,
236 Literal,
237}
238
239#[derive(Debug)]
241pub struct EsRename {
242 pub source: EsFile,
244 pub destination_existing: Option<EsFile>,
246 pub destintaion_newpath: Option<(EsFile, String)>,
248}
249
250#[derive(Debug)]
252pub enum EsSSHLoginResult {
253 LoginExceedMaxTries,
254 LoginRootDenied,
255 AuthSuccess,
256 FailNone,
257 FailPasswd,
258 FailKBDInt,
259 FailPubKey,
260 FailHostBased,
261 FailGSSApi,
262 InvalidUser,
263}
264
265#[derive(Debug)]
266pub enum EsAddressType {
267 None,
268 Ipv4(std::net::Ipv4Addr),
269 Ipv6(std::net::Ipv6Addr),
270 NamedSocket(String),
271}
272
273impl EsAddressType {
274 fn parse(str: &sys::es_string_token_t, ty: u32) -> Self {
275 if ty == 0 {
276 EsAddressType::None
277 } else {
278 let addr_str = unsafe {
279 std::ffi::CStr::from_ptr(str.data)
280 .to_string_lossy()
281 .to_string()
282 };
283 match ty {
284 1 => EsAddressType::Ipv4(addr_str.parse().unwrap()),
285 2 => EsAddressType::Ipv6(addr_str.parse().unwrap()),
286 _ => panic!("Shouldn't reach here"),
287 }
288 }
289 }
290}
291
292#[derive(Debug)]
293pub struct EsSshLogin {
294 pub success: bool,
295 pub result: EsSSHLoginResult,
296 pub source_address: EsAddressType,
297 pub username: String,
298 pub uid: Option<u32>,
299}
300
301impl From<&sys::es_event_openssh_login_t> for EsSshLogin {
302 fn from(value: &sys::es_event_openssh_login_t) -> Self {
303 let username = unsafe {
304 std::ffi::CStr::from_ptr(value.username.data)
305 .to_string_lossy()
306 .to_string()
307 };
308
309 let result = match value.result_type {
310 0 => EsSSHLoginResult::LoginExceedMaxTries,
311 1 => EsSSHLoginResult::LoginRootDenied,
312 2 => EsSSHLoginResult::AuthSuccess,
313 3 => EsSSHLoginResult::FailNone,
314 4 => EsSSHLoginResult::FailPasswd,
315 5 => EsSSHLoginResult::FailKBDInt,
316 6 => EsSSHLoginResult::FailPubKey,
317 7 => EsSSHLoginResult::FailHostBased,
318 8 => EsSSHLoginResult::FailGSSApi,
319 9 => EsSSHLoginResult::InvalidUser,
320 _ => panic!("Should never reach this case"),
321 };
322
323 Self {
324 success: value.success,
325 username,
326 uid: (if value.has_uid {
327 unsafe { Some(value.uid.uid) }
328 } else {
329 None
330 }),
331 result,
332 source_address: EsAddressType::parse(&value.source_address, value.source_address_type),
333 }
334 }
335}
336
337#[derive(Debug)]
338pub struct EsSSHLogout {
339 pub source_address: EsAddressType,
340 pub username: String,
341 pub uid: u32,
342}
343
344impl From<&sys::es_event_openssh_logout_t> for EsSSHLogout {
345 fn from(value: &sys::es_event_openssh_logout_t) -> Self {
346 Self {
347 username: unsafe {
348 std::ffi::CStr::from_ptr(value.username.data)
349 .to_string_lossy()
350 .to_string()
351 },
352 uid: value.uid,
353 source_address: EsAddressType::parse(&value.source_address, value.source_address_type),
354 }
355 }
356}
357
358#[derive(Debug)]
359pub struct EsUnlinkFile {
360 pub parent_dir: EsFile,
361 pub target: EsFile,
362}
363
364impl From<sys::es_event_unlink_t> for EsUnlinkFile {
365 fn from(value: sys::es_event_unlink_t) -> Self {
366 unsafe {
367 Self {
368 parent_dir: value.parent_dir.as_ref().unwrap().into(),
369 target: value.target.as_ref().unwrap().into(),
370 }
371 }
372 }
373}
374
375#[derive(Debug)]
376pub struct EsCopyFile {
377 pub source: Option<EsFile>,
378 pub target_file: Option<EsFile>,
379 pub target_dir: Option<EsFile>,
380 pub target_name: String,
381 pub mode: u16,
382 pub flags: i32,
383}
384
385impl From<sys::es_event_copyfile_t> for EsCopyFile {
386 fn from(value: sys::es_event_copyfile_t) -> Self {
387 unsafe {
388 Self {
389 source: value.source.as_ref().map(|src| src.into()),
390 target_file: value.target_file.as_ref().map(|tar| tar.into()),
391 target_dir: value.target_dir.as_ref().map(|t_dir| t_dir.into()),
392 target_name: CStr::from_ptr(value.target_name.data)
393 .to_string_lossy()
394 .to_string(),
395 mode: value.mode,
396 flags: value.flags,
397 }
398 }
399 }
400}
401
402#[derive(Debug)]
403pub enum EsDestination {
404 ExistingFile(EsFile),
405 NewPath(EsCreateMetadata),
406}
407
408#[derive(Debug)]
409pub struct EsCreateMetadata {
410 pub dir: EsFile,
411 pub filename: String,
412 pub mode: u16,
413}
414
415#[derive(Debug)]
416pub struct EsCreate {
417 pub destination: EsDestination,
418}
419
420impl From<sys::es_event_create_t> for EsCreate {
421 fn from(value: sys::es_event_create_t) -> Self {
422 let destination = match value.destination_type {
423 0 => unsafe {
424 EsDestination::ExistingFile(
425 value.destination.existing_file.as_ref().unwrap().into(),
426 )
427 },
428 1 => {
429 let new_path = unsafe { value.destination.new_path };
430 unsafe {
431 EsDestination::NewPath(EsCreateMetadata {
432 dir: new_path.dir.as_ref().unwrap().into(),
433 filename: es_string_to_string!(new_path.filename.data),
434 mode: new_path.mode,
435 })
436 }
437 }
438 _ => {
439 panic!("EsCreate: destination type out of range")
440 }
441 };
442 Self { destination }
443 }
444}
445
446#[derive(Debug)]
447pub struct EsClone {
448 pub source: EsFile,
449 pub target_dir: EsFile,
450 pub name: String,
451}
452
453impl From<sys::es_event_clone_t> for EsClone {
454 fn from(value: sys::es_event_clone_t) -> Self {
455 Self {
456 source: unsafe { value.source.as_ref().unwrap().into() },
457 target_dir: unsafe { value.target_dir.as_ref().unwrap().into() },
458 name: unsafe { es_string_to_string!(value.target_name.data) },
459 }
460 }
461}
462
463#[derive(Debug)]
464pub struct EsMMap {
465 pub protection: i32,
466 pub max_protection: i32,
467 pub flags: i32,
468 pub file_pos: u64,
469 pub source: EsFile,
470}
471
472impl From<sys::es_event_mmap_t> for EsMMap {
473 fn from(value: sys::es_event_mmap_t) -> Self {
474 Self {
475 protection: value.protection,
476 max_protection: value.max_protection,
477 flags: value.flags,
478 file_pos: value.file_pos,
479 source: unsafe { value.source.as_ref() }.unwrap().into(),
480 }
481 }
482}
483
484#[derive(Debug)]
485pub struct EsScreenSharingAttach {
486 pub success: bool,
487 pub source_address: EsAddressType,
488 pub viewer_appleid: Option<String>,
489 pub authentication_type: Option<String>,
490 pub session_username: Option<String>,
491 pub existing_session: bool,
492 pub graphical_session_id: u32,
493}
494
495impl From<sys::es_event_screensharing_attach_t> for EsScreenSharingAttach {
496 fn from(value: sys::es_event_screensharing_attach_t) -> Self {
497 Self {
498 success: value.success,
499 source_address: EsAddressType::parse(&value.source_address, value.source_address_type),
500 viewer_appleid: unsafe { es_string_to_opt_string!(value.viewer_appleid.data) },
501 authentication_type: unsafe {
502 es_string_to_opt_string!(value.authentication_type.data)
503 },
504 existing_session: value.existing_session,
505 graphical_session_id: value.graphical_session_id,
506 session_username: unsafe { es_string_to_opt_string!(value.viewer_appleid.data) },
507 }
508 }
509}
510
511#[derive(Debug)]
512pub enum EsEventData {
513 AuthOpen(EsFile),
514 AuthRename(EsRename),
515 AuthUnlink(EsUnlinkFile),
516 AuthReadDir(EsFile),
517 AuthChroot(EsFile),
518 AuthCopyFile(EsCopyFile),
519 NotifyCopyFile(EsCopyFile),
520
521 NotifyClone(EsClone),
522 AuthClone(EsClone),
523
524 NotifyMMap(EsMMap),
525 AuthMMap(EsMMap),
526
527 NotifyExit(i32),
528
529 NotifyOpen(EsFile),
530 NotifyExec(EsProcess),
531 NotifyWrite(EsFile),
532 NotifyRename(EsRename),
533 NotifyReadDir(EsFile),
534 NotifyChroot(EsFile),
535 NotifyClose((EsFile, bool)),
537 NotifyOpenSSHLogin(EsSshLogin),
538 NotifyOpenSSHLogout(EsSSHLogout),
539 NotifyLWSessionLock(EsLWSession),
540 NotifyLWSessionUnlock(EsLWSession),
541 NotifyLWSessionLogin(EsLWSession),
542 NotifyLWSessionLogout(EsLWSession),
543}
544
545impl From<sys::es_event_rename_t> for EsRename {
546 fn from(value: sys::es_event_rename_t) -> Self {
547 let source = unsafe { value.source.as_ref().unwrap().into() };
548
549 let mut rename_info = Self {
550 source,
551 destination_existing: None,
552 destintaion_newpath: None,
553 };
554
555 if value.destination_type == 0 {
556 rename_info.destination_existing = unsafe {
557 value
558 .destination
559 .existing_file
560 .as_ref()
561 .map(|file| file.into())
562 };
563 } else {
564 rename_info.destintaion_newpath = Some((
565 unsafe { value.destination.new_path.dir.as_ref().unwrap().into() },
566 unsafe {
567 std::ffi::CStr::from_ptr(value.destination.new_path.filename.data)
568 .to_string_lossy()
569 .to_string()
570 },
571 ));
572 }
573
574 rename_info
575 }
576}
577
578#[derive(Debug)]
579pub struct EsFile {
580 pub path: String,
581 pub path_truncated: bool,
582}
583
584impl From<&sys::es_file_t> for EsFile {
585 fn from(file: &sys::es_file_t) -> Self {
586 let path = unsafe { std::ffi::CStr::from_ptr(file.path.data) }
587 .to_string_lossy()
588 .to_string();
589
590 Self {
591 path,
592 path_truncated: file.path_truncated,
593 }
594 }
595}
596
597#[derive(Debug)]
598pub struct EsProcess {
599 pub pid: i32,
601 pub ppid: i32,
603 pub gid: i32,
605 pub exe: EsFile,
606 audit_token: sys::audit_token_t,
607}
608
609impl EsProcess {
610 pub fn audit_token(&self) -> sys::audit_token_t {
611 self.audit_token
612 }
613
614 pub fn mute(&self, client: &EsClient) {
615 unsafe { sys::es_mute_process(client.client as _, &self.audit_token as _) };
616 }
617}
618
619impl From<&sys::es_process_t> for EsProcess {
620 fn from(value: &sys::es_process_t) -> Self {
621 let pid = unsafe { bsm::audit_token_to_pid(value.audit_token) };
622
623 Self {
624 audit_token: value.audit_token,
625 ppid: value.ppid,
626 gid: value.group_id,
627 exe: unsafe { value.executable.as_ref().unwrap().into() },
628 pid,
629 }
630 }
631}
632
633#[derive(Debug)]
634pub struct EsLWSession {
635 pub graphical_session_id: u32,
636 pub username: String,
637}
638
639impl EsLWSession {
640 fn from_es_type(id: u32, str: *const i8) -> Self {
641 Self {
642 graphical_session_id: id,
643 username: unsafe { CStr::from_ptr(str).to_string_lossy().to_string() },
644 }
645 }
646}
647
648pub struct EsUser {
649 pub uid: u32,
650 pub username: String,
651}
652
653pub struct EsLogin {
654 pub success: bool,
655 pub err: Option<String>,
656 pub user: EsUser,
657}
658
659impl From<&sys::es_event_login_login_t> for EsLogin {
660 fn from(value: &sys::es_event_login_login_t) -> Self {
661 Self {
662 success: value.success,
663 err: if !value.success {
664 Some(unsafe { es_string_to_string!(value.failure_message.data) })
665 } else {
666 None
667 },
668 user: EsUser {
669 uid: unsafe { value.uid.uid },
670 username: unsafe { es_string_to_string!(value.username.data) },
671 },
672 }
673 }
674}
675
676#[derive(Debug)]
677pub struct EsMessage {
678 pub action: EsActionType,
679 pub event: EsEventType,
680 pub event_data: Option<EsEventData>,
681 pub version: u32,
682 pub seq_num: u64,
683 pub process: Option<EsProcess>,
684 pub thread_id: Option<u64>,
685 message_ptr: *const sys::es_message_t,
686}
687
688impl EsMessage {
689 pub fn allow(&self, client: &EsClient) {
690 if self.action == EsActionType::Auth {
691 assert!(
692 unsafe { sys::es_respond_auth_result(client.client, self.message_ptr, 0, true) }
693 == 0
694 );
695 }
696 }
697
698 pub fn deny(&self, client: &EsClient) {
699 if self.action == EsActionType::Auth {
700 assert!(
701 unsafe { sys::es_respond_auth_result(client.client, self.message_ptr, 1, true) }
702 == 0
703 );
704 }
705 }
706}
707
708impl Drop for EsMessage {
709 fn drop(&mut self) {
710 unsafe { sys::es_release_message(self.message_ptr) };
711 }
712}
713
714impl From<&sys::es_message_t> for EsMessage {
715 fn from(message: &sys::es_message_t) -> Self {
716 let action = match message.action_type {
717 0 => EsActionType::Auth,
718 1 => EsActionType::Notify,
719 _ => panic!("Useless"),
720 };
721
722 let eve_type: EsEventType = unsafe { std::mem::transmute(message.event_type) };
724 let process = unsafe { message.process.as_ref().map(|process| process.into()) };
725 let thread_id = unsafe { message.thread.as_ref().map(|tid| tid.thread_id) };
726
727 let eve = match eve_type {
730 EsEventType::AuthOpen => unsafe {
731 message
732 .event
733 .open
734 .file
735 .as_ref()
736 .map(|file| EsEventData::AuthOpen(file.into()))
737 },
738 EsEventType::AuthRename => Some(EsEventData::AuthRename(unsafe {
739 message.event.rename.into()
740 })),
741 EsEventType::AuthUnlink => Some(EsEventData::AuthUnlink(unsafe {
742 message.event.unlink.into()
743 })),
744 EsEventType::AuthReadDir => unsafe {
745 message
746 .event
747 .readdir
748 .target
749 .as_ref()
750 .map(|readdir| EsEventData::AuthReadDir(readdir.into()))
751 },
752 EsEventType::AuthChroot => unsafe {
753 message
754 .event
755 .chroot
756 .target
757 .as_ref()
758 .map(|chroot| EsEventData::AuthChroot(chroot.into()))
759 },
760 EsEventType::NotifyChroot => unsafe {
761 message
762 .event
763 .chroot
764 .target
765 .as_ref()
766 .map(|chroot| EsEventData::NotifyChroot(chroot.into()))
767 },
768 EsEventType::NotifyExec => unsafe {
769 message
770 .event
771 .exec
772 .target
773 .as_ref()
774 .map(|tar| EsEventData::NotifyExec(tar.into()))
775 },
776 EsEventType::NotifyOpen => unsafe {
777 message
778 .event
779 .open
780 .file
781 .as_ref()
782 .map(|file| EsEventData::NotifyOpen(file.into()))
783 },
784 EsEventType::NotifyWrite => unsafe {
785 message
786 .event
787 .write
788 .target
789 .as_ref()
790 .map(|target| EsEventData::NotifyWrite(target.into()))
791 },
792 EsEventType::NotifyRename => Some(EsEventData::NotifyRename(unsafe {
793 message.event.rename.into()
794 })),
795 EsEventType::NotifyClose => Some(EsEventData::NotifyClose((
796 unsafe { message.event.close.target.as_ref().unwrap().into() },
797 unsafe { message.event.close.modified },
798 ))),
799 EsEventType::NotifyReadDir => unsafe {
800 message
801 .event
802 .readdir
803 .target
804 .as_ref()
805 .map(|readdir| EsEventData::NotifyReadDir(readdir.into()))
806 },
807 EsEventType::NotifyOpenSSHLogin => unsafe {
808 message
809 .event
810 .openssh_login
811 .as_ref()
812 .map(|login| EsEventData::NotifyOpenSSHLogin(login.into()))
813 },
814 EsEventType::NotifyOpenSSHLogout => unsafe {
815 message
816 .event
817 .openssh_logout
818 .as_ref()
819 .map(|logout| EsEventData::NotifyOpenSSHLogout(logout.into()))
820 },
821 EsEventType::NotifyLWSessionLock => unsafe {
822 message.event.lw_session_lock.as_ref().map(|session| {
823 EsEventData::NotifyLWSessionLock(EsLWSession::from_es_type(
824 session.graphical_session_id,
825 session.username.data,
826 ))
827 })
828 },
829 EsEventType::NotifyLWSessionLogin => unsafe {
830 message.event.lw_session_login.as_ref().map(|session| {
831 EsEventData::NotifyLWSessionLogin(EsLWSession::from_es_type(
832 session.graphical_session_id,
833 session.username.data,
834 ))
835 })
836 },
837 EsEventType::NotifyLWSessionLogout => unsafe {
838 message.event.lw_session_logout.as_ref().map(|session| {
839 EsEventData::NotifyLWSessionLogout(EsLWSession::from_es_type(
840 session.graphical_session_id,
841 session.username.data,
842 ))
843 })
844 },
845 EsEventType::NotifyLWSessionUnlock => unsafe {
846 message.event.lw_session_unlock.as_ref().map(|session| {
847 EsEventData::NotifyLWSessionUnlock(EsLWSession::from_es_type(
848 session.graphical_session_id,
849 session.username.data,
850 ))
851 })
852 },
853 EsEventType::AuthCopyFile => unsafe {
854 Some(EsEventData::AuthCopyFile(message.event.copyfile.into()))
855 },
856 EsEventType::NotifyCopyFile => unsafe {
857 Some(EsEventData::NotifyCopyFile(message.event.copyfile.into()))
858 },
859 EsEventType::NotifyClone => unsafe {
860 Some(EsEventData::AuthClone(message.event.clone.into()))
861 },
862 EsEventType::AuthClone => unsafe {
863 Some(EsEventData::AuthClone(message.event.clone.into()))
864 },
865 EsEventType::NotifyMMap => unsafe {
866 Some(EsEventData::NotifyMMap(message.event.mmap.into()))
867 },
868 EsEventType::AuthMMap => unsafe {
869 Some(EsEventData::AuthMMap(message.event.mmap.into()))
870 },
871 EsEventType::NotifyExit => unsafe {
872 Some(EsEventData::NotifyExit(message.event.exit.stat))
873 },
874 _ => None,
875 };
876
877 unsafe { sys::es_retain_message(message as _) }
878
879 Self {
880 event: eve_type,
881 event_data: eve,
882 version: message.version,
883 seq_num: message.seq_num,
884 message_ptr: message as _,
885 action,
886 process,
887 thread_id,
888 }
889 }
890}
891
892pub struct EsClient {
894 client: *mut sys::es_client_t,
895 subscribed_events: Vec<EsEventType>,
896 pub rx: crossbeam::channel::Receiver<EsMessage>,
897}
898
899impl EsClient {
900 extern "C" fn handler(_c: *mut sys::es_client_t, _m: *const sys::es_message_t) {}
901
902 pub fn new() -> anyhow::Result<EsClient> {
910 let mut client: *mut sys::es_client_t = std::ptr::null_mut();
911
912 let (tx, rx) = channel::unbounded();
913
914 let handler = ConcreteBlock::new(move |c, msg: *const sys::es_message_t| {
915 let message = unsafe { msg.as_ref() };
916 if message.is_none() {
917 println!("Failed to get message reference");
918 return;
919 }
920 let message = message.unwrap();
921 assert!(msg as usize == message as *const _ as usize);
922
923 _ = tx.send(message.into());
924
925 Self::handler(c, msg);
927 })
928 .copy();
929
930 if let Some(err) = EsClientCreateError::from_u32(unsafe {
931 sys::es_new_client(
932 &mut client as _,
933 &*handler as *const block::Block<_, _> as *mut std::ffi::c_void,
934 )
935 }) {
936 anyhow::bail!(err)
937 }
938
939 Ok(EsClient {
940 client,
941 subscribed_events: vec![],
942 rx,
943 })
944 }
945
946 pub fn add_event(&mut self, event: EsEventType) -> &mut Self {
948 if !self.subscribed_events.contains(&event) {
949 self.subscribed_events.push(event);
950 }
951 self
952 }
953
954 pub fn subscribe(&self) {
956 let mut event_ids = vec![];
957 for evt in &self.subscribed_events {
958 event_ids.push(*evt as sys::es_event_type_t)
959 }
960
961 if unsafe {
962 sys::es_subscribe(self.client as _, event_ids.as_ptr(), event_ids.len() as u32)
963 } != 0
964 {
965 panic!("Error: Failed to subscribe");
966 }
967 }
968
969 pub fn unsubscribe_all(&self) -> bool {
971 (unsafe { sys::es_unsubscribe_all(self.client) } == 0)
972 }
973
974 pub fn unsubscribe(&mut self, event: EsEventType) -> bool {
976 if let Some(idx) = self.subscribed_events.iter().position(|eve| *eve == event) {
977 self.subscribed_events.swap_remove(idx);
978 } else {
979 return false;
980 }
981
982 let events = vec![event as u32];
983
984 (unsafe { sys::es_unsubscribe(self.client, events.as_ptr(), events.len() as u32) } == 0)
985 }
986
987 pub fn subscriptions(&self) -> Option<Vec<EsEventType>> {
989 let mut count = 0;
990 let mut eves: *mut EsEventType = core::ptr::null_mut();
991 if unsafe {
992 sys::es_subscriptions(
993 self.client,
994 &mut count,
995 &mut eves as *mut *mut _ as *mut *mut u32,
996 )
997 } != 0
998 {
999 None
1000 } else {
1001 let events = unsafe { std::slice::from_raw_parts(eves, count) }.to_vec();
1002
1003 extern "C" {
1005 fn free(ptr: *mut std::ffi::c_void);
1006 }
1007 unsafe { free(eves as _) };
1008
1009 Some(events)
1010 }
1011 }
1012
1013 pub fn recv_msg(&self) -> Result<EsMessage, channel::RecvError> {
1015 self.rx.recv()
1016 }
1017
1018 pub fn try_recv_msg(&self) -> Result<EsMessage, channel::TryRecvError> {
1020 self.rx.try_recv()
1021 }
1022
1023 pub fn mute_path(&self, path: &std::path::Path, ty: EsMutePath) -> bool {
1026 (unsafe { sys::es_mute_path(self.client, path.to_string_lossy().as_ptr() as _, ty as u32) }
1027 == 0)
1028 }
1029
1030 pub fn unmute_path(&self, path: &std::path::Path, ty: EsMutePath) -> bool {
1033 (unsafe {
1034 sys::es_unmute_path(self.client, path.to_string_lossy().as_ptr() as _, ty as u32)
1035 } == 0)
1036 }
1037
1038 pub fn unmute_path_events(
1040 &self,
1041 path: &std::path::Path,
1042 ty: EsMutePath,
1043 events: &[EsEventType],
1044 ) -> bool {
1045 let events: Vec<_> = events.iter().map(|event| *event as u32).collect();
1046
1047 (unsafe {
1048 sys::es_unmute_path_events(
1049 self.client,
1050 path.to_string_lossy().as_ptr() as _,
1051 ty as u32,
1052 events.as_ptr(),
1053 events.len(),
1054 )
1055 } == 0)
1056 }
1057
1058 pub fn unmute_all_paths(&self) -> bool {
1060 (unsafe { sys::es_unmute_all_paths(self.client) } == 0)
1061 }
1062
1063 pub fn destroy_client(self) {}
1065}
1066
1067impl Drop for EsClient {
1068 fn drop(&mut self) {
1069 if unsafe { sys::es_delete_client(self.client) } != 0 {
1070 println!("Failed to delete client");
1071 }
1072 }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077
1078 #[test]
1079 pub fn test_new_es_client() {
1080 let client = crate::EsClient::new();
1081 assert!(client.is_ok());
1082 }
1083}