diff --git a/rust/src/database.rs b/rust/src/database.rs new file mode 100644 index 000000000..9bbe04611 --- /dev/null +++ b/rust/src/database.rs @@ -0,0 +1,656 @@ +use std::collections::HashMap; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::binaryview::BinaryView; +use crate::databuffer::DataBuffer; +use crate::disassembly::InstructionTextToken; +use crate::filemetadata::FileMetadata; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::{BnStrCompatible, BnString}; + +#[repr(transparent)] +pub struct Database { + handle: ptr::NonNull, +} + +impl Database { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn as_raw(&self) -> &mut BNDatabase { + &mut *self.handle.as_ptr() + } + + /// Get a snapshot by its id, or None if no snapshot with that id exists + pub fn snapshot(&self, id: i64) -> Option { + let result = unsafe { BNGetDatabaseSnapshot(self.as_raw(), id) }; + ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) }) + } + + /// Get a list of all snapshots in the database + pub fn snapshots(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetDatabaseSnapshots(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the current snapshot + pub fn current_snapshot(&self) -> Option { + let result = unsafe { BNGetDatabaseCurrentSnapshot(self.as_raw()) }; + ptr::NonNull::new(result).map(|handle| unsafe { Snapshot::from_raw(handle) }) + } + + pub fn set_current_snapshot(&self, value: &Snapshot) { + unsafe { BNSetDatabaseCurrentSnapshot(self.as_raw(), value.id()) } + } + + pub fn write_snapshot_data( + &self, + parents: &[i64], + file: &BinaryView, + name: N, + data: &KeyValueStore, + auto_save: bool, + ) -> i64 { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { + BNWriteDatabaseSnapshotData( + self.as_raw(), + parents.as_ptr() as *mut _, + parents.len(), + file.handle, + name_ptr, + data.as_raw(), + auto_save, + ptr::null_mut(), + Some(cb_progress_nop), + ) + } + } + + pub fn write_snapshot_data_with_progress( + &self, + parents: &[i64], + file: &BinaryView, + name: N, + data: &KeyValueStore, + auto_save: bool, + mut progress: F, + ) -> i64 + where + N: BnStrCompatible, + F: FnMut(usize, usize) -> bool, + { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + unsafe { + BNWriteDatabaseSnapshotData( + self.as_raw(), + parents.as_ptr() as *mut _, + parents.len(), + file.handle, + name_ptr, + data.as_raw(), + auto_save, + ctxt, + Some(cb_progress::), + ) + } + } + + /// Trim a snapshot's contents in the database by id, but leave the parent/child + /// hierarchy intact. Future references to this snapshot will return False for has_contents + pub fn trim_snapshot(&self, id: i64) -> Result<(), ()> { + if unsafe { BNTrimDatabaseSnapshot(self.as_raw(), id) } { + Ok(()) + } else { + Err(()) + } + } + + /// Remove a snapshot in the database by id, deleting its contents and references. + /// Attempting to remove a snapshot with children will raise an exception. + pub fn remove_snapshot(&self, id: i64) -> Result<(), ()> { + if unsafe { BNRemoveDatabaseSnapshot(self.as_raw(), id) } { + Ok(()) + } else { + Err(()) + } + } + pub fn has_global(&self, key: S) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNDatabaseHasGlobal(self.as_raw(), key_ptr) != 0 } + } + + /// Get a list of keys for all globals in the database + pub fn global_keys(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetDatabaseGlobalKeys(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a dictionary of all globals + pub fn globals(&self) -> HashMap { + self.global_keys() + .iter() + .map(|key| (key.to_string(), self.read_global(key).to_string())) + .collect() + } + + /// Get a specific global by key + pub fn read_global(&self, key: S) -> BnString { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNReadDatabaseGlobal(self.as_raw(), key_ptr) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Write a global into the database + pub fn write_global(&self, key: K, value: V) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let value_raw = value.into_bytes_with_nul(); + let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNWriteDatabaseGlobal(self.as_raw(), key_ptr, value_ptr) } + } + + /// Get a specific global by key, as a binary buffer + pub fn read_global_data(&self, key: S) -> DataBuffer { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNReadDatabaseGlobalData(self.as_raw(), key_ptr) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Write a binary buffer into a global in the database + pub fn write_global_data(&self, key: K, value: &DataBuffer) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNWriteDatabaseGlobalData(self.as_raw(), key_ptr, value.as_raw()) } + } + + /// Get the owning FileMetadata + pub fn file(&self) -> FileMetadata { + let result = unsafe { BNGetDatabaseFile(self.as_raw()) }; + assert!(!result.is_null()); + FileMetadata::from_raw(result) + } + + /// Get the backing analysis cache kvs + pub fn analysis_cache(&self) -> KeyValueStore { + let result = unsafe { BNReadDatabaseAnalysisCache(self.as_raw()) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn reload_connection(&self) { + unsafe { BNDatabaseReloadConnection(self.as_raw()) } + } + + pub fn write_analysis_cache(&self, val: &KeyValueStore) -> Result<(), ()> { + if unsafe { BNWriteDatabaseAnalysisCache(self.as_raw(), val.as_raw()) } { + Ok(()) + } else { + Err(()) + } + } + + pub fn snapshot_has_data(&self, id: i64) -> bool { + unsafe { BNSnapshotHasData(self.as_raw(), id) } + } +} + +impl Clone for Database { + fn clone(&self) -> Self { + unsafe { Self::from_raw(ptr::NonNull::new(BNNewDatabaseReference(self.as_raw())).unwrap()) } + } +} + +impl Drop for Database { + fn drop(&mut self) { + unsafe { BNFreeDatabase(self.as_raw()) } + } +} + +#[repr(transparent)] +pub struct Snapshot { + handle: ptr::NonNull, +} + +impl Snapshot { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNSnapshot) -> &Self { + mem::transmute(handle) + } + + pub(crate) unsafe fn as_raw(&self) -> &mut BNSnapshot { + &mut *self.handle.as_ptr() + } + + /// Get the owning database + pub fn database(&self) -> Database { + unsafe { + Database::from_raw(ptr::NonNull::new(BNGetSnapshotDatabase(self.as_raw())).unwrap()) + } + } + + /// Get the numerical id (read-only) + pub fn id(&self) -> i64 { + unsafe { BNGetSnapshotId(self.as_raw()) } + } + + /// Get the displayed snapshot name + pub fn name(&self) -> BnString { + unsafe { BnString::from_raw(BNGetSnapshotName(self.as_raw())) } + } + + /// Set the displayed snapshot name + pub fn set_name(&self, value: S) { + let value_raw = value.into_bytes_with_nul(); + let value_ptr = value_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNSetSnapshotName(self.as_raw(), value_ptr) } + } + + /// If the snapshot was the result of an auto-save + pub fn is_auto_save(&self) -> bool { + unsafe { BNIsSnapshotAutoSave(self.as_raw()) } + } + + /// If the snapshot has contents, and has not been trimmed + pub fn has_contents(&self) -> bool { + unsafe { BNSnapshotHasContents(self.as_raw()) } + } + + /// If the snapshot has undo data + pub fn has_undo(&self) -> bool { + unsafe { BNSnapshotHasUndo(self.as_raw()) } + } + + /// Get the first parent of the snapshot, or None if it has no parents + pub fn first_parent(&self) -> Option { + let result = unsafe { BNGetSnapshotFirstParent(self.as_raw()) }; + ptr::NonNull::new(result).map(|s| unsafe { Snapshot::from_raw(s) }) + } + + /// Get a list of all parent snapshots of the snapshot + pub fn parents(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetSnapshotParents(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all child snapshots of the snapshot + pub fn children(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetSnapshotChildren(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a buffer of the raw data at the time of the snapshot + pub fn file_contents(&self) -> Option { + self.has_contents().then(|| unsafe { + let result = BNGetSnapshotFileContents(self.as_raw()); + assert!(!result.is_null()); + DataBuffer::from_raw(result) + }) + } + + /// Get a hash of the data at the time of the snapshot + pub fn file_contents_hash(&self) -> Option { + self.has_contents().then(|| unsafe { + let result = BNGetSnapshotFileContentsHash(self.as_raw()); + assert!(!result.is_null()); + DataBuffer::from_raw(result) + }) + } + + /// Get a list of undo entries at the time of the snapshot + pub fn undo_entries(&self) -> Array { + assert!(self.has_undo()); + let mut count = 0; + let result = unsafe { BNGetSnapshotUndoEntries(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn undo_entries_with_progress bool>( + &self, + mut progress: F, + ) -> Array { + assert!(self.has_undo()); + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + let mut count = 0; + let result = unsafe { + BNGetSnapshotUndoEntriesWithProgress( + self.as_raw(), + ctxt, + Some(cb_progress::), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the backing kvs data with snapshot fields + pub fn read_data(&self) -> KeyValueStore { + let result = unsafe { BNReadSnapshotData(self.as_raw()) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn read_data_with_progress bool>( + &self, + mut progress: F, + ) -> KeyValueStore { + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + let result = + unsafe { BNReadSnapshotDataWithProgress(self.as_raw(), ctxt, Some(cb_progress::)) }; + unsafe { KeyValueStore::from_raw(ptr::NonNull::new(result).unwrap()) } + } + + pub fn undo_data(&self) -> DataBuffer { + let result = unsafe { BNGetSnapshotUndoData(self.as_raw()) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + pub fn store_data bool>( + &self, + data: KeyValueStore, + mut progress: F, + ) -> bool { + let ctxt = &mut progress as *mut _ as *mut ffi::c_void; + unsafe { BNSnapshotStoreData(self.as_raw(), data.as_raw(), ctxt, Some(cb_progress::)) } + } + + /// Determine if this snapshot has another as an ancestor + pub fn has_ancestor(self, other: &Snapshot) -> bool { + unsafe { BNSnapshotHasAncestor(self.as_raw(), other.as_raw()) } + } +} + +impl Clone for Snapshot { + fn clone(&self) -> Self { + unsafe { Self::from_raw(ptr::NonNull::new(BNNewSnapshotReference(self.as_raw())).unwrap()) } + } +} + +impl Drop for Snapshot { + fn drop(&mut self) { + unsafe { BNFreeSnapshot(self.as_raw()) } + } +} + +impl CoreArrayProvider for Snapshot { + type Raw = *mut BNSnapshot; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for Snapshot { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeSnapshotList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +#[repr(transparent)] +pub struct KeyValueStore { + handle: ptr::NonNull, +} + +impl KeyValueStore { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNKeyValueStore) -> &Self { + mem::transmute(handle) + } + + pub(crate) unsafe fn as_raw(&self) -> &mut BNKeyValueStore { + &mut *self.handle.as_ptr() + } + + /// Get a list of all keys stored in the kvs + pub fn keys(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetKeyValueStoreKeys(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the value for a single key + pub fn value(&self, key: S) -> DataBuffer { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + let result = unsafe { BNGetKeyValueStoreBuffer(self.as_raw(), key_ptr) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Set the value for a single key + pub fn set_value(&self, key: S, value: &DataBuffer) -> bool { + let key_raw = key.into_bytes_with_nul(); + let key_ptr = key_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNSetKeyValueStoreBuffer(self.as_raw(), key_ptr, value.as_raw()) } + } + + /// Get the stored representation of the kvs + pub fn serialized_data(&self) -> DataBuffer { + let result = unsafe { BNGetKeyValueStoreSerializedData(self.as_raw()) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Begin storing new keys into a namespace + pub fn begin_namespace(&self, name: S) { + let name_raw = name.into_bytes_with_nul(); + let name_ptr = name_raw.as_ref().as_ptr() as *const ffi::c_char; + unsafe { BNBeginKeyValueStoreNamespace(self.as_raw(), name_ptr) } + } + + /// End storing new keys into a namespace + pub fn end_namespace(&self) { + unsafe { BNEndKeyValueStoreNamespace(self.as_raw()) } + } + + /// If the kvs is empty + pub fn empty(&self) -> bool { + unsafe { BNIsKeyValueStoreEmpty(self.as_raw()) } + } + + /// Number of values in the kvs + pub fn value_size(&self) -> usize { + unsafe { BNGetKeyValueStoreValueSize(self.as_raw()) } + } + + /// Length of serialized data + pub fn data_size(&self) -> usize { + unsafe { BNGetKeyValueStoreDataSize(self.as_raw()) } + } + + /// Size of all data in storage + pub fn value_storage_size(self) -> usize { + unsafe { BNGetKeyValueStoreValueStorageSize(self.as_raw()) } + } + + /// Number of namespaces pushed with begin_namespace + pub fn namespace_size(self) -> usize { + unsafe { BNGetKeyValueStoreNamespaceSize(self.as_raw()) } + } +} + +impl Clone for KeyValueStore { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewKeyValueStoreReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for KeyValueStore { + fn drop(&mut self) { + unsafe { BNFreeKeyValueStore(self.as_raw()) } + } +} + +#[repr(transparent)] +pub struct UndoEntry { + handle: ptr::NonNull, +} + +impl UndoEntry { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoEntry) -> &Self { + mem::transmute(handle) + } + + pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoEntry { + &mut *self.handle.as_ptr() + } + + pub fn id(&self) -> BnString { + let result = unsafe { BNUndoEntryGetId(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn actions(&self) -> Array { + let mut count = 0; + let result = unsafe { BNUndoEntryGetActions(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn time(&self) -> SystemTime { + let m = Duration::from_secs(unsafe { BNUndoEntryGetTimestamp(self.as_raw()) }); + UNIX_EPOCH + m + } +} + +impl Clone for UndoEntry { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewUndoEntryReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for UndoEntry { + fn drop(&mut self) { + unsafe { BNFreeUndoEntry(self.as_raw()) } + } +} + +impl CoreArrayProvider for UndoEntry { + type Raw = *mut BNUndoEntry; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UndoEntry { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUndoEntryList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +#[repr(transparent)] +pub struct UndoAction { + handle: ptr::NonNull, +} + +impl UndoAction { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNUndoAction) -> &Self { + mem::transmute(handle) + } + + pub(crate) unsafe fn as_raw(&self) -> &mut BNUndoAction { + &mut *self.handle.as_ptr() + } + + pub fn summary_text(&self) -> BnString { + let result = unsafe { BNUndoActionGetSummaryText(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn summary(&self) -> Array { + let mut count = 0; + let result = unsafe { BNUndoActionGetSummary(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } +} + +impl Clone for UndoAction { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewUndoActionReference(self.as_raw())).unwrap()) + } + } +} + +impl Drop for UndoAction { + fn drop(&mut self) { + unsafe { BNFreeUndoAction(self.as_raw()) } + } +} + +impl CoreArrayProvider for UndoAction { + type Raw = *mut BNUndoAction; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for UndoAction { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeUndoActionList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +unsafe extern "C" fn cb_progress bool>( + ctxt: *mut ffi::c_void, + arg1: usize, + arg2: usize, +) -> bool { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(arg1, arg2) +} + +unsafe extern "C" fn cb_progress_nop(_ctxt: *mut ffi::c_void, _arg1: usize, _arg2: usize) -> bool { + true +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e24a3f784..7f3a773fd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -133,6 +133,7 @@ pub mod binarywriter; pub mod callingconvention; pub mod command; pub mod custombinaryview; +pub mod database; pub mod databuffer; pub mod debuginfo; pub mod demangle;