1use std::ffi::c_void;
2use std::path::PathBuf;
3use std::ptr::NonNull;
4use std::time::SystemTime;
5
6use binaryninjacore_sys::*;
7
8use super::{
9 sync, CollaborationPermissionLevel, NameChangeset, Permission, Remote, RemoteFile,
10 RemoteFileType, RemoteFolder,
11};
12
13use crate::binary_view::{BinaryView, BinaryViewExt};
14use crate::database::Database;
15use crate::file_metadata::FileMetadata;
16use crate::progress::{NoProgressCallback, ProgressCallback};
17use crate::project::Project;
18use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
19use crate::string::{BnString, IntoCStr};
20
21#[repr(transparent)]
22pub struct RemoteProject {
23 pub(crate) handle: NonNull<BNRemoteProject>,
24}
25
26impl RemoteProject {
27 pub(crate) unsafe fn from_raw(handle: NonNull<BNRemoteProject>) -> Self {
28 Self { handle }
29 }
30
31 pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRemoteProject>) -> Ref<Self> {
32 Ref::new(Self { handle })
33 }
34
35 pub fn is_open(&self) -> bool {
37 unsafe { BNRemoteProjectIsOpen(self.handle.as_ptr()) }
38 }
39
40 pub fn open(&self) -> Result<(), ()> {
43 self.open_with_progress(NoProgressCallback)
44 }
45
46 pub fn open_with_progress<F: ProgressCallback>(&self, mut progress: F) -> Result<(), ()> {
49 if self.is_open() {
50 return Ok(());
51 }
52 let success = unsafe {
53 BNRemoteProjectOpen(
54 self.handle.as_ptr(),
55 Some(F::cb_progress_callback),
56 &mut progress as *mut F as *mut c_void,
57 )
58 };
59 success.then_some(()).ok_or(())
60 }
61
62 pub fn close(&self) {
64 unsafe { BNRemoteProjectClose(self.handle.as_ptr()) }
65 }
66
67 pub fn get_for_local_database(database: &Database) -> Result<Option<Ref<Self>>, ()> {
69 if sync::pull_projects(database)? {
71 return Ok(None);
72 }
73 sync::get_remote_project_for_local_database(database)
74 }
75
76 pub fn get_for_binaryview(bv: &BinaryView) -> Result<Option<Ref<Self>>, ()> {
78 let file = bv.file();
79 let Some(database) = file.database() else {
80 return Ok(None);
81 };
82 Self::get_for_local_database(&database)
83 }
84
85 pub fn core_project(&self) -> Result<Ref<Project>, ()> {
89 self.open()?;
91
92 let value = unsafe { BNRemoteProjectGetCoreProject(self.handle.as_ptr()) };
93 NonNull::new(value)
94 .map(|handle| unsafe { Project::ref_from_raw(handle) })
95 .ok_or(())
96 }
97
98 pub fn remote(&self) -> Result<Ref<Remote>, ()> {
100 let value = unsafe { BNRemoteProjectGetRemote(self.handle.as_ptr()) };
101 NonNull::new(value)
102 .map(|handle| unsafe { Remote::ref_from_raw(handle) })
103 .ok_or(())
104 }
105
106 pub fn url(&self) -> String {
108 let result = unsafe { BNRemoteProjectGetUrl(self.handle.as_ptr()) };
109 assert!(!result.is_null());
110 unsafe { BnString::into_string(result) }
111 }
112
113 pub fn id(&self) -> String {
115 let result = unsafe { BNRemoteProjectGetId(self.handle.as_ptr()) };
116 assert!(!result.is_null());
117 unsafe { BnString::into_string(result) }
118 }
119
120 pub fn created(&self) -> SystemTime {
122 let result = unsafe { BNRemoteProjectGetCreated(self.handle.as_ptr()) };
123 crate::ffi::time_from_bn(result.try_into().unwrap())
124 }
125
126 pub fn last_modified(&self) -> SystemTime {
128 let result = unsafe { BNRemoteProjectGetLastModified(self.handle.as_ptr()) };
129 crate::ffi::time_from_bn(result.try_into().unwrap())
130 }
131
132 pub fn name(&self) -> String {
134 let result = unsafe { BNRemoteProjectGetName(self.handle.as_ptr()) };
135 assert!(!result.is_null());
136 unsafe { BnString::into_string(result) }
137 }
138
139 pub fn set_name(&self, name: &str) -> Result<(), ()> {
141 let name = name.to_cstr();
142 let success = unsafe { BNRemoteProjectSetName(self.handle.as_ptr(), name.as_ptr()) };
143 success.then_some(()).ok_or(())
144 }
145
146 pub fn description(&self) -> String {
148 let result = unsafe { BNRemoteProjectGetDescription(self.handle.as_ptr()) };
149 assert!(!result.is_null());
150 unsafe { BnString::into_string(result) }
151 }
152
153 pub fn set_description(&self, description: &str) -> Result<(), ()> {
155 let description = description.to_cstr();
156 let success =
157 unsafe { BNRemoteProjectSetDescription(self.handle.as_ptr(), description.as_ptr()) };
158 success.then_some(()).ok_or(())
159 }
160
161 pub fn received_file_count(&self) -> u64 {
163 unsafe { BNRemoteProjectGetReceivedFileCount(self.handle.as_ptr()) }
164 }
165
166 pub fn received_folder_count(&self) -> u64 {
168 unsafe { BNRemoteProjectGetReceivedFolderCount(self.handle.as_ptr()) }
169 }
170
171 pub fn default_path(&self) -> Result<PathBuf, ()> {
174 sync::default_project_path(self)
175 }
176
177 pub fn has_pulled_files(&self) -> bool {
179 unsafe { BNRemoteProjectHasPulledFiles(self.handle.as_ptr()) }
180 }
181
182 pub fn has_pulled_folders(&self) -> bool {
184 unsafe { BNRemoteProjectHasPulledFolders(self.handle.as_ptr()) }
185 }
186
187 pub fn has_pulled_group_permissions(&self) -> bool {
189 unsafe { BNRemoteProjectHasPulledGroupPermissions(self.handle.as_ptr()) }
190 }
191
192 pub fn has_pulled_user_permissions(&self) -> bool {
194 unsafe { BNRemoteProjectHasPulledUserPermissions(self.handle.as_ptr()) }
195 }
196
197 pub fn is_admin(&self) -> bool {
200 unsafe { BNRemoteProjectIsAdmin(self.handle.as_ptr()) }
201 }
202
203 pub fn files(&self) -> Result<Array<RemoteFile>, ()> {
209 if !self.has_pulled_files() {
211 self.pull_files()?;
212 }
213
214 let mut count = 0;
215 let result = unsafe { BNRemoteProjectGetFiles(self.handle.as_ptr(), &mut count) };
216 (!result.is_null())
217 .then(|| unsafe { Array::new(result, count, ()) })
218 .ok_or(())
219 }
220
221 pub fn get_file_by_id(&self, id: &str) -> Result<Option<Ref<RemoteFile>>, ()> {
226 if !self.has_pulled_files() {
228 self.pull_files()?;
229 }
230 let id = id.to_cstr();
231 let result = unsafe { BNRemoteProjectGetFileById(self.handle.as_ptr(), id.as_ptr()) };
232 Ok(NonNull::new(result).map(|handle| unsafe { RemoteFile::ref_from_raw(handle) }))
233 }
234
235 pub fn get_file_by_name(&self, name: &str) -> Result<Option<Ref<RemoteFile>>, ()> {
240 if !self.has_pulled_files() {
242 self.pull_files()?;
243 }
244 let id = name.to_cstr();
245 let result = unsafe { BNRemoteProjectGetFileByName(self.handle.as_ptr(), id.as_ptr()) };
246 Ok(NonNull::new(result).map(|handle| unsafe { RemoteFile::ref_from_raw(handle) }))
247 }
248
249 pub fn pull_files(&self) -> Result<(), ()> {
254 self.pull_files_with_progress(NoProgressCallback)
255 }
256
257 pub fn pull_files_with_progress<P: ProgressCallback>(&self, mut progress: P) -> Result<(), ()> {
262 if !self.has_pulled_folders() {
264 self.pull_folders()?;
265 }
266 let success = unsafe {
267 BNRemoteProjectPullFiles(
268 self.handle.as_ptr(),
269 Some(P::cb_progress_callback),
270 &mut progress as *mut P as *mut c_void,
271 )
272 };
273 success.then_some(()).ok_or(())
274 }
275
276 pub fn create_file(
287 &self,
288 filename: &str,
289 contents: &[u8],
290 name: &str,
291 description: &str,
292 parent_folder: Option<&RemoteFolder>,
293 file_type: RemoteFileType,
294 ) -> Result<Ref<RemoteFile>, ()> {
295 self.create_file_with_progress(
296 filename,
297 contents,
298 name,
299 description,
300 parent_folder,
301 file_type,
302 NoProgressCallback,
303 )
304 }
305
306 pub fn create_file_with_progress<P>(
318 &self,
319 filename: &str,
320 contents: &[u8],
321 name: &str,
322 description: &str,
323 parent_folder: Option<&RemoteFolder>,
324 file_type: RemoteFileType,
325 mut progress: P,
326 ) -> Result<Ref<RemoteFile>, ()>
327 where
328 P: ProgressCallback,
329 {
330 self.open()?;
332
333 let filename = filename.to_cstr();
334 let name = name.to_cstr();
335 let description = description.to_cstr();
336 let folder_handle = parent_folder.map_or(std::ptr::null_mut(), |f| f.handle.as_ptr());
337 let file_ptr = unsafe {
338 BNRemoteProjectCreateFile(
339 self.handle.as_ptr(),
340 filename.as_ptr(),
341 contents.as_ptr() as *mut _,
342 contents.len(),
343 name.as_ptr(),
344 description.as_ptr(),
345 folder_handle,
346 file_type,
347 Some(P::cb_progress_callback),
348 &mut progress as *mut P as *mut c_void,
349 )
350 };
351
352 NonNull::new(file_ptr)
353 .map(|handle| unsafe { RemoteFile::ref_from_raw(handle) })
354 .ok_or(())
355 }
356
357 pub fn push_file<I>(&self, file: &RemoteFile, extra_fields: I) -> Result<(), ()>
361 where
362 I: IntoIterator<Item = (String, String)>,
363 {
364 self.open()?;
366
367 let (keys, values): (Vec<_>, Vec<_>) = extra_fields
368 .into_iter()
369 .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
370 .unzip();
371 let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
372 let mut values_raw = values.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
373 let success = unsafe {
374 BNRemoteProjectPushFile(
375 self.handle.as_ptr(),
376 file.handle.as_ptr(),
377 keys_raw.as_mut_ptr(),
378 values_raw.as_mut_ptr(),
379 keys_raw.len(),
380 )
381 };
382 success.then_some(()).ok_or(())
383 }
384
385 pub fn delete_file(&self, file: &RemoteFile) -> Result<(), ()> {
386 self.open()?;
388
389 let success =
390 unsafe { BNRemoteProjectDeleteFile(self.handle.as_ptr(), file.handle.as_ptr()) };
391 success.then_some(()).ok_or(())
392 }
393
394 pub fn folders(&self) -> Result<Array<RemoteFolder>, ()> {
399 if !self.has_pulled_folders() {
401 self.pull_folders()?;
402 }
403 let mut count = 0;
404 let result = unsafe { BNRemoteProjectGetFolders(self.handle.as_ptr(), &mut count) };
405 if result.is_null() {
406 return Err(());
407 }
408 Ok(unsafe { Array::new(result, count, ()) })
409 }
410
411 pub fn get_folder_by_id(&self, id: &str) -> Result<Option<Ref<RemoteFolder>>, ()> {
416 if !self.has_pulled_folders() {
418 self.pull_folders()?;
419 }
420 let id = id.to_cstr();
421 let result = unsafe { BNRemoteProjectGetFolderById(self.handle.as_ptr(), id.as_ptr()) };
422 Ok(NonNull::new(result).map(|handle| unsafe { RemoteFolder::ref_from_raw(handle) }))
423 }
424
425 pub fn pull_folders(&self) -> Result<(), ()> {
429 self.pull_folders_with_progress(NoProgressCallback)
430 }
431
432 pub fn pull_folders_with_progress<P: ProgressCallback>(
436 &self,
437 mut progress: P,
438 ) -> Result<(), ()> {
439 self.open()?;
441
442 let success = unsafe {
443 BNRemoteProjectPullFolders(
444 self.handle.as_ptr(),
445 Some(P::cb_progress_callback),
446 &mut progress as *mut P as *mut c_void,
447 )
448 };
449 success.then_some(()).ok_or(())
450 }
451
452 pub fn create_folder(
460 &self,
461 name: &str,
462 description: &str,
463 parent_folder: Option<&RemoteFolder>,
464 ) -> Result<Ref<RemoteFolder>, ()> {
465 self.create_folder_with_progress(name, description, parent_folder, NoProgressCallback)
466 }
467
468 pub fn create_folder_with_progress<P>(
477 &self,
478 name: &str,
479 description: &str,
480 parent_folder: Option<&RemoteFolder>,
481 mut progress: P,
482 ) -> Result<Ref<RemoteFolder>, ()>
483 where
484 P: ProgressCallback,
485 {
486 self.open()?;
488
489 let name = name.to_cstr();
490 let description = description.to_cstr();
491 let folder_handle = parent_folder.map_or(std::ptr::null_mut(), |f| f.handle.as_ptr());
492 let file_ptr = unsafe {
493 BNRemoteProjectCreateFolder(
494 self.handle.as_ptr(),
495 name.as_ptr(),
496 description.as_ptr(),
497 folder_handle,
498 Some(P::cb_progress_callback),
499 &mut progress as *mut P as *mut c_void,
500 )
501 };
502
503 NonNull::new(file_ptr)
504 .map(|handle| unsafe { RemoteFolder::ref_from_raw(handle) })
505 .ok_or(())
506 }
507
508 pub fn push_folder<I>(&self, folder: &RemoteFolder, extra_fields: I) -> Result<(), ()>
515 where
516 I: IntoIterator<Item = (String, String)>,
517 {
518 self.open()?;
520
521 let (keys, values): (Vec<_>, Vec<_>) = extra_fields
522 .into_iter()
523 .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
524 .unzip();
525 let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
526 let mut values_raw = values.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
527 let success = unsafe {
528 BNRemoteProjectPushFolder(
529 self.handle.as_ptr(),
530 folder.handle.as_ptr(),
531 keys_raw.as_mut_ptr(),
532 values_raw.as_mut_ptr(),
533 keys_raw.len(),
534 )
535 };
536 success.then_some(()).ok_or(())
537 }
538
539 pub fn delete_folder(&self, folder: &RemoteFolder) -> Result<(), ()> {
543 self.open()?;
545
546 let success =
547 unsafe { BNRemoteProjectDeleteFolder(self.handle.as_ptr(), folder.handle.as_ptr()) };
548 success.then_some(()).ok_or(())
549 }
550
551 pub fn group_permissions(&self) -> Result<Array<Permission>, ()> {
555 if !self.has_pulled_group_permissions() {
557 self.pull_group_permissions()?;
558 }
559
560 let mut count: usize = 0;
561 let value = unsafe { BNRemoteProjectGetGroupPermissions(self.handle.as_ptr(), &mut count) };
562 assert!(!value.is_null());
563 Ok(unsafe { Array::new(value, count, ()) })
564 }
565
566 pub fn user_permissions(&self) -> Result<Array<Permission>, ()> {
570 if !self.has_pulled_user_permissions() {
572 self.pull_user_permissions()?;
573 }
574
575 let mut count: usize = 0;
576 let value = unsafe { BNRemoteProjectGetUserPermissions(self.handle.as_ptr(), &mut count) };
577 assert!(!value.is_null());
578 Ok(unsafe { Array::new(value, count, ()) })
579 }
580
581 pub fn get_permission_by_id(&self, id: &str) -> Result<Option<Ref<Permission>>, ()> {
585 if !self.has_pulled_user_permissions() {
587 self.pull_user_permissions()?;
588 }
589 if !self.has_pulled_group_permissions() {
591 self.pull_group_permissions()?;
592 }
593
594 let id = id.to_cstr();
595 let value = unsafe {
596 BNRemoteProjectGetPermissionById(self.handle.as_ptr(), id.as_ref().as_ptr() as *const _)
597 };
598 Ok(NonNull::new(value).map(|v| unsafe { Permission::ref_from_raw(v) }))
599 }
600
601 pub fn pull_group_permissions(&self) -> Result<(), ()> {
603 self.pull_group_permissions_with_progress(NoProgressCallback)
604 }
605
606 pub fn pull_group_permissions_with_progress<F: ProgressCallback>(
608 &self,
609 mut progress: F,
610 ) -> Result<(), ()> {
611 let success = unsafe {
612 BNRemoteProjectPullGroupPermissions(
613 self.handle.as_ptr(),
614 Some(F::cb_progress_callback),
615 &mut progress as *mut F as *mut c_void,
616 )
617 };
618 success.then_some(()).ok_or(())
619 }
620
621 pub fn pull_user_permissions(&self) -> Result<(), ()> {
623 self.pull_user_permissions_with_progress(NoProgressCallback)
624 }
625
626 pub fn pull_user_permissions_with_progress<F: ProgressCallback>(
628 &self,
629 mut progress: F,
630 ) -> Result<(), ()> {
631 let success = unsafe {
632 BNRemoteProjectPullUserPermissions(
633 self.handle.as_ptr(),
634 Some(F::cb_progress_callback),
635 &mut progress as *mut F as *mut c_void,
636 )
637 };
638 success.then_some(()).ok_or(())
639 }
640
641 pub fn create_group_permission(
648 &self,
649 group_id: i64,
650 level: CollaborationPermissionLevel,
651 ) -> Result<Ref<Permission>, ()> {
652 self.create_group_permission_with_progress(group_id, level, NoProgressCallback)
653 }
654
655 pub fn create_group_permission_with_progress<F: ProgressCallback>(
663 &self,
664 group_id: i64,
665 level: CollaborationPermissionLevel,
666 mut progress: F,
667 ) -> Result<Ref<Permission>, ()> {
668 let value = unsafe {
669 BNRemoteProjectCreateGroupPermission(
670 self.handle.as_ptr(),
671 group_id,
672 level,
673 Some(F::cb_progress_callback),
674 &mut progress as *mut F as *mut c_void,
675 )
676 };
677
678 NonNull::new(value)
679 .map(|v| unsafe { Permission::ref_from_raw(v) })
680 .ok_or(())
681 }
682
683 pub fn create_user_permission(
690 &self,
691 user_id: &str,
692 level: CollaborationPermissionLevel,
693 ) -> Result<Ref<Permission>, ()> {
694 self.create_user_permission_with_progress(user_id, level, NoProgressCallback)
695 }
696
697 pub fn create_user_permission_with_progress<F: ProgressCallback>(
705 &self,
706 user_id: &str,
707 level: CollaborationPermissionLevel,
708 mut progress: F,
709 ) -> Result<Ref<Permission>, ()> {
710 let user_id = user_id.to_cstr();
711 let value = unsafe {
712 BNRemoteProjectCreateUserPermission(
713 self.handle.as_ptr(),
714 user_id.as_ptr(),
715 level,
716 Some(F::cb_progress_callback),
717 &mut progress as *mut F as *mut c_void,
718 )
719 };
720
721 NonNull::new(value)
722 .map(|v| unsafe { Permission::ref_from_raw(v) })
723 .ok_or(())
724 }
725
726 pub fn push_permission<I>(&self, permission: &Permission, extra_fields: I) -> Result<(), ()>
733 where
734 I: IntoIterator<Item = (String, String)>,
735 {
736 let (keys, values): (Vec<_>, Vec<_>) = extra_fields
737 .into_iter()
738 .map(|(k, v)| (k.to_cstr(), v.to_cstr()))
739 .unzip();
740 let mut keys_raw = keys.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
741 let mut values_raw = values.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
742
743 let success = unsafe {
744 BNRemoteProjectPushPermission(
745 self.handle.as_ptr(),
746 permission.handle.as_ptr(),
747 keys_raw.as_mut_ptr(),
748 values_raw.as_mut_ptr(),
749 keys_raw.len(),
750 )
751 };
752 success.then_some(()).ok_or(())
753 }
754
755 pub fn delete_permission(&self, permission: &Permission) -> Result<(), ()> {
757 let success = unsafe {
758 BNRemoteProjectDeletePermission(self.handle.as_ptr(), permission.handle.as_ptr())
759 };
760 success.then_some(()).ok_or(())
761 }
762
763 pub fn can_user_view(&self, username: &str) -> bool {
769 let username = username.to_cstr();
770 unsafe { BNRemoteProjectCanUserView(self.handle.as_ptr(), username.as_ptr()) }
771 }
772
773 pub fn can_user_edit(&self, username: &str) -> bool {
779 let username = username.to_cstr();
780 unsafe { BNRemoteProjectCanUserEdit(self.handle.as_ptr(), username.as_ptr()) }
781 }
782
783 pub fn can_user_admin(&self, username: &str) -> bool {
789 let username = username.to_cstr();
790 unsafe { BNRemoteProjectCanUserAdmin(self.handle.as_ptr(), username.as_ptr()) }
791 }
792
793 pub fn default_project_path(&self) -> String {
797 let result = unsafe { BNCollaborationDefaultProjectPath(self.handle.as_ptr()) };
798 unsafe { BnString::into_string(result) }
799 }
800
801 pub fn upload_database<C>(
807 &self,
808 metadata: &FileMetadata,
809 parent_folder: Option<&RemoteFolder>,
810 name_changeset: C,
811 ) -> Result<Ref<RemoteFile>, ()>
812 where
813 C: NameChangeset,
814 {
815 if !self.has_pulled_files() {
819 self.pull_files()?;
820 }
821 sync::upload_database(self, parent_folder, metadata, name_changeset)
822 }
823
824 pub fn upload_database_with_progress<C>(
831 &self,
832 metadata: &FileMetadata,
833 parent_folder: Option<&RemoteFolder>,
834 name_changeset: C,
835 progress_function: impl ProgressCallback,
836 ) -> Result<Ref<RemoteFile>, ()>
837 where
838 C: NameChangeset,
839 {
840 sync::upload_database_with_progress(
841 self,
842 parent_folder,
843 metadata,
844 name_changeset,
845 progress_function,
846 )
847 }
848
849 }
872
873impl PartialEq for RemoteProject {
874 fn eq(&self, other: &Self) -> bool {
875 self.id() == other.id()
876 }
877}
878impl Eq for RemoteProject {}
879
880impl ToOwned for RemoteProject {
881 type Owned = Ref<Self>;
882
883 fn to_owned(&self) -> Self::Owned {
884 unsafe { RefCountable::inc_ref(self) }
885 }
886}
887
888unsafe impl RefCountable for RemoteProject {
889 unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
890 Ref::new(Self {
891 handle: NonNull::new(BNNewRemoteProjectReference(handle.handle.as_ptr())).unwrap(),
892 })
893 }
894
895 unsafe fn dec_ref(handle: &Self) {
896 BNFreeRemoteProject(handle.handle.as_ptr());
897 }
898}
899
900impl CoreArrayProvider for RemoteProject {
901 type Raw = *mut BNRemoteProject;
902 type Context = ();
903 type Wrapped<'a> = Guard<'a, Self>;
904}
905
906unsafe impl CoreArrayProviderInner for RemoteProject {
907 unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
908 BNFreeRemoteProjectList(raw, count)
909 }
910
911 unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
912 let raw_ptr = NonNull::new(*raw).unwrap();
913 Guard::new(Self::from_raw(raw_ptr), context)
914 }
915}