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