diff options
| author | Rubens Brandao <git@rubens.io> | 2024-06-20 09:15:03 -0300 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-02-06 22:54:17 -0500 |
| commit | fe3ed7630079c1166bb4529100ffa7c4ae97a130 (patch) | |
| tree | 291a7978f66f91bec1e2b6c44c823d34895b8652 /rust/src | |
| parent | 4075371ae3e3b7f0f7d42be3b83dbd4bc83fdfb5 (diff) | |
Implement Rust Repository
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/lib.rs | 56 | ||||
| -rw-r--r-- | rust/src/repository.rs | 134 | ||||
| -rw-r--r-- | rust/src/repository/manager.rs | 125 | ||||
| -rw-r--r-- | rust/src/repository/plugin.rs | 300 |
4 files changed, 612 insertions, 3 deletions
diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 38fe377c..6c41180f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -70,6 +70,7 @@ pub mod references; pub mod relocation; pub mod render_layer; pub mod secrets_provider; +pub mod repository; pub mod section; pub mod segment; pub mod settings; @@ -95,6 +96,7 @@ use binaryninjacore_sys::*; use metadata::Metadata; use metadata::MetadataType; use rc::Ref; +use std::cmp; use std::collections::HashMap; use std::ffi::{c_char, c_void, CStr}; use std::path::{Path, PathBuf}; @@ -103,6 +105,7 @@ use string::BnString; use string::IntoJson; use crate::progress::{NoProgressCallback, ProgressCallback}; +use crate::string::raw_to_string; pub use binaryninjacore_sys::BNBranchType as BranchType; pub use binaryninjacore_sys::BNDataFlowQueryOption as DataFlowQueryOption; pub use binaryninjacore_sys::BNEndianness as Endianness; @@ -436,7 +439,7 @@ pub fn build_id() -> u32 { unsafe { BNGetBuildId() } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct VersionInfo { pub major: u32, pub minor: u32, @@ -445,12 +448,59 @@ pub struct VersionInfo { } impl VersionInfo { - pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self { + pub(crate) fn from_raw(value: &BNVersionInfo) -> Self { Self { major: value.major, minor: value.minor, build: value.build, - channel: unsafe { BnString::from_raw(value.channel) }.to_string(), + // NOTE: Because of plugin manager the channel might not be filled. + channel: raw_to_string(value.channel).unwrap_or_default(), + } + } + + pub(crate) fn from_owned_raw(value: BNVersionInfo) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + pub(crate) fn into_owned_raw(value: &Self) -> BNVersionInfo { + BNVersionInfo { + major: value.major, + minor: value.minor, + build: value.build, + channel: value.channel.as_ptr() as *mut c_char, + } + } + + pub(crate) fn free_raw(value: BNVersionInfo) { + let _ = unsafe { BnString::from_raw(value.channel) }; + } + + pub fn from_string<S: BnStrCompatible>(string: S) -> Self { + let string = string.into_bytes_with_nul(); + let result = unsafe { BNParseVersionString(string.as_ref().as_ptr() as *const c_char) }; + Self::from_owned_raw(result) + } +} + +impl PartialOrd for VersionInfo { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for VersionInfo { + fn cmp(&self, other: &Self) -> cmp::Ordering { + if self == other { + return cmp::Ordering::Equal; + } + let bn_version_0 = VersionInfo::into_owned_raw(self); + let bn_version_1 = VersionInfo::into_owned_raw(other); + if unsafe { BNVersionLessThan(bn_version_0, bn_version_1) } { + cmp::Ordering::Less + } else { + cmp::Ordering::Greater } } } diff --git a/rust/src/repository.rs b/rust/src/repository.rs new file mode 100644 index 00000000..135b2367 --- /dev/null +++ b/rust/src/repository.rs @@ -0,0 +1,134 @@ +mod manager; +mod plugin; + +use std::ffi::c_char; +use std::fmt::Debug; +use std::ptr::NonNull; + +use binaryninjacore_sys::*; + +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; +use crate::repository::plugin::RepositoryPlugin; +use crate::string::{BnStrCompatible, BnString}; + +pub use manager::RepositoryManager; + +pub type PluginType = BNPluginType; +pub type PluginStatus = BNPluginStatus; + +#[repr(transparent)] +pub struct Repository { + handle: NonNull<BNRepository>, +} + +impl Repository { + pub(crate) unsafe fn from_raw(handle: NonNull<BNRepository>) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRepository>) -> Ref<Self> { + Ref::new(Self { handle }) + } + + /// String URL of the git repository where the plugin repository's are stored + pub fn url(&self) -> BnString { + let result = unsafe { BNRepositoryGetUrl(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String local path to store the given plugin repository + pub fn path(&self) -> BnString { + let result = unsafe { BNRepositoryGetRepoPath(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// List of RepoPlugin objects contained within this repository + pub fn plugins(&self) -> Array<RepositoryPlugin> { + let mut count = 0; + let result = unsafe { BNRepositoryGetPlugins(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn plugin_by_path<S: BnStrCompatible>(&self, path: S) -> Option<Ref<RepositoryPlugin>> { + let path = path.into_bytes_with_nul(); + let result = unsafe { + BNRepositoryGetPluginByPath( + self.handle.as_ptr(), + path.as_ref().as_ptr() as *const c_char, + ) + }; + NonNull::new(result).map(|h| unsafe { RepositoryPlugin::ref_from_raw(h) }) + } + + // TODO: Make this a PathBuf? + /// String full path the repository + pub fn full_path(&self) -> BnString { + let result = unsafe { BNRepositoryGetPluginsPath(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } +} + +impl Debug for Repository { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Repository") + .field("url", &self.url()) + .field("path", &self.path()) + .field("full_path", &self.full_path()) + .field("plugins", &self.plugins().to_vec()) + .finish() + } +} + +impl ToOwned for Repository { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { <Self as RefCountable>::inc_ref(self) } + } +} + +unsafe impl RefCountable for Repository { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Self::ref_from_raw(NonNull::new(BNNewRepositoryReference(handle.handle.as_ptr())).unwrap()) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeRepository(handle.handle.as_ptr()) + } +} + +impl CoreArrayProvider for Repository { + type Raw = *mut BNRepository; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for Repository { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeRepositoryManagerRepositoriesList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self::from_raw(NonNull::new(*raw).unwrap()), context) + } +} + +impl CoreArrayProvider for PluginType { + type Raw = BNPluginType; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for PluginType { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreePluginTypes(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + *raw + } +} diff --git a/rust/src/repository/manager.rs b/rust/src/repository/manager.rs new file mode 100644 index 00000000..59889162 --- /dev/null +++ b/rust/src/repository/manager.rs @@ -0,0 +1,125 @@ +use crate::rc::{Array, Ref, RefCountable}; +use crate::repository::Repository; +use crate::string::BnStrCompatible; +use binaryninjacore_sys::{ + BNCreateRepositoryManager, BNFreeRepositoryManager, BNGetRepositoryManager, + BNNewRepositoryManagerReference, BNRepositoryGetRepositoryByPath, BNRepositoryManager, + BNRepositoryManagerAddRepository, BNRepositoryManagerCheckForUpdates, + BNRepositoryManagerGetDefaultRepository, BNRepositoryManagerGetRepositories, +}; +use std::ffi::c_char; +use std::fmt::Debug; +use std::ptr::NonNull; + +/// Keeps track of all the repositories and keeps the `enabled_plugins.json` +/// file coherent with the plugins that are installed/uninstalled enabled/disabled +#[repr(transparent)] +pub struct RepositoryManager { + handle: NonNull<BNRepositoryManager>, +} + +impl RepositoryManager { + #[allow(clippy::should_implement_trait)] + pub fn default() -> Ref<Self> { + let result = unsafe { BNGetRepositoryManager() }; + unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRepositoryManager>) -> Ref<Self> { + Ref::new(Self { handle }) + } + + pub fn new<S: BnStrCompatible>(plugins_path: S) -> Ref<Self> { + let plugins_path = plugins_path.into_bytes_with_nul(); + let result = + unsafe { BNCreateRepositoryManager(plugins_path.as_ref().as_ptr() as *const c_char) }; + unsafe { Self::ref_from_raw(NonNull::new(result).unwrap()) } + } + + /// Check for updates for all managed [`Repository`] objects + pub fn check_for_updates(&self) -> bool { + unsafe { BNRepositoryManagerCheckForUpdates(self.handle.as_ptr()) } + } + + /// List of [`Repository`] objects being managed + pub fn repositories(&self) -> Array<Repository> { + let mut count = 0; + let result = + unsafe { BNRepositoryManagerGetRepositories(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Adds a new plugin repository for the manager to track. + /// + /// To remove a repository, restart Binary Ninja (and don't re-add the repository!). + /// File artifacts will remain on disk under repositories/ file in the User Folder. + /// + /// Before you can query plugin metadata from a repository, you need to call [`RepositoryManager::check_for_updates`]. + /// + /// * `url` - URL to the plugins.json containing the records for this repository + /// * `repository_path` - path to where the repository will be stored on disk locally + /// + /// Returns true if the repository was successfully added, false otherwise. + pub fn add_repository<U: BnStrCompatible, P: BnStrCompatible>( + &self, + url: U, + repository_path: P, + ) -> bool { + let url = url.into_bytes_with_nul(); + let repo_path = repository_path.into_bytes_with_nul(); + unsafe { + BNRepositoryManagerAddRepository( + self.handle.as_ptr(), + url.as_ref().as_ptr() as *const c_char, + repo_path.as_ref().as_ptr() as *const c_char, + ) + } + } + + pub fn repository_by_path<P: BnStrCompatible>(&self, path: P) -> Option<Repository> { + let path = path.into_bytes_with_nul(); + let result = unsafe { + BNRepositoryGetRepositoryByPath( + self.handle.as_ptr(), + path.as_ref().as_ptr() as *const c_char, + ) + }; + NonNull::new(result).map(|raw| unsafe { Repository::from_raw(raw) }) + } + + /// Gets the default [`Repository`] + pub fn default_repository(&self) -> Ref<Repository> { + let result = unsafe { BNRepositoryManagerGetDefaultRepository(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { Repository::ref_from_raw(NonNull::new(result).unwrap()) } + } +} + +impl Debug for RepositoryManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RepositoryManager") + .field("repositories", &self.repositories().to_vec()) + .finish() + } +} + +impl ToOwned for RepositoryManager { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for RepositoryManager { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Self::ref_from_raw( + NonNull::new(BNNewRepositoryManagerReference(handle.handle.as_ptr())).unwrap(), + ) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeRepositoryManager(handle.handle.as_ptr()) + } +} diff --git a/rust/src/repository/plugin.rs b/rust/src/repository/plugin.rs new file mode 100644 index 00000000..7a3d99b0 --- /dev/null +++ b/rust/src/repository/plugin.rs @@ -0,0 +1,300 @@ +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; +use crate::repository::{PluginStatus, PluginType}; +use crate::string::BnString; +use crate::VersionInfo; +use binaryninjacore_sys::*; +use std::ffi::c_char; +use std::fmt::Debug; +use std::ptr::NonNull; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +#[repr(transparent)] +pub struct RepositoryPlugin { + handle: NonNull<BNRepoPlugin>, +} + +impl RepositoryPlugin { + pub(crate) unsafe fn from_raw(handle: NonNull<BNRepoPlugin>) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNRepoPlugin>) -> Ref<Self> { + Ref::new(Self { handle }) + } + + /// String indicating the API used by the plugin + pub fn apis(&self) -> Array<BnString> { + let mut count = 0; + let result = unsafe { BNPluginGetApis(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// String of the plugin author + pub fn author(&self) -> BnString { + let result = unsafe { BNPluginGetAuthor(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String short description of the plugin + pub fn description(&self) -> BnString { + let result = unsafe { BNPluginGetDescription(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String complete license text for the given plugin + pub fn license_text(&self) -> BnString { + let result = unsafe { BNPluginGetLicenseText(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String long description of the plugin + pub fn long_description(&self) -> BnString { + let result = unsafe { BNPluginGetLongdescription(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// Minimum version info the plugin was tested on + pub fn minimum_version_info(&self) -> VersionInfo { + let result = unsafe { BNPluginGetMinimumVersionInfo(self.handle.as_ptr()) }; + VersionInfo::from_owned_raw(result) + } + + /// Maximum version info the plugin will support + pub fn maximum_version_info(&self) -> VersionInfo { + let result = unsafe { BNPluginGetMaximumVersionInfo(self.handle.as_ptr()) }; + VersionInfo::from_owned_raw(result) + } + + /// String plugin name + pub fn name(&self) -> BnString { + let result = unsafe { BNPluginGetName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String URL of the plugin's git repository + pub fn project_url(&self) -> BnString { + let result = unsafe { BNPluginGetProjectUrl(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String URL of the plugin's git repository + pub fn package_url(&self) -> BnString { + let result = unsafe { BNPluginGetPackageUrl(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + + /// String URL of the plugin author's url + pub fn author_url(&self) -> BnString { + let result = unsafe { BNPluginGetAuthorUrl(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut c_char) } + } + /// String version of the plugin + pub fn version(&self) -> BnString { + let result = unsafe { BNPluginGetVersion(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// String of the commit of this plugin git repository + pub fn commit(&self) -> BnString { + let result = unsafe { BNPluginGetCommit(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// Relative path from the base of the repository to the actual plugin + pub fn path(&self) -> BnString { + let result = unsafe { BNPluginGetPath(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// Optional sub-directory the plugin code lives in as a relative path from the plugin root + pub fn subdir(&self) -> BnString { + let result = unsafe { BNPluginGetSubdir(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// Dependencies required for installing this plugin + pub fn dependencies(&self) -> BnString { + let result = unsafe { BNPluginGetDependencies(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// true if the plugin is installed, false otherwise + pub fn is_installed(&self) -> bool { + unsafe { BNPluginIsInstalled(self.handle.as_ptr()) } + } + + /// true if the plugin is enabled, false otherwise + pub fn is_enabled(&self) -> bool { + unsafe { BNPluginIsEnabled(self.handle.as_ptr()) } + } + + pub fn status(&self) -> PluginStatus { + unsafe { BNPluginGetPluginStatus(self.handle.as_ptr()) } + } + + /// List of PluginType enumeration objects indicating the plugin type(s) + pub fn types(&self) -> Array<PluginType> { + let mut count = 0; + let result = unsafe { BNPluginGetPluginTypes(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Enable this plugin, optionally trying to force it. + /// Force loading a plugin with ignore platform and api constraints. + pub fn enable(&self, force: bool) -> bool { + unsafe { BNPluginEnable(self.handle.as_ptr(), force) } + } + + pub fn disable(&self) -> bool { + unsafe { BNPluginDisable(self.handle.as_ptr()) } + } + + /// Attempt to install the given plugin + pub fn install(&self) -> bool { + unsafe { BNPluginInstall(self.handle.as_ptr()) } + } + + pub fn install_dependencies(&self) -> bool { + unsafe { BNPluginInstallDependencies(self.handle.as_ptr()) } + } + + /// Attempt to uninstall the given plugin + pub fn uninstall(&self) -> bool { + unsafe { BNPluginUninstall(self.handle.as_ptr()) } + } + + pub fn updated(&self) -> bool { + unsafe { BNPluginUpdate(self.handle.as_ptr()) } + } + + /// List of platforms this plugin can execute on + pub fn platforms(&self) -> Array<BnString> { + let mut count = 0; + let result = unsafe { BNPluginGetPlatforms(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn repository(&self) -> BnString { + let result = unsafe { BNPluginGetRepository(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result as *mut i8) } + } + + /// Boolean status indicating that the plugin is being deleted + pub fn is_being_deleted(&self) -> bool { + unsafe { BNPluginIsBeingDeleted(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin is being updated + pub fn is_being_updated(&self) -> bool { + unsafe { BNPluginIsBeingUpdated(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin is currently running + pub fn is_running(&self) -> bool { + unsafe { BNPluginIsRunning(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin has updates will be installed after the next restart + pub fn is_update_pending(&self) -> bool { + unsafe { BNPluginIsUpdatePending(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin will be disabled after the next restart + pub fn is_disable_pending(&self) -> bool { + unsafe { BNPluginIsDisablePending(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin will be deleted after the next restart + pub fn is_delete_pending(&self) -> bool { + unsafe { BNPluginIsDeletePending(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin has updates available + pub fn is_updated_available(&self) -> bool { + unsafe { BNPluginIsUpdateAvailable(self.handle.as_ptr()) } + } + + /// Boolean status indicating that the plugin's dependencies are currently being installed + pub fn are_dependencies_being_installed(&self) -> bool { + unsafe { BNPluginAreDependenciesBeingInstalled(self.handle.as_ptr()) } + } + + /// Gets a json object of the project data field + pub fn project_data(&self) -> BnString { + let result = unsafe { BNPluginGetProjectData(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Returns a datetime object representing the plugins last update + pub fn last_update(&self) -> SystemTime { + let result = unsafe { BNPluginGetLastUpdate(self.handle.as_ptr()) }; + UNIX_EPOCH + Duration::from_secs(result) + } +} + +impl Debug for RepositoryPlugin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RepositoryPlugin") + .field("name", &self.name()) + .field("version", &self.version()) + .field("author", &self.author()) + .field("description", &self.description()) + .field("minimum_version_info", &self.minimum_version_info()) + .field("maximum_version_info", &self.maximum_version_info()) + .field("last_update", &self.last_update()) + .field("status", &self.status()) + .finish() + } +} + +impl ToOwned for RepositoryPlugin { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for RepositoryPlugin { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + Self::ref_from_raw(NonNull::new(BNNewPluginReference(handle.handle.as_ptr())).unwrap()) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreePlugin(handle.handle.as_ptr()) + } +} + +impl CoreArrayProvider for RepositoryPlugin { + type Raw = *mut BNRepoPlugin; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for RepositoryPlugin { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeRepositoryPluginList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self::from_raw(NonNull::new(*raw).unwrap()), context) + } +} |
