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 },
+    )
+}