summaryrefslogtreecommitdiff
path: root/rust
diff options
context:
space:
mode:
authorRubens Brandao <git@rubens.io>2024-06-20 09:15:03 -0300
committerMason Reed <mason@vector35.com>2025-02-06 22:54:17 -0500
commitfe3ed7630079c1166bb4529100ffa7c4ae97a130 (patch)
tree291a7978f66f91bec1e2b6c44c823d34895b8652 /rust
parent4075371ae3e3b7f0f7d42be3b83dbd4bc83fdfb5 (diff)
Implement Rust Repository
Diffstat (limited to 'rust')
-rw-r--r--rust/src/lib.rs56
-rw-r--r--rust/src/repository.rs134
-rw-r--r--rust/src/repository/manager.rs125
-rw-r--r--rust/src/repository/plugin.rs300
-rw-r--r--rust/tests/repository.rs30
5 files changed, 642 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)
+ }
+}
diff --git a/rust/tests/repository.rs b/rust/tests/repository.rs
new file mode 100644
index 00000000..6047bcae
--- /dev/null
+++ b/rust/tests/repository.rs
@@ -0,0 +1,30 @@
+use binaryninja::headless::Session;
+use binaryninja::repository::RepositoryManager;
+use rstest::*;
+
+#[fixture]
+fn session() -> Session {
+ Session::new().expect("Failed to initialize session")
+}
+
+#[rstest]
+fn test_list(_session: Session) {
+ let manager = RepositoryManager::default();
+ let repositories = manager.repositories();
+ for repository in &repositories {
+ let repo_path = repository.path();
+ let repository_by_path = manager.repository_by_path(repo_path).unwrap();
+ assert_eq!(repository.url(), repository_by_path.url());
+ }
+
+ let repository = manager.default_repository();
+ let _full_path = repository.full_path();
+ let _path = repository.path();
+ let _url = repository.url();
+ let plugins = repository.plugins();
+ for plugin in &plugins {
+ let plugin_path = plugin.path();
+ let plugin_by_path = repository.plugin_by_path(plugin_path).unwrap();
+ assert_eq!(plugin.package_url(), plugin_by_path.package_url());
+ }
+}