binaryninja/
database.rs

1pub mod kvs;
2pub mod snapshot;
3pub mod undo;
4
5use binaryninjacore_sys::*;
6use std::collections::HashMap;
7use std::ffi::c_void;
8use std::fmt::Debug;
9use std::path::Path;
10use std::ptr::NonNull;
11
12use crate::binary_view::BinaryView;
13use crate::data_buffer::DataBuffer;
14use crate::database::kvs::KeyValueStore;
15use crate::database::snapshot::{Snapshot, SnapshotId};
16use crate::progress::{NoProgressCallback, ProgressCallback};
17use crate::rc::{Array, Ref, RefCountable};
18use crate::string::{BnString, IntoCStr};
19
20pub struct Database {
21    pub(crate) handle: NonNull<BNDatabase>,
22}
23
24impl Database {
25    pub(crate) unsafe fn from_raw(handle: NonNull<BNDatabase>) -> Self {
26        Self { handle }
27    }
28
29    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNDatabase>) -> Ref<Self> {
30        Ref::new(Self { handle })
31    }
32
33    /// Open a database with the given file path
34    pub fn open_existing(path: impl AsRef<Path>) -> Result<Ref<Self>, ()> {
35        let db = unsafe { Self::ref_from_raw(NonNull::new(BNCreateDatabaseInstance()).ok_or(())?) };
36        let path_raw = path.as_ref().to_cstr();
37        if unsafe { BNDatabaseOpenExisting(db.handle.as_ptr(), path_raw.as_ptr()) } {
38            Ok(db)
39        } else {
40            Err(())
41        }
42    }
43
44    /// Get a [`Snapshot`] by its `id`, or `None` if no snapshot with that `id` exists.
45    pub fn snapshot_by_id(&self, id: SnapshotId) -> Option<Ref<Snapshot>> {
46        let result = unsafe { BNGetDatabaseSnapshot(self.handle.as_ptr(), id.0) };
47        NonNull::new(result).map(|handle| unsafe { Snapshot::ref_from_raw(handle) })
48    }
49
50    /// Get a list of all snapshots in the database
51    pub fn snapshots(&self) -> Array<Snapshot> {
52        let mut count = 0;
53        let result = unsafe { BNGetDatabaseSnapshots(self.handle.as_ptr(), &mut count) };
54        assert!(!result.is_null());
55        unsafe { Array::new(result, count, ()) }
56    }
57
58    /// Get the current snapshot
59    pub fn current_snapshot(&self) -> Option<Ref<Snapshot>> {
60        let result = unsafe { BNGetDatabaseCurrentSnapshot(self.handle.as_ptr()) };
61        NonNull::new(result).map(|handle| unsafe { Snapshot::ref_from_raw(handle) })
62    }
63
64    /// Equivalent to [`Self::set_current_snapshot_id`].
65    pub fn set_current_snapshot(&self, value: &Snapshot) {
66        self.set_current_snapshot_id(value.id())
67    }
68
69    /// Sets the current snapshot to the [`SnapshotId`].
70    ///
71    /// **No** validation is done to ensure that the id is valid.
72    pub fn set_current_snapshot_id(&self, id: SnapshotId) {
73        unsafe { BNSetDatabaseCurrentSnapshot(self.handle.as_ptr(), id.0) }
74    }
75
76    pub fn write_snapshot_data(
77        &self,
78        parents: &[SnapshotId],
79        file: &BinaryView,
80        name: &str,
81        data: &KeyValueStore,
82        auto_save: bool,
83    ) -> SnapshotId {
84        self.write_snapshot_data_with_progress(
85            parents,
86            file,
87            name,
88            data,
89            auto_save,
90            NoProgressCallback,
91        )
92    }
93
94    pub fn write_snapshot_data_with_progress<P>(
95        &self,
96        parents: &[SnapshotId],
97        file: &BinaryView,
98        name: &str,
99        data: &KeyValueStore,
100        auto_save: bool,
101        mut progress: P,
102    ) -> SnapshotId
103    where
104        P: ProgressCallback,
105    {
106        let name_raw = name.to_cstr();
107        let name_ptr = name_raw.as_ptr();
108
109        let new_id = unsafe {
110            BNWriteDatabaseSnapshotData(
111                self.handle.as_ptr(),
112                // SAFETY: SnapshotId is just i64
113                parents.as_ptr() as *mut _,
114                parents.len(),
115                file.handle,
116                name_ptr,
117                data.handle.as_ptr(),
118                auto_save,
119                &mut progress as *mut P as *mut c_void,
120                Some(P::cb_progress_callback),
121            )
122        };
123
124        SnapshotId(new_id)
125    }
126
127    /// Trim a snapshot's contents in the database but leave the parent/child hierarchy intact.
128    ///
129    /// NOTE: Future references to this snapshot will return `false` for [`Database::snapshot_has_data`]
130    pub fn trim_snapshot(&self, id: SnapshotId) -> Result<(), ()> {
131        if unsafe { BNTrimDatabaseSnapshot(self.handle.as_ptr(), id.0) } {
132            Ok(())
133        } else {
134            Err(())
135        }
136    }
137
138    /// Remove a snapshot in the database by id, deleting its contents and references.
139    /// Attempting to remove a snapshot with children will raise an exception.
140    pub fn remove_snapshot(&self, id: SnapshotId) -> Result<(), ()> {
141        if unsafe { BNRemoveDatabaseSnapshot(self.handle.as_ptr(), id.0) } {
142            Ok(())
143        } else {
144            Err(())
145        }
146    }
147    pub fn has_global(&self, key: &str) -> bool {
148        let key_raw = key.to_cstr();
149        unsafe { BNDatabaseHasGlobal(self.handle.as_ptr(), key_raw.as_ptr()) != 0 }
150    }
151
152    /// Get a list of keys for all globals in the database
153    pub fn global_keys(&self) -> Array<BnString> {
154        let mut count = 0;
155        let result = unsafe { BNGetDatabaseGlobalKeys(self.handle.as_ptr(), &mut count) };
156        assert!(!result.is_null());
157        unsafe { Array::new(result, count, ()) }
158    }
159
160    /// Get a dictionary of all globals
161    pub fn globals(&self) -> HashMap<String, BnString> {
162        self.global_keys()
163            .iter()
164            .filter_map(|key| Some((key.to_string(), self.read_global(key)?)))
165            .collect()
166    }
167
168    /// Get a specific global by key
169    pub fn read_global(&self, key: &str) -> Option<BnString> {
170        let key_raw = key.to_cstr();
171        let result = unsafe { BNReadDatabaseGlobal(self.handle.as_ptr(), key_raw.as_ptr()) };
172        unsafe { NonNull::new(result).map(|_| BnString::from_raw(result)) }
173    }
174
175    /// Write a global into the database
176    pub fn write_global(&self, key: &str, value: &str) -> bool {
177        let key_raw = key.to_cstr();
178        let value_raw = value.to_cstr();
179        unsafe { BNWriteDatabaseGlobal(self.handle.as_ptr(), key_raw.as_ptr(), value_raw.as_ptr()) }
180    }
181
182    /// Get a specific global by key, as a binary buffer
183    pub fn read_global_data(&self, key: &str) -> Option<DataBuffer> {
184        let key_raw = key.to_cstr();
185        let result = unsafe { BNReadDatabaseGlobalData(self.handle.as_ptr(), key_raw.as_ptr()) };
186        NonNull::new(result).map(|_| DataBuffer::from_raw(result))
187    }
188
189    /// Write a binary buffer into a global in the database
190    pub fn write_global_data(&self, key: &str, value: &DataBuffer) -> bool {
191        let key_raw = key.to_cstr();
192        unsafe { BNWriteDatabaseGlobalData(self.handle.as_ptr(), key_raw.as_ptr(), value.as_raw()) }
193    }
194
195    /// Get the backing analysis cache kvs
196    pub fn analysis_cache(&self) -> Ref<KeyValueStore> {
197        let result = unsafe { BNReadDatabaseAnalysisCache(self.handle.as_ptr()) };
198        unsafe { KeyValueStore::ref_from_raw(NonNull::new(result).unwrap()) }
199    }
200
201    #[deprecated(note = "Use crate::file_metadata::FileMetadata::reopen_moved_database instead")]
202    /// Closes then reopens the database.
203    pub fn reload_connection(&self) {}
204
205    pub fn write_analysis_cache(&self, val: &KeyValueStore) -> Result<(), ()> {
206        if unsafe { BNWriteDatabaseAnalysisCache(self.handle.as_ptr(), val.handle.as_ptr()) } {
207            Ok(())
208        } else {
209            Err(())
210        }
211    }
212
213    pub fn snapshot_has_data(&self, id: SnapshotId) -> bool {
214        unsafe { BNSnapshotHasData(self.handle.as_ptr(), id.0) }
215    }
216}
217
218impl Debug for Database {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        f.debug_struct("Database")
221            .field("current_snapshot", &self.current_snapshot())
222            .field("snapshot_count", &self.snapshots().len())
223            .field("globals", &self.globals())
224            .field("analysis_cache", &self.analysis_cache())
225            .finish()
226    }
227}
228
229impl ToOwned for Database {
230    type Owned = Ref<Self>;
231
232    fn to_owned(&self) -> Self::Owned {
233        unsafe { RefCountable::inc_ref(self) }
234    }
235}
236
237unsafe impl RefCountable for Database {
238    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
239        Ref::new(Self {
240            handle: NonNull::new(BNNewDatabaseReference(handle.handle.as_ptr())).unwrap(),
241        })
242    }
243
244    unsafe fn dec_ref(handle: &Self) {
245        BNFreeDatabase(handle.handle.as_ptr());
246    }
247}