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 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 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 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 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 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 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 pub fn current_version(&self) -> ExtensionVersion {
203 let result = unsafe { BNPluginGetCurrentVersion(self.handle.as_ptr()) };
204 ExtensionVersion::from_owned_raw(result)
205 }
206
207 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 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 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 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 pub fn is_paid(&self) -> bool {
237 unsafe { BNPluginGetIsPaid(self.handle.as_ptr()) }
238 }
239
240 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 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 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 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 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 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 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 pub fn is_installed(&self) -> bool {
317 unsafe { BNPluginIsInstalled(self.handle.as_ptr()) }
318 }
319
320 pub fn is_listed(&self) -> bool {
322 unsafe { BNPluginIsListed(self.handle.as_ptr()) }
323 }
324
325 pub fn is_deprecated(&self) -> bool {
327 unsafe { BNPluginIsDeprecated(self.handle.as_ptr()) }
328 }
329
330 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 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 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 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 pub fn install_dependencies(&self) -> bool {
365 unsafe { BNPluginInstallDependencies(self.handle.as_ptr()) }
366 }
367
368 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 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 pub fn uninstall(&self) -> bool {
403 unsafe { BNPluginUninstall(self.handle.as_ptr()) }
404 }
405
406 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 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 pub fn is_being_deleted(&self) -> bool {
432 unsafe { BNPluginIsBeingDeleted(self.handle.as_ptr()) }
433 }
434
435 pub fn is_being_updated(&self) -> bool {
437 unsafe { BNPluginIsBeingUpdated(self.handle.as_ptr()) }
438 }
439
440 pub fn is_running(&self) -> bool {
442 unsafe { BNPluginIsRunning(self.handle.as_ptr()) }
443 }
444
445 pub fn is_update_pending(&self) -> bool {
447 unsafe { BNPluginIsUpdatePending(self.handle.as_ptr()) }
448 }
449
450 pub fn is_disable_pending(&self) -> bool {
452 unsafe { BNPluginIsDisablePending(self.handle.as_ptr()) }
453 }
454
455 pub fn is_delete_pending(&self) -> bool {
457 unsafe { BNPluginIsDeletePending(self.handle.as_ptr()) }
458 }
459
460 pub fn is_updated_available(&self) -> bool {
462 unsafe { BNPluginIsUpdateAvailable(self.handle.as_ptr()) }
463 }
464
465 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}