Skip to main content

binaryninja/repository/
plugin.rs

1use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
2use crate::repository::{PluginStatus, PluginType};
3use crate::string::{raw_to_string, BnString, IntoCStr};
4use crate::VersionInfo;
5use binaryninjacore_sys::*;
6use std::ffi::c_char;
7use std::fmt::Debug;
8use std::path::PathBuf;
9use std::ptr::NonNull;
10use std::slice;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct ExtensionVersionPlatform {
14    pub name: String,
15    pub download_url: String,
16    pub untracked_download_url: String,
17}
18
19impl ExtensionVersionPlatform {
20    pub(crate) fn from_raw(value: &BNPluginVersionPlatform) -> Self {
21        Self {
22            name: raw_to_string(value.name as *mut _).unwrap_or_default(),
23            download_url: raw_to_string(value.downloadUrl as *mut _).unwrap_or_default(),
24            untracked_download_url: raw_to_string(value.untrackedDownloadUrl as *mut _)
25                .unwrap_or_default(),
26        }
27    }
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct ExtensionVersion {
32    pub id: String,
33    pub version: String,
34    pub long_description: String,
35    pub changelog: String,
36    pub minimum_client_version: u64,
37    pub platforms: Vec<ExtensionVersionPlatform>,
38    pub created: String,
39}
40
41impl ExtensionVersion {
42    pub(crate) fn from_raw(value: &BNPluginVersion) -> Self {
43        let platforms = if value.platforms.is_null() || value.platformCount == 0 {
44            Vec::new()
45        } else {
46            unsafe { slice::from_raw_parts(value.platforms, value.platformCount) }
47                .iter()
48                .map(ExtensionVersionPlatform::from_raw)
49                .collect()
50        };
51
52        Self {
53            id: raw_to_string(value.id as *mut _).unwrap_or_default(),
54            version: raw_to_string(value.versionString as *mut _).unwrap_or_default(),
55            long_description: raw_to_string(value.longDescription as *mut _).unwrap_or_default(),
56            changelog: raw_to_string(value.changelog as *mut _).unwrap_or_default(),
57            minimum_client_version: value.minimumClientVersion,
58            platforms,
59            created: raw_to_string(value.created as *mut _).unwrap_or_default(),
60        }
61    }
62
63    pub(crate) fn from_owned_raw(value: BNPluginVersion) -> Self {
64        let owned = Self::from_raw(&value);
65        unsafe { BNPluginFreeVersion(value) };
66        owned
67    }
68}
69
70pub type PluginDependencyConflictStatus = BNPluginDependencyConflictStatus;
71
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct PluginDependencyRequirement {
74    pub plugin_name: String,
75    pub requirement: String,
76}
77
78impl PluginDependencyRequirement {
79    fn from_raw(value: &BNPluginDependencyRequirement) -> Self {
80        Self {
81            plugin_name: raw_to_string(value.pluginName).unwrap_or_default(),
82            requirement: raw_to_string(value.requirement).unwrap_or_default(),
83        }
84    }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct PluginDependencyConflict {
89    pub status: PluginDependencyConflictStatus,
90    pub package_name: String,
91    pub candidate_requirements: Vec<PluginDependencyRequirement>,
92    pub installed_requirements: Vec<PluginDependencyRequirement>,
93}
94
95impl PluginDependencyConflict {
96    fn from_raw(value: &BNPluginDependencyConflict) -> Self {
97        let requirements = |requirements: *mut BNPluginDependencyRequirement, count| {
98            if requirements.is_null() || count == 0 {
99                Vec::new()
100            } else {
101                unsafe { slice::from_raw_parts(requirements, count) }
102                    .iter()
103                    .map(PluginDependencyRequirement::from_raw)
104                    .collect()
105            }
106        };
107
108        Self {
109            status: value.status,
110            package_name: raw_to_string(value.packageName).unwrap_or_default(),
111            candidate_requirements: requirements(
112                value.candidateRequirements,
113                value.candidateRequirementCount,
114            ),
115            installed_requirements: requirements(
116                value.installedRequirements,
117                value.installedRequirementCount,
118            ),
119        }
120    }
121}
122
123struct RawPluginDependencyConflicts {
124    conflicts: *mut BNPluginDependencyConflict,
125    count: usize,
126}
127
128impl RawPluginDependencyConflicts {
129    unsafe fn as_slice(&self) -> &[BNPluginDependencyConflict] {
130        slice::from_raw_parts(self.conflicts, self.count)
131    }
132}
133
134impl Drop for RawPluginDependencyConflicts {
135    fn drop(&mut self) {
136        unsafe { BNFreePluginDependencyConflicts(self.conflicts, self.count) };
137    }
138}
139
140#[repr(transparent)]
141pub struct Extension {
142    handle: NonNull<BNPlugin>,
143}
144
145impl Extension {
146    pub(crate) unsafe fn from_raw(handle: NonNull<BNPlugin>) -> Self {
147        Self { handle }
148    }
149
150    pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNPlugin>) -> Ref<Self> {
151        Ref::new(Self { handle })
152    }
153
154    /// String indicating the API used by the plugin
155    pub fn apis(&self) -> Array<BnString> {
156        let mut count = 0;
157        let result = unsafe { BNPluginGetApis(self.handle.as_ptr(), &mut count) };
158        assert!(!result.is_null());
159        unsafe { Array::new(result, count, ()) }
160    }
161
162    /// String of the plugin author
163    pub fn author(&self) -> String {
164        let result = unsafe { BNPluginGetAuthor(self.handle.as_ptr()) };
165        assert!(!result.is_null());
166        unsafe { BnString::into_string(result as *mut c_char) }
167    }
168
169    /// String short description of the plugin
170    pub fn description(&self) -> String {
171        let result = unsafe { BNPluginGetDescription(self.handle.as_ptr()) };
172        assert!(!result.is_null());
173        unsafe { BnString::into_string(result as *mut c_char) }
174    }
175
176    /// String complete license text for the given plugin
177    pub fn license_text(&self) -> String {
178        let result = unsafe { BNPluginGetLicenseText(self.handle.as_ptr()) };
179        assert!(!result.is_null());
180        unsafe { BnString::into_string(result as *mut c_char) }
181    }
182
183    /// Minimum version info the plugin was tested on
184    pub fn minimum_version_info(&self) -> VersionInfo {
185        let result = unsafe { BNPluginGetMinimumVersionInfo(self.handle.as_ptr()) };
186        VersionInfo::from_owned_raw(result)
187    }
188
189    /// Maximum version info the plugin will support
190    pub fn maximum_version_info(&self) -> VersionInfo {
191        let result = unsafe { BNPluginGetMaximumVersionInfo(self.handle.as_ptr()) };
192        VersionInfo::from_owned_raw(result)
193    }
194
195    /// Metadata for all available versions of this plugin
196    pub fn versions(&self) -> Array<ExtensionVersion> {
197        let mut count = 0;
198        let result = unsafe { BNPluginGetVersions(self.handle.as_ptr(), &mut count) };
199        assert!(!result.is_null());
200        unsafe { Array::new(result, count, ()) }
201    }
202
203    /// Metadata for the currently selected version of this plugin
204    pub fn current_version(&self) -> ExtensionVersion {
205        let result = unsafe { BNPluginGetCurrentVersion(self.handle.as_ptr()) };
206        ExtensionVersion::from_owned_raw(result)
207    }
208
209    /// Latest version id available for this platform
210    pub fn latest_version_id(&self) -> String {
211        let result = unsafe { BNPluginGetLatestVersionID(self.handle.as_ptr()) };
212        assert!(!result.is_null());
213        unsafe { BnString::into_string(result as *mut c_char) }
214    }
215
216    /// String plugin name
217    pub fn name(&self) -> String {
218        let result = unsafe { BNPluginGetName(self.handle.as_ptr()) };
219        assert!(!result.is_null());
220        unsafe { BnString::into_string(result as *mut c_char) }
221    }
222
223    /// String URL of the plugin's git repository
224    pub fn project_url(&self) -> String {
225        let result = unsafe { BNPluginGetProjectUrl(self.handle.as_ptr()) };
226        assert!(!result.is_null());
227        unsafe { BnString::into_string(result as *mut c_char) }
228    }
229
230    /// String URL of the plugin's git repository
231    pub fn package_url(&self) -> String {
232        let result = unsafe { BNPluginGetPackageUrl(self.handle.as_ptr()) };
233        assert!(!result.is_null());
234        unsafe { BnString::into_string(result as *mut c_char) }
235    }
236
237    /// Boolean True if this plugin requires payment, False otherwise
238    pub fn is_paid(&self) -> bool {
239        unsafe { BNPluginGetIsPaid(self.handle.as_ptr()) }
240    }
241
242    /// String URL of the plugin author's url
243    pub fn author_url(&self) -> String {
244        let result = unsafe { BNPluginGetAuthorUrl(self.handle.as_ptr()) };
245        assert!(!result.is_null());
246        unsafe { BnString::into_string(result as *mut c_char) }
247    }
248
249    /// String of the commit of this plugin git repository
250    pub fn commit(&self) -> String {
251        let result = unsafe { BNPluginGetCommit(self.handle.as_ptr()) };
252        assert!(!result.is_null());
253        unsafe { BnString::into_string(result as *mut c_char) }
254    }
255
256    /// Relative path from the base of the repository to the actual plugin
257    pub fn path(&self) -> PathBuf {
258        let result = unsafe { BNPluginGetPath(self.handle.as_ptr()) };
259        assert!(!result.is_null());
260        let result_str = unsafe { BnString::into_string(result as *mut c_char) };
261        PathBuf::from(result_str)
262    }
263
264    /// Optional sub-directory the plugin code lives in as a relative path from the plugin root
265    pub fn subdir(&self) -> PathBuf {
266        let result = unsafe { BNPluginGetSubdir(self.handle.as_ptr()) };
267        assert!(!result.is_null());
268        let result_str = unsafe { BnString::into_string(result as *mut c_char) };
269        PathBuf::from(result_str)
270    }
271
272    /// Dependencies required for installing this plugin
273    pub fn dependencies(&self) -> String {
274        let result = unsafe { BNPluginGetDependencies(self.handle.as_ptr()) };
275        assert!(!result.is_null());
276        unsafe { BnString::into_string(result as *mut c_char) }
277    }
278
279    /// Dependencies required for installing a specific version of this plugin
280    pub fn dependencies_for_version(&self, version_id: &str) -> String {
281        let version_id_raw = version_id.to_cstr();
282        let result = unsafe {
283            BNPluginGetDependenciesForVersion(self.handle.as_ptr(), version_id_raw.as_ptr())
284        };
285        assert!(!result.is_null());
286        unsafe { BnString::into_string(result as *mut c_char) }
287    }
288
289    /// Dependency conflicts with installed plugins
290    pub fn dependency_conflicts(&self) -> Vec<PluginDependencyConflict> {
291        let mut count = 0;
292        let conflicts = unsafe { BNPluginGetDependencyConflicts(self.handle.as_ptr(), &mut count) };
293        Self::dependency_conflicts_from_raw(conflicts, count)
294    }
295
296    /// Dependency conflicts with installed plugins for a specific plugin version
297    pub fn dependency_conflicts_for_version(
298        &self,
299        version_id: &str,
300    ) -> Vec<PluginDependencyConflict> {
301        let version_id_raw = version_id.to_cstr();
302        let mut count = 0;
303        let conflicts = unsafe {
304            BNPluginGetDependencyConflictsForVersion(
305                self.handle.as_ptr(),
306                version_id_raw.as_ptr(),
307                &mut count,
308            )
309        };
310        Self::dependency_conflicts_from_raw(conflicts, count)
311    }
312
313    /// Turns raw dependency conflicts into a vector of PluginDependencyConflict
314    fn dependency_conflicts_from_raw(
315        conflicts: *mut BNPluginDependencyConflict,
316        count: usize,
317    ) -> Vec<PluginDependencyConflict> {
318        if conflicts.is_null() {
319            return Vec::new();
320        }
321        let conflicts = RawPluginDependencyConflicts { conflicts, count };
322        unsafe {
323            conflicts
324                .as_slice()
325                .iter()
326                .map(PluginDependencyConflict::from_raw)
327                .collect()
328        }
329    }
330
331    /// true if the plugin is installed, false otherwise
332    pub fn is_installed(&self) -> bool {
333        unsafe { BNPluginIsInstalled(self.handle.as_ptr()) }
334    }
335
336    /// true if the plugin is present in its repository's latest successful listing
337    pub fn is_listed(&self) -> bool {
338        unsafe { BNPluginIsListed(self.handle.as_ptr()) }
339    }
340
341    /// true if the plugin is marked deprecated by its repository
342    pub fn is_deprecated(&self) -> bool {
343        unsafe { BNPluginIsDeprecated(self.handle.as_ptr()) }
344    }
345
346    /// true if the plugin is enabled, false otherwise
347    pub fn is_enabled(&self) -> bool {
348        unsafe { BNPluginIsEnabled(self.handle.as_ptr()) }
349    }
350
351    pub fn status(&self) -> PluginStatus {
352        unsafe { BNPluginGetPluginStatus(self.handle.as_ptr()) }
353    }
354
355    /// List of PluginType enumeration objects indicating the plugin type(s)
356    pub fn types(&self) -> Array<PluginType> {
357        let mut count = 0;
358        let result = unsafe { BNPluginGetPluginTypes(self.handle.as_ptr(), &mut count) };
359        assert!(!result.is_null());
360        unsafe { Array::new(result, count, ()) }
361    }
362
363    /// Enable this plugin, optionally trying to force it.
364    /// Force loading a plugin with ignore platform and api constraints.
365    pub fn enable(&self, force: bool) -> bool {
366        unsafe { BNPluginEnable(self.handle.as_ptr(), force) }
367    }
368
369    pub fn disable(&self) -> bool {
370        unsafe { BNPluginDisable(self.handle.as_ptr()) }
371    }
372
373    /// Attempt to install the given plugin
374    pub fn install(&self, version_id: &str) -> bool {
375        let version_id_raw = version_id.to_cstr();
376        unsafe { BNPluginInstall(self.handle.as_ptr(), version_id_raw.as_ptr()) }
377    }
378
379    /// Attempt to install the dependencies of this plugin
380    pub fn install_dependencies(&self) -> bool {
381        unsafe { BNPluginInstallDependencies(self.handle.as_ptr()) }
382    }
383
384    /// Attempt to install the dependencies of a specific version of this plugin
385    pub fn install_dependencies_for_version(&self, version_id: &str) -> bool {
386        let version_id_raw = version_id.to_cstr();
387        unsafe {
388            BNPluginInstallDependenciesForVersion(self.handle.as_ptr(), version_id_raw.as_ptr())
389        }
390    }
391
392    /// Attempt to install the dependencies of a specific version of this plugin, excluding some packages by canonical name
393    pub fn install_dependencies_for_version_with_exclusions(
394        &self,
395        version_id: &str,
396        excluded_package_names: &[&str],
397    ) -> bool {
398        let version_id_raw = version_id.to_cstr();
399        let excluded_package_names_raw: Vec<_> = excluded_package_names
400            .iter()
401            .map(|package_name| package_name.to_cstr())
402            .collect();
403        let excluded_package_name_ptrs: Vec<_> = excluded_package_names_raw
404            .iter()
405            .map(|package_name| package_name.as_ptr())
406            .collect();
407        unsafe {
408            BNPluginInstallDependenciesWithExclusionsForVersion(
409                self.handle.as_ptr(),
410                version_id_raw.as_ptr(),
411                excluded_package_name_ptrs.as_ptr(),
412                excluded_package_name_ptrs.len(),
413            )
414        }
415    }
416
417    /// Attempt to uninstall the given plugin
418    pub fn uninstall(&self) -> bool {
419        unsafe { BNPluginUninstall(self.handle.as_ptr()) }
420    }
421
422    /// Cancel an uninstall that is pending until restart.
423    pub fn cancel_uninstall(&self) -> bool {
424        unsafe { BNPluginCancelUninstall(self.handle.as_ptr()) }
425    }
426
427    pub fn updated(&self, version_id: &str) -> bool {
428        let version_id_raw = version_id.to_cstr();
429        unsafe { BNPluginUpdate(self.handle.as_ptr(), version_id_raw.as_ptr()) }
430    }
431
432    /// List of platforms this plugin can execute on
433    pub fn platforms(&self) -> Array<BnString> {
434        let mut count = 0;
435        let result = unsafe { BNPluginGetPlatforms(self.handle.as_ptr(), &mut count) };
436        assert!(!result.is_null());
437        unsafe { Array::new(result, count, ()) }
438    }
439
440    pub fn repository(&self) -> String {
441        let result = unsafe { BNPluginGetRepository(self.handle.as_ptr()) };
442        assert!(!result.is_null());
443        unsafe { BnString::into_string(result as *mut c_char) }
444    }
445
446    /// Boolean status indicating that the plugin is being deleted
447    pub fn is_being_deleted(&self) -> bool {
448        unsafe { BNPluginIsBeingDeleted(self.handle.as_ptr()) }
449    }
450
451    /// Boolean status indicating that the plugin is being updated
452    pub fn is_being_updated(&self) -> bool {
453        unsafe { BNPluginIsBeingUpdated(self.handle.as_ptr()) }
454    }
455
456    /// Boolean status indicating that the plugin is currently running
457    pub fn is_running(&self) -> bool {
458        unsafe { BNPluginIsRunning(self.handle.as_ptr()) }
459    }
460
461    /// Boolean status indicating that the plugin has updates will be installed after the next restart
462    pub fn is_update_pending(&self) -> bool {
463        unsafe { BNPluginIsUpdatePending(self.handle.as_ptr()) }
464    }
465
466    /// Boolean status indicating that the plugin will be disabled after the next restart
467    pub fn is_disable_pending(&self) -> bool {
468        unsafe { BNPluginIsDisablePending(self.handle.as_ptr()) }
469    }
470
471    /// Boolean status indicating that the plugin will be deleted after the next restart
472    pub fn is_delete_pending(&self) -> bool {
473        unsafe { BNPluginIsDeletePending(self.handle.as_ptr()) }
474    }
475
476    /// Boolean status indicating that the plugin has updates available
477    pub fn is_updated_available(&self) -> bool {
478        unsafe { BNPluginIsUpdateAvailable(self.handle.as_ptr()) }
479    }
480
481    /// Boolean status indicating that the plugin's dependencies are currently being installed
482    pub fn are_dependencies_being_installed(&self) -> bool {
483        unsafe { BNPluginAreDependenciesBeingInstalled(self.handle.as_ptr()) }
484    }
485
486    /// Gets a json object of the project data field
487    pub fn project_data(&self) -> String {
488        let result = unsafe { BNPluginGetProjectData(self.handle.as_ptr()) };
489        assert!(!result.is_null());
490        unsafe { BnString::into_string(result) }
491    }
492}
493
494impl Debug for Extension {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        f.debug_struct("Extension")
497            .field("name", &self.name())
498            .field("author", &self.author())
499            .field("description", &self.description())
500            .field("minimum_version_info", &self.minimum_version_info())
501            .field("maximum_version_info", &self.maximum_version_info())
502            .field("status", &self.status())
503            .finish()
504    }
505}
506
507impl ToOwned for Extension {
508    type Owned = Ref<Self>;
509
510    fn to_owned(&self) -> Self::Owned {
511        unsafe { RefCountable::inc_ref(self) }
512    }
513}
514
515unsafe impl RefCountable for Extension {
516    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
517        Self::ref_from_raw(NonNull::new(BNNewPluginReference(handle.handle.as_ptr())).unwrap())
518    }
519
520    unsafe fn dec_ref(handle: &Self) {
521        BNFreePlugin(handle.handle.as_ptr())
522    }
523}
524
525impl CoreArrayProvider for Extension {
526    type Raw = *mut BNPlugin;
527    type Context = ();
528    type Wrapped<'a> = Guard<'a, Self>;
529}
530
531unsafe impl CoreArrayProviderInner for Extension {
532    unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
533        BNFreeRepositoryPluginList(raw)
534    }
535
536    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
537        Guard::new(Self::from_raw(NonNull::new(*raw).unwrap()), context)
538    }
539}
540
541impl CoreArrayProvider for ExtensionVersion {
542    type Raw = BNPluginVersion;
543    type Context = ();
544    type Wrapped<'a> = Self;
545}
546
547unsafe impl CoreArrayProviderInner for ExtensionVersion {
548    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
549        BNFreePluginVersions(raw, count)
550    }
551
552    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
553        ExtensionVersion::from_raw(raw)
554    }
555}