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