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 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 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 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 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 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 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 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 pub fn current_version(&self) -> ExtensionVersion {
205 let result = unsafe { BNPluginGetCurrentVersion(self.handle.as_ptr()) };
206 ExtensionVersion::from_owned_raw(result)
207 }
208
209 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 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 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 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 pub fn is_paid(&self) -> bool {
239 unsafe { BNPluginGetIsPaid(self.handle.as_ptr()) }
240 }
241
242 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 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 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 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 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 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 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 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 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 pub fn is_installed(&self) -> bool {
333 unsafe { BNPluginIsInstalled(self.handle.as_ptr()) }
334 }
335
336 pub fn is_listed(&self) -> bool {
338 unsafe { BNPluginIsListed(self.handle.as_ptr()) }
339 }
340
341 pub fn is_deprecated(&self) -> bool {
343 unsafe { BNPluginIsDeprecated(self.handle.as_ptr()) }
344 }
345
346 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 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 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 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 pub fn install_dependencies(&self) -> bool {
381 unsafe { BNPluginInstallDependencies(self.handle.as_ptr()) }
382 }
383
384 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 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 pub fn uninstall(&self) -> bool {
419 unsafe { BNPluginUninstall(self.handle.as_ptr()) }
420 }
421
422 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 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 pub fn is_being_deleted(&self) -> bool {
448 unsafe { BNPluginIsBeingDeleted(self.handle.as_ptr()) }
449 }
450
451 pub fn is_being_updated(&self) -> bool {
453 unsafe { BNPluginIsBeingUpdated(self.handle.as_ptr()) }
454 }
455
456 pub fn is_running(&self) -> bool {
458 unsafe { BNPluginIsRunning(self.handle.as_ptr()) }
459 }
460
461 pub fn is_update_pending(&self) -> bool {
463 unsafe { BNPluginIsUpdatePending(self.handle.as_ptr()) }
464 }
465
466 pub fn is_disable_pending(&self) -> bool {
468 unsafe { BNPluginIsDisablePending(self.handle.as_ptr()) }
469 }
470
471 pub fn is_delete_pending(&self) -> bool {
473 unsafe { BNPluginIsDeletePending(self.handle.as_ptr()) }
474 }
475
476 pub fn is_updated_available(&self) -> bool {
478 unsafe { BNPluginIsUpdateAvailable(self.handle.as_ptr()) }
479 }
480
481 pub fn are_dependencies_being_installed(&self) -> bool {
483 unsafe { BNPluginAreDependenciesBeingInstalled(self.handle.as_ptr()) }
484 }
485
486 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}