From 64a04fb380ac2e45628c52f47300d7fd32b02590 Mon Sep 17 00:00:00 2001 From: Rubens Brandao <git@rubens.io> Date: Tue, 18 Jun 2024 09:43:55 -0300 Subject: [PATCH] implement rust TypeArchive --- rust/src/lib.rs | 1 + rust/src/typearchive.rs | 947 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 948 insertions(+) create mode 100644 rust/src/typearchive.rs diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e24a3f784..e66e1c7fa 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -162,6 +162,7 @@ pub mod string; pub mod symbol; pub mod tags; pub mod templatesimplifier; +pub mod typearchive; pub mod types; use std::path::PathBuf; diff --git a/rust/src/typearchive.rs b/rust/src/typearchive.rs new file mode 100644 index 000000000..71a7058e9 --- /dev/null +++ b/rust/src/typearchive.rs @@ -0,0 +1,947 @@ +use core::{ffi, mem, ptr}; + +use binaryninjacore_sys::*; + +use crate::databuffer::DataBuffer; +use crate::metadata::Metadata; +use crate::platform::Platform; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{BnStrCompatible, BnString}; +use crate::types::{QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type}; + +/// Type Archives are a collection of types which can be shared between different analysis +/// sessions and are backed by a database file on disk. Their types can be modified, and +/// a history of previous versions of types is stored in snapshots in the archive. +#[repr(transparent)] +pub struct TypeArchive { + handle: ptr::NonNull<BNTypeArchive>, +} + +impl Drop for TypeArchive { + fn drop(&mut self) { + unsafe { BNFreeTypeArchiveReference(self.as_raw()) } + } +} + +impl Clone for TypeArchive { + fn clone(&self) -> Self { + unsafe { + Self::from_raw(ptr::NonNull::new(BNNewTypeArchiveReference(self.as_raw())).unwrap()) + } + } +} + +impl PartialEq for TypeArchive { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} +impl Eq for TypeArchive {} + +impl core::hash::Hash for TypeArchive { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + (self.handle.as_ptr() as usize).hash(state); + } +} + +impl core::fmt::Debug for TypeArchive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let path = self.path().map(|x| x.to_string()); + f.debug_struct("TypeArchive").field("path", &path).finish() + } +} + +impl TypeArchive { + pub(crate) unsafe fn from_raw(handle: ptr::NonNull<BNTypeArchive>) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypeArchive) -> &Self { + assert!(!handle.is_null()); + mem::transmute(handle) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeArchive { + &mut *self.handle.as_ptr() + } + + /// Open the Type Archive at the given path, if it exists. + pub fn open<S: BnStrCompatible>(path: S) -> Option<TypeArchive> { + let path = path.into_bytes_with_nul(); + let handle = unsafe { BNOpenTypeArchive(path.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Create a Type Archive at the given path, returning None if it could not be created. + pub fn create<S: BnStrCompatible>(path: S, platform: &Platform) -> Option<TypeArchive> { + let path = path.into_bytes_with_nul(); + let handle = unsafe { + BNCreateTypeArchive( + path.as_ref().as_ptr() as *const ffi::c_char, + platform.handle, + ) + }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Create a Type Archive at the given path and id, returning None if it could not be created. + pub fn create_with_id<P: BnStrCompatible, I: BnStrCompatible>( + path: P, + id: I, + platform: &Platform, + ) -> Option<TypeArchive> { + let path = path.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let handle = unsafe { + BNCreateTypeArchiveWithId( + path.as_ref().as_ptr() as *const ffi::c_char, + platform.handle, + id.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Get a reference to the Type Archive with the known id, if one exists. + pub fn lookup_by_id<S: BnStrCompatible>(id: S) -> Option<TypeArchive> { + let id = id.into_bytes_with_nul(); + let handle = unsafe { BNLookupTypeArchiveById(id.as_ref().as_ptr() as *const ffi::c_char) }; + ptr::NonNull::new(handle).map(|handle| unsafe { TypeArchive::from_raw(handle) }) + } + + /// Get the path to the Type Archive's file + pub fn path(&self) -> Option<BnString> { + let result = unsafe { BNGetTypeArchivePath(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Get the guid for a Type Archive + pub fn id(&self) -> Option<BnString> { + let result = unsafe { BNGetTypeArchiveId(self.as_raw()) }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Get the associated Platform for a Type Archive + pub fn platform(&self) -> Ref<Platform> { + let result = unsafe { BNGetTypeArchivePlatform(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { Platform::ref_from_raw(result) } + } + + /// Get the id of the current snapshot in the type archive + pub fn current_snapshot_id(&self) -> BnString { + let result = unsafe { BNGetTypeArchiveCurrentSnapshotId(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Revert the type archive's current snapshot to the given snapshot + pub fn set_current_snapshot_id<S: BnStrCompatible>(&self, id: S) { + let id = id.into_bytes_with_nul(); + unsafe { + BNSetTypeArchiveCurrentSnapshot( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + ) + } + } + + /// Get a list of every snapshot's id + pub fn all_snapshot_ids(&self) -> Array<BnString> { + let mut count = 0; + let result = unsafe { BNGetTypeArchiveAllSnapshotIds(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the ids of the parents to the given snapshot + pub fn get_snapshot_parent_ids<S: BnStrCompatible>( + &self, + snapshot: S, + ) -> Option<Array<BnString>> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveSnapshotParentIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Get the ids of the children to the given snapshot + pub fn get_snapshot_child_ids<S: BnStrCompatible>( + &self, + snapshot: S, + ) -> Option<Array<BnString>> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveSnapshotChildIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Add named types to the type archive. Type must have all dependant named types added + /// prior to being added, or this function will fail. + /// If the type already exists, it will be overwritten. + /// + /// * `name` - Name of new type + /// * `type` - Definition of new type + pub fn add_type(&self, name: &QualifiedNameAndType) { + self.add_types(core::slice::from_ref(name)) + } + + /// Add named types to the type archive. Types must have all dependant named + /// types prior to being added, or included in the list, or this function will fail. + /// Types already existing with any added names will be overwritten. + /// + /// * `new_types` - Names and definitions of new types + pub fn add_types(&self, new_types: &[QualifiedNameAndType]) { + // SAFETY BNQualifiedNameAndType and QualifiedNameAndType are transparent + let new_types_raw: &[BNQualifiedNameAndType] = unsafe { mem::transmute(new_types) }; + let result = unsafe { + BNAddTypeArchiveTypes(self.as_raw(), new_types_raw.as_ptr(), new_types.len()) + }; + assert!(result); + } + + /// Change the name of an existing type in the type archive. + /// + /// * `old_name` - Old type name in archive + /// * `new_name` - New type name + pub fn rename_type(&self, old_name: &QualifiedName, new_name: &QualifiedNameAndType) { + let id = self + .get_type_id(old_name, self.current_snapshot_id()) + .unwrap(); + return self.rename_type_by_id(id, new_name.name()); + } + + /// Change the name of an existing type in the type archive. + /// + /// * `id` - Old id of type in archive + /// * `new_name` - New type name + pub fn rename_type_by_id<S: BnStrCompatible>(&self, id: S, new_name: &QualifiedName) { + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNRenameTypeArchiveType( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + &new_name.0, + ) + }; + assert!(result); + } + + /// Delete an existing type in the type archive. + pub fn delete_type(&self, name: &QualifiedName) { + let id = self.get_type_id(name, self.current_snapshot_id()); + let Some(id) = id else { + panic!("Unknown type {}", name.string()) + }; + self.delete_type_by_id(id); + } + + /// Delete an existing type in the type archive. + pub fn delete_type_by_id<S: BnStrCompatible>(&self, id: S) { + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNDeleteTypeArchiveType(self.as_raw(), id.as_ref().as_ptr() as *const ffi::c_char) + }; + assert!(result); + } + + /// Retrieve a stored type in the archive + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_name<S: BnStrCompatible>( + &self, + name: &QualifiedName, + snapshot: S, + ) -> Option<Ref<Type>> { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeByName( + self.as_raw(), + &name.0, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a stored type in the archive by id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_id<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> Option<Ref<Type>> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeById( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a type's name by its id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_name_by_id<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> QualifiedName { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeName( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + QualifiedName(result) + } + + /// Retrieve a type's id by its name + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_id<S: BnStrCompatible>( + &self, + name: &QualifiedName, + snapshot: S, + ) -> Option<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNGetTypeArchiveTypeId( + self.as_raw(), + &name.0, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + (!result.is_null()).then(|| unsafe { BnString::from_raw(result) }) + } + + /// Retrieve all stored types in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_types_and_ids<S: BnStrCompatible>( + &self, + snapshot: S, + ) -> Array<QualifiedNameTypeAndId> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypes( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' ids in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_ids<S: BnStrCompatible>(&self, snapshot: S) -> Array<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypeIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names<S: BnStrCompatible>(&self, snapshot: S) -> Array<QualifiedName> { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveTypeNames( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names and ids in the archive at a current snapshot + + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names_and_ids<S: BnStrCompatible>( + &self, + snapshot: S, + ) -> (Array<QualifiedName>, Array<BnString>) { + let snapshot = snapshot.into_bytes_with_nul(); + let mut count = 0; + let mut names = ptr::null_mut(); + let mut ids = ptr::null_mut(); + let result = unsafe { + BNGetTypeArchiveTypeNamesAndIds( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut names, + &mut ids, + &mut count, + ) + }; + assert!(result); + (unsafe { Array::new(names, count, ()) }, unsafe { + Array::new(ids, count, ()) + }) + } + + /// Get all types a given type references directly + /// + /// * `id` - Source type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_outgoing_direct_references<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> Array<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingDirectTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types a given type references, and any types that the referenced types reference + /// + /// :param id: Source type id + /// :param snapshot: Snapshot id to search for types + pub fn get_outgoing_recursive_references<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> Array<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingRecursiveTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_incoming_direct_references<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> Array<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingDirectTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type, and all types that reference them, recursively + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types, or empty string to search the latest snapshot + pub fn get_incoming_recursive_references<I: BnStrCompatible, S: BnStrCompatible>( + &self, + id: I, + snapshot: S, + ) -> Array<BnString> { + let snapshot = snapshot.into_bytes_with_nul(); + let id = id.into_bytes_with_nul(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingRecursiveTypeReferences( + self.as_raw(), + id.as_ref().as_ptr() as *const ffi::c_char, + snapshot.as_ref().as_ptr() as *const ffi::c_char, + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Look up a metadata entry in the archive + pub fn query_metadata<S: BnStrCompatible>(&self, key: S) -> Option<Ref<Metadata>> { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveQueryMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + }; + (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) + } + + /// Store a key/value pair in the archive's metadata storage + /// + /// * `key` - key value to associate the Metadata object with + /// * `md` - object to store. + pub fn store_metadata<S: BnStrCompatible>(&self, key: S, md: &Metadata) { + let key = key.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveStoreMetadata( + self.as_raw(), + key.as_ref().as_ptr() as *const ffi::c_char, + md.handle, + ) + }; + assert!(result); + } + + /// Delete a given metadata entry in the archive from the `key` + pub fn remove_metadata<S: BnStrCompatible>(&self, key: S) -> bool { + let key = key.into_bytes_with_nul(); + unsafe { + BNTypeArchiveRemoveMetadata(self.as_raw(), key.as_ref().as_ptr() as *const ffi::c_char) + } + } + + /// Turn a given `snapshot` id into a data stream + pub fn serialize_snapshot<S: BnStrCompatible>(&self, snapshot: S) -> DataBuffer { + let snapshot = snapshot.into_bytes_with_nul(); + let result = unsafe { + BNTypeArchiveSerializeSnapshot( + self.as_raw(), + snapshot.as_ref().as_ptr() as *const ffi::c_char, + ) + }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Take a serialized snapshot `data` stream and create a new snapshot from it + pub fn deserialize_snapshot(&self, data: &DataBuffer) -> BnString { + let result = unsafe { BNTypeArchiveDeserializeSnapshot(self.as_raw(), data.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Register a notification listener + pub fn register_notification_callback<T: TypeArchiveNotificationCallback>( + &self, + callback: T, + ) -> TypeArchiveCallbackHandle<T> { + // SAFETY free on [TypeArchiveCallbackHandle::Drop] + let callback = Box::leak(Box::new(callback)); + let mut notification = BNTypeArchiveNotification { + context: callback as *mut T as *mut ffi::c_void, + typeAdded: Some(cb_type_added::<T>), + typeUpdated: Some(cb_type_updated::<T>), + typeRenamed: Some(cb_type_renamed::<T>), + typeDeleted: Some(cb_type_deleted::<T>), + }; + unsafe { BNRegisterTypeArchiveNotification(self.as_raw(), &mut notification) } + TypeArchiveCallbackHandle { + callback, + type_archive: self.clone(), + } + } + + // NOTE NotificationClosure is left private, there is no need for the user + // to know or use it. + #[allow(private_interfaces)] + pub fn register_notification_closure<A, U, R, D>( + &self, + type_added: A, + type_updated: U, + type_renamed: R, + type_deleted: D, + ) -> TypeArchiveCallbackHandle<NotificationClosure<A, U, R, D>> + where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), + { + self.register_notification_callback(NotificationClosure { + fun_type_added: type_added, + fun_type_updated: type_updated, + fun_type_renamed: type_renamed, + fun_type_deleted: type_deleted, + }) + } + + /// Close a type archive, disconnecting it from any active views and closing + /// any open file handles + pub fn close(self) { + unsafe { BNCloseTypeArchive(self.as_raw()) } + // NOTE self must be dropped after, don't make it `&self` + } + + /// Determine if `file` is a Type Archive + pub fn is_type_archive<P: BnStrCompatible>(file: P) -> bool { + let file = file.into_bytes_with_nul(); + unsafe { BNIsTypeArchive(file.as_ref().as_ptr() as *const ffi::c_char) } + } + + // TODO implement TypeContainer + ///// Get the TypeContainer interface for this Type Archive, presenting types + ///// at the current snapshot in the archive. + //pub fn type_container(&self) -> TypeContainer { + // let result = unsafe { BNGetTypeArchiveTypeContainer(self.as_raw()) }; + // unsafe { TypeContainer::from_raw(ptr::NonNull::new(result).unwrap()) } + //} + + /// Do some function in a transaction making a new snapshot whose id is passed to func. If func throws, + /// the transaction will be rolled back and the snapshot will not be created. + /// + /// * `func` - Function to call + /// * `parents` - Parent snapshot ids + /// + /// Returns Created snapshot id + pub fn new_snapshot_transaction<P, F>(&self, mut function: F, parents: &[BnString]) -> BnString + where + P: BnStrCompatible, + F: FnMut(&str) -> bool, + { + unsafe extern "C" fn cb_callback<F: FnMut(&str) -> bool>( + ctxt: *mut ffi::c_void, + id: *const ffi::c_char, + ) -> bool { + let fun: &mut F = &mut *(ctxt as *mut F); + fun(&ffi::CStr::from_ptr(id).to_string_lossy()) + } + + // SAFETY BnString and `*const ffi::c_char` are transparent + let parents_raw = parents.as_ptr() as *const *const ffi::c_char; + + let result = unsafe { + BNTypeArchiveNewSnapshotTransaction( + self.as_raw(), + Some(cb_callback::<F>), + &mut function as *mut F as *mut ffi::c_void, + parents_raw, + parents.len(), + ) + }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Merge two snapshots in the archive to produce a new snapshot + /// + /// * `base_snapshot` - Common ancestor of snapshots + /// * `first_snapshot` - First snapshot to merge + /// * `second_snapshot` - Second snapshot to merge + /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot + /// * `progress` - Function to call for progress updates + /// + /// Returns Snapshot id, if merge was successful, otherwise the List of + /// conflicting type ids + pub fn merge_snapshots<B, F, S, P, M, MI, MK>( + &self, + base_snapshot: B, + first_snapshot: F, + second_snapshot: S, + merge_conflicts: M, + mut progress: P, + ) -> Result<BnString, Array<BnString>> + where + B: BnStrCompatible, + F: BnStrCompatible, + S: BnStrCompatible, + P: FnMut(usize, usize) -> bool, + M: IntoIterator<Item = (MI, MK)>, + MI: BnStrCompatible, + MK: BnStrCompatible, + { + unsafe extern "C" fn cb_callback<F: FnMut(usize, usize) -> bool>( + ctxt: *mut ffi::c_void, + progress: usize, + total: usize, + ) -> bool { + let ctxt: &mut F = &mut *(ctxt as *mut F); + ctxt(progress, total) + } + + let base_snapshot = base_snapshot.into_bytes_with_nul(); + let first_snapshot = first_snapshot.into_bytes_with_nul(); + let second_snapshot = second_snapshot.into_bytes_with_nul(); + let (merge_keys, merge_values): (Vec<BnString>, Vec<BnString>) = merge_conflicts + .into_iter() + .map(|(k, v)| (BnString::new(k), BnString::new(v))) + .unzip(); + // SAFETY BnString and `*const ffi::c_char` are transparent + let merge_keys_raw = merge_keys.as_ptr() as *const *const ffi::c_char; + let merge_values_raw = merge_values.as_ptr() as *const *const ffi::c_char; + + let mut conflicts_errors = ptr::null_mut(); + let mut conflicts_errors_count = 0; + + let mut result = ptr::null_mut(); + + let success = unsafe { + BNTypeArchiveMergeSnapshots( + self.as_raw(), + base_snapshot.as_ref().as_ptr() as *const ffi::c_char, + first_snapshot.as_ref().as_ptr() as *const ffi::c_char, + second_snapshot.as_ref().as_ptr() as *const ffi::c_char, + merge_keys_raw, + merge_values_raw, + merge_keys.len(), + &mut conflicts_errors, + &mut conflicts_errors_count, + &mut result, + Some(cb_callback::<P>), + (&mut progress) as *mut P as *mut ffi::c_void, + ) + }; + if success { + assert!(!result.is_null()); + Ok(unsafe { BnString::from_raw(result) }) + } else { + assert!(!conflicts_errors.is_null()); + Err(unsafe { Array::new(conflicts_errors, conflicts_errors_count, ()) }) + } + } +} + +impl CoreArrayProvider for TypeArchive { + type Raw = *mut BNTypeArchive; + type Context = (); + type Wrapped<'a> = &'a TypeArchive; +} + +unsafe impl CoreArrayProviderInner for TypeArchive { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeArchiveList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::ref_from_raw(raw) + } +} + +pub struct TypeArchiveCallbackHandle<T: TypeArchiveNotificationCallback> { + callback: *mut T, + type_archive: TypeArchive, +} + +impl<T: TypeArchiveNotificationCallback> Drop for TypeArchiveCallbackHandle<T> { + fn drop(&mut self) { + let mut notification = BNTypeArchiveNotification { + context: self.callback as *mut ffi::c_void, + typeAdded: Some(cb_type_added::<T>), + typeUpdated: Some(cb_type_updated::<T>), + typeRenamed: Some(cb_type_renamed::<T>), + typeDeleted: Some(cb_type_deleted::<T>), + }; + // unregister the notification callback + unsafe { + BNUnregisterTypeArchiveNotification(self.type_archive.as_raw(), &mut notification) + } + // free the context created at [TypeArchive::register_notification_callback] + drop(unsafe { Box::from_raw(self.callback) }); + } +} + +pub trait TypeArchiveNotificationCallback { + /// Called when a type is added to the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type added + /// * `definition` - Definition of type + fn type_added(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} + + /// Called when a type in the archive is updated to a new definition + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type + /// * `old_definition` - Previous definition + /// * `new_definition` - Current definition + fn type_updated( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_definition: &Type, + _new_definition: &Type, + ) { + } + + /// Called when a type in the archive is renamed + /// + /// * `archive` - Source Type archive + /// * `id` - Type id + /// * `old_name` - Previous name + /// * `new_name` - Current name + fn type_renamed( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_name: &QualifiedName, + _new_name: &QualifiedName, + ) { + } + + /// Called when a type in the archive is deleted from the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type deleted + /// * `definition` - Definition of type deleted + fn type_deleted(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} +} + +struct NotificationClosure<A, U, R, D> +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fun_type_added: A, + fun_type_updated: U, + fun_type_renamed: R, + fun_type_deleted: D, +} + +impl<A, U, R, D> TypeArchiveNotificationCallback for NotificationClosure<A, U, R, D> +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fn type_added(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_added)(archive, id, definition) + } + + fn type_updated( + &mut self, + archive: &TypeArchive, + id: &str, + old_definition: &Type, + new_definition: &Type, + ) { + (self.fun_type_updated)(archive, id, old_definition, new_definition) + } + + fn type_renamed( + &mut self, + archive: &TypeArchive, + id: &str, + old_name: &QualifiedName, + new_name: &QualifiedName, + ) { + (self.fun_type_renamed)(archive, id, old_name, new_name) + } + + fn type_deleted(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_deleted)(archive, id, definition) + } +} + +unsafe extern "C" fn cb_type_added<T: TypeArchiveNotificationCallback>( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_added( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +} +unsafe extern "C" fn cb_type_updated<T: TypeArchiveNotificationCallback>( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_definition: *mut BNType, + new_definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_updated( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { + handle: old_definition, + }, + &Type { + handle: new_definition, + }, + ) +} +unsafe extern "C" fn cb_type_renamed<T: TypeArchiveNotificationCallback>( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_name: *const BNQualifiedName, + new_name: *const BNQualifiedName, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let old_name = mem::ManuallyDrop::new(QualifiedName(*old_name)); + let new_name = mem::ManuallyDrop::new(QualifiedName(*new_name)); + ctxt.type_renamed( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &old_name, + &new_name, + ) +} +unsafe extern "C" fn cb_type_deleted<T: TypeArchiveNotificationCallback>( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + ctxt.type_deleted( + unsafe { TypeArchive::ref_from_raw(&archive) }, + unsafe { ffi::CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +}