binaryninja/collaboration/
snapshot.rs

1use std::ffi::c_void;
2use std::ptr::NonNull;
3use std::time::SystemTime;
4
5use super::{sync, Remote, RemoteFile, RemoteProject};
6use crate::binary_view::{BinaryView, BinaryViewExt};
7use crate::collaboration::undo::{RemoteUndoEntry, RemoteUndoEntryId};
8use crate::database::snapshot::Snapshot;
9use crate::progress::{NoProgressCallback, ProgressCallback};
10use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
11use crate::string::{BnString, IntoCStr};
12use binaryninjacore_sys::*;
13
14// TODO: RemoteSnapshotId ?
15
16#[repr(transparent)]
17pub struct RemoteSnapshot {
18    pub(crate) handle: NonNull<BNCollaborationSnapshot>,
19}
20
21impl RemoteSnapshot {
22    pub(crate) unsafe fn from_raw(handle: NonNull<BNCollaborationSnapshot>) -> Self {
23        Self { handle }
24    }
25
26    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNCollaborationSnapshot>) -> Ref<Self> {
27        Ref::new(Self { handle })
28    }
29
30    /// Get the remote snapshot associated with a local snapshot (if it exists)
31    pub fn get_for_local_snapshot(snapshot: &Snapshot) -> Result<Option<Ref<RemoteSnapshot>>, ()> {
32        sync::get_remote_snapshot_from_local(snapshot)
33    }
34
35    /// Owning File
36    pub fn file(&self) -> Result<Ref<RemoteFile>, ()> {
37        let result = unsafe { BNCollaborationSnapshotGetFile(self.handle.as_ptr()) };
38        let raw = NonNull::new(result).ok_or(())?;
39        Ok(unsafe { RemoteFile::ref_from_raw(raw) })
40    }
41
42    /// Owning Project
43    pub fn project(&self) -> Result<Ref<RemoteProject>, ()> {
44        let result = unsafe { BNCollaborationSnapshotGetProject(self.handle.as_ptr()) };
45        let raw = NonNull::new(result).ok_or(())?;
46        Ok(unsafe { RemoteProject::ref_from_raw(raw) })
47    }
48
49    /// Owning Remote
50    pub fn remote(&self) -> Result<Ref<Remote>, ()> {
51        let result = unsafe { BNCollaborationSnapshotGetRemote(self.handle.as_ptr()) };
52        let raw = NonNull::new(result).ok_or(())?;
53        Ok(unsafe { Remote::ref_from_raw(raw) })
54    }
55
56    /// Web api endpoint url
57    pub fn url(&self) -> String {
58        let value = unsafe { BNCollaborationSnapshotGetUrl(self.handle.as_ptr()) };
59        assert!(!value.is_null());
60        unsafe { BnString::into_string(value) }
61    }
62
63    /// Unique id
64    pub fn id(&self) -> String {
65        let value = unsafe { BNCollaborationSnapshotGetId(self.handle.as_ptr()) };
66        assert!(!value.is_null());
67        unsafe { BnString::into_string(value) }
68    }
69
70    /// Name of snapshot
71    pub fn name(&self) -> String {
72        let value = unsafe { BNCollaborationSnapshotGetName(self.handle.as_ptr()) };
73        assert!(!value.is_null());
74        unsafe { BnString::into_string(value) }
75    }
76
77    /// Get the title of a snapshot: the first line of its name
78    pub fn title(&self) -> String {
79        let value = unsafe { BNCollaborationSnapshotGetTitle(self.handle.as_ptr()) };
80        assert!(!value.is_null());
81        unsafe { BnString::into_string(value) }
82    }
83
84    /// Get the description of a snapshot: the lines of its name after the first line
85    pub fn description(&self) -> String {
86        let value = unsafe { BNCollaborationSnapshotGetDescription(self.handle.as_ptr()) };
87        assert!(!value.is_null());
88        unsafe { BnString::into_string(value) }
89    }
90
91    /// Get the user id of the author of a snapshot
92    pub fn author(&self) -> String {
93        let value = unsafe { BNCollaborationSnapshotGetAuthor(self.handle.as_ptr()) };
94        assert!(!value.is_null());
95        unsafe { BnString::into_string(value) }
96    }
97
98    /// Get the username of the author of a snapshot, if possible (vs author which is user id)
99    pub fn author_username(&self) -> String {
100        let value = unsafe { BNCollaborationSnapshotGetAuthorUsername(self.handle.as_ptr()) };
101        assert!(!value.is_null());
102        unsafe { BnString::into_string(value) }
103    }
104
105    /// Created date of Snapshot
106    pub fn created(&self) -> SystemTime {
107        let timestamp = unsafe { BNCollaborationSnapshotGetCreated(self.handle.as_ptr()) };
108        crate::ffi::time_from_bn(timestamp.try_into().unwrap())
109    }
110
111    /// Date of last modification to the snapshot
112    pub fn last_modified(&self) -> SystemTime {
113        let timestamp = unsafe { BNCollaborationSnapshotGetLastModified(self.handle.as_ptr()) };
114        crate::ffi::time_from_bn(timestamp.try_into().unwrap())
115    }
116
117    /// Hash of snapshot data (analysis and markup, etc)
118    /// No specific hash algorithm is guaranteed
119    pub fn hash(&self) -> String {
120        let value = unsafe { BNCollaborationSnapshotGetHash(self.handle.as_ptr()) };
121        assert!(!value.is_null());
122        unsafe { BnString::into_string(value) }
123    }
124
125    /// Hash of file contents in snapshot
126    /// No specific hash algorithm is guaranteed
127    pub fn snapshot_file_hash(&self) -> String {
128        let value = unsafe { BNCollaborationSnapshotGetSnapshotFileHash(self.handle.as_ptr()) };
129        assert!(!value.is_null());
130        unsafe { BnString::into_string(value) }
131    }
132
133    /// If the snapshot has pulled undo entries yet
134    pub fn has_pulled_undo_entries(&self) -> bool {
135        unsafe { BNCollaborationSnapshotHasPulledUndoEntries(self.handle.as_ptr()) }
136    }
137
138    /// If the snapshot has been finalized on the server and is no longer editable
139    pub fn is_finalized(&self) -> bool {
140        unsafe { BNCollaborationSnapshotIsFinalized(self.handle.as_ptr()) }
141    }
142
143    /// List of ids of all remote parent Snapshots
144    pub fn parent_ids(&self) -> Result<Array<BnString>, ()> {
145        let mut count = 0;
146        let raw = unsafe { BNCollaborationSnapshotGetParentIds(self.handle.as_ptr(), &mut count) };
147        (!raw.is_null())
148            .then(|| unsafe { Array::new(raw, count, ()) })
149            .ok_or(())
150    }
151
152    /// List of ids of all remote child Snapshots
153    pub fn child_ids(&self) -> Result<Array<BnString>, ()> {
154        let mut count = 0;
155        let raw = unsafe { BNCollaborationSnapshotGetChildIds(self.handle.as_ptr(), &mut count) };
156        (!raw.is_null())
157            .then(|| unsafe { Array::new(raw, count, ()) })
158            .ok_or(())
159    }
160
161    /// List of all parent Snapshot objects
162    pub fn parents(&self) -> Result<Array<RemoteSnapshot>, ()> {
163        let mut count = 0;
164        let raw = unsafe { BNCollaborationSnapshotGetParents(self.handle.as_ptr(), &mut count) };
165        (!raw.is_null())
166            .then(|| unsafe { Array::new(raw, count, ()) })
167            .ok_or(())
168    }
169
170    /// List of all child Snapshot objects
171    pub fn children(&self) -> Result<Array<RemoteSnapshot>, ()> {
172        let mut count = 0;
173        let raw = unsafe { BNCollaborationSnapshotGetChildren(self.handle.as_ptr(), &mut count) };
174        (!raw.is_null())
175            .then(|| unsafe { Array::new(raw, count, ()) })
176            .ok_or(())
177    }
178
179    /// Get the list of undo entries stored in this snapshot.
180    ///
181    /// NOTE: If undo entries have not been pulled, they will be pulled upon calling this.
182    pub fn undo_entries(&self) -> Result<Array<RemoteUndoEntry>, ()> {
183        if !self.has_pulled_undo_entries() {
184            self.pull_undo_entries()?;
185        }
186        let mut count = 0;
187        let raw =
188            unsafe { BNCollaborationSnapshotGetUndoEntries(self.handle.as_ptr(), &mut count) };
189        (!raw.is_null())
190            .then(|| unsafe { Array::new(raw, count, ()) })
191            .ok_or(())
192    }
193
194    /// Get a specific Undo Entry in the Snapshot by its id
195    ///
196    /// NOTE: If undo entries have not been pulled, they will be pulled upon calling this.
197    pub fn get_undo_entry_by_id(
198        &self,
199        id: RemoteUndoEntryId,
200    ) -> Result<Option<Ref<RemoteUndoEntry>>, ()> {
201        if !self.has_pulled_undo_entries() {
202            self.pull_undo_entries()?;
203        }
204        let raw = unsafe { BNCollaborationSnapshotGetUndoEntryById(self.handle.as_ptr(), id.0) };
205        Ok(NonNull::new(raw).map(|handle| unsafe { RemoteUndoEntry::ref_from_raw(handle) }))
206    }
207
208    /// Pull the list of Undo Entries from the Remote.
209    pub fn pull_undo_entries(&self) -> Result<(), ()> {
210        self.pull_undo_entries_with_progress(NoProgressCallback)
211    }
212
213    /// Pull the list of Undo Entries from the Remote.
214    pub fn pull_undo_entries_with_progress<P: ProgressCallback>(
215        &self,
216        mut progress: P,
217    ) -> Result<(), ()> {
218        let success = unsafe {
219            BNCollaborationSnapshotPullUndoEntries(
220                self.handle.as_ptr(),
221                Some(P::cb_progress_callback),
222                &mut progress as *mut P as *mut c_void,
223            )
224        };
225        success.then_some(()).ok_or(())
226    }
227
228    /// Create a new Undo Entry in this snapshot.
229    pub fn create_undo_entry(
230        &self,
231        parent: Option<u64>,
232        data: &str,
233    ) -> Result<Ref<RemoteUndoEntry>, ()> {
234        let data = data.to_cstr();
235        let value = unsafe {
236            BNCollaborationSnapshotCreateUndoEntry(
237                self.handle.as_ptr(),
238                parent.is_some(),
239                parent.unwrap_or(0),
240                data.as_ptr(),
241            )
242        };
243        let handle = NonNull::new(value).ok_or(())?;
244        Ok(unsafe { RemoteUndoEntry::ref_from_raw(handle) })
245    }
246
247    /// Mark a snapshot as Finalized, committing it to the Remote, preventing future updates,
248    /// and allowing snapshots to be children of it.
249    pub fn finalize(&self) -> Result<(), ()> {
250        let success = unsafe { BNCollaborationSnapshotFinalize(self.handle.as_ptr()) };
251        success.then_some(()).ok_or(())
252    }
253
254    // TODO what kind of struct is this and how to free it?
255    ///// Download the contents of the file in the Snapshot.
256    //pub fn download_snapshot_file<P: ProgressCallback>(
257    //    &self,
258    //    mut progress: P,
259    //) -> Result<BnData, ()> {
260    //    let mut data = ptr::null_mut();
261    //    let mut count = 0;
262    //    let success = unsafe {
263    //        BNCollaborationSnapshotDownloadSnapshotFile(
264    //            self.handle.as_ptr(),
265    //            Some(P::cb_progress_callback),
266    //            &mut progress as *mut P as *mut ffi::c_void,
267    //            &mut data,
268    //            &mut count,
269    //        )
270    //    };
271    //    todo!();
272    //}
273    //
274    /////  Download the snapshot fields blob, compatible with KeyValueStore.
275    //pub fn download<P: ProgressCallback>(
276    //    &self,
277    //    mut progress: P,
278    //) -> Result<BnData, ()> {
279    //    let mut data = ptr::null_mut();
280    //    let mut count = 0;
281    //    let success = unsafe {
282    //        BNCollaborationSnapshotDownload(
283    //            self.handle.as_ptr(),
284    //            Some(P::cb_progress_callback),
285    //            &mut progress as *mut P as *mut ffi::c_void,
286    //            &mut data,
287    //            &mut count,
288    //        )
289    //    };
290    //    todo!();
291    //}
292    //
293    ///// Download the analysis cache fields blob, compatible with KeyValueStore.
294    //pub fn download_analysis_cache<P: ProgressCallback>(
295    //    &self,
296    //    mut progress: P,
297    //) -> Result<BnData, ()> {
298    //    let mut data = ptr::null_mut();
299    //    let mut count = 0;
300    //    let success = unsafe {
301    //        BNCollaborationSnapshotDownloadAnalysisCache(
302    //            self.handle.as_ptr(),
303    //            Some(P::cb_progress_callback),
304    //            &mut progress as *mut P as *mut ffi::c_void,
305    //            &mut data,
306    //            &mut count,
307    //        )
308    //    };
309    //    todo!();
310    //}
311
312    /// Get the local snapshot associated with a remote snapshot (if it exists)
313    pub fn get_local_snapshot(&self, bv: &BinaryView) -> Result<Option<Ref<Snapshot>>, ()> {
314        let Some(db) = bv.file().database() else {
315            return Ok(None);
316        };
317        sync::get_local_snapshot_for_remote(self, &db)
318    }
319
320    pub fn analysis_cache_build_id(&self) -> u64 {
321        unsafe { BNCollaborationSnapshotGetAnalysisCacheBuildId(self.handle.as_ptr()) }
322    }
323}
324
325impl PartialEq for RemoteSnapshot {
326    fn eq(&self, other: &Self) -> bool {
327        self.id() == other.id()
328    }
329}
330impl Eq for RemoteSnapshot {}
331
332impl ToOwned for RemoteSnapshot {
333    type Owned = Ref<Self>;
334
335    fn to_owned(&self) -> Self::Owned {
336        unsafe { RefCountable::inc_ref(self) }
337    }
338}
339
340unsafe impl RefCountable for RemoteSnapshot {
341    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
342        Ref::new(Self {
343            handle: NonNull::new(BNNewCollaborationSnapshotReference(handle.handle.as_ptr()))
344                .unwrap(),
345        })
346    }
347
348    unsafe fn dec_ref(handle: &Self) {
349        BNFreeCollaborationSnapshot(handle.handle.as_ptr());
350    }
351}
352
353impl CoreArrayProvider for RemoteSnapshot {
354    type Raw = *mut BNCollaborationSnapshot;
355    type Context = ();
356    type Wrapped<'a> = Guard<'a, RemoteSnapshot>;
357}
358
359unsafe impl CoreArrayProviderInner for RemoteSnapshot {
360    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
361        BNFreeCollaborationSnapshotList(raw, count)
362    }
363
364    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
365        let raw_ptr = NonNull::new(*raw).unwrap();
366        Guard::new(Self::from_raw(raw_ptr), context)
367    }
368}