1use super::*;
2use crate::diagnostics::{
3 decoder_capability_summary, source_normalizer_packet_capability_summary,
4 source_normalizer_resource_capability_summary,
5};
6#[cfg(feature = "wasm")]
7use player_plugin::PluginOwnerDisposalError;
8use player_plugin::{
9 AudioProcessorPluginFactory, PluginInvocationPolicy, PluginInvocationWorkload, PluginReference,
10 PluginReferenceError, PluginScope, PluginTransport,
11};
12#[cfg(feature = "installed-catalog")]
13use player_plugin_package::{
14 PluginArtifactTransport, VerifiedInstalledArtifact, VerifiedInstalledPluginCatalog,
15};
16#[cfg(feature = "wasm")]
17use player_plugin_wasm_host::{
18 WASM_PLUGIN_FLUSH_TIMEOUT_MILLIS, WasmPluginRuntime, WasmPluginRuntimeError,
19};
20#[cfg(feature = "installed-catalog")]
21use std::collections::BTreeSet;
22use std::collections::HashMap;
23
24#[derive(Debug, Clone)]
30pub struct NativePluginArtifact {
31 plugin_id: String,
32 path: PathBuf,
33 loaded: Option<Arc<LoadedNativePlugin>>,
34}
35
36impl PartialEq for NativePluginArtifact {
37 fn eq(&self, other: &Self) -> bool {
38 self.plugin_id == other.plugin_id
39 && self.path == other.path
40 && match (&self.loaded, &other.loaded) {
41 (Some(left), Some(right)) => Arc::ptr_eq(left, right),
42 (None, None) => true,
43 _ => false,
44 }
45 }
46}
47
48impl Eq for NativePluginArtifact {}
49
50impl NativePluginArtifact {
51 pub fn new(
54 plugin_id: impl Into<String>,
55 path: impl Into<PathBuf>,
56 ) -> Result<Self, PluginReferenceError> {
57 let plugin_id = plugin_id.into();
58 PluginReference::new(plugin_id.clone(), None, PluginTransport::Native)?;
59 Ok(Self {
60 plugin_id,
61 path: path.into(),
62 loaded: None,
63 })
64 }
65
66 pub fn plugin_id(&self) -> &str {
67 &self.plugin_id
68 }
69
70 pub fn path(&self) -> &Path {
71 &self.path
72 }
73
74 fn load(&self) -> Result<Arc<LoadedNativePlugin>, PluginLoadError> {
75 match &self.loaded {
76 Some(plugin) => Ok(plugin.clone()),
77 None => LoadedNativePlugin::load_host_verified(&self.path).map(Arc::new),
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83struct PluginIdentityKey {
84 transport: PluginTransport,
85 plugin_id: String,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89struct PluginInterfaceKey {
90 transport: PluginTransport,
91 plugin_id: String,
92 interface_id: [u8; 16],
93 instance_id: String,
94}
95
96fn interface_references(
97 plugin: &LoadedNativePlugin,
98 interface_id: [u8; 16],
99) -> Result<Vec<PluginReference>, PluginReferenceError> {
100 plugin
101 .interfaces()
102 .iter()
103 .filter(|interface| interface.metadata.interface_id == interface_id)
104 .map(|interface| {
105 PluginReference::new(
106 plugin.plugin_id(),
107 Some(interface.metadata.instance_id.clone()),
108 PluginTransport::Native,
109 )
110 })
111 .collect()
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct RegisteredPluginInterface {
116 pub artifact_path: PathBuf,
117 pub transport: PluginTransport,
118 pub plugin_id: String,
119 pub interface: PluginInterfaceRecord,
120}
121
122#[derive(Clone)]
123pub struct ResolvedPluginCapability<T: ?Sized> {
124 reference: PluginReference,
125 capability: Arc<T>,
126}
127
128impl<T: ?Sized> std::fmt::Debug for ResolvedPluginCapability<T> {
129 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 formatter
131 .debug_struct("ResolvedPluginCapability")
132 .field("reference", &self.reference)
133 .finish_non_exhaustive()
134 }
135}
136
137impl<T: ?Sized> ResolvedPluginCapability<T> {
138 pub fn reference(&self) -> &PluginReference {
139 &self.reference
140 }
141
142 pub fn capability(&self) -> Arc<T> {
143 self.capability.clone()
144 }
145}
146
147#[derive(Debug, Error)]
148pub enum PluginRegistryBuildError {
149 #[error("failed to load native plugin artifact `{path}`: {source}")]
150 Load {
151 path: String,
152 #[source]
153 source: PluginLoadError,
154 },
155 #[error(
156 "native plugin artifact `{path}` declared identity `{expected_plugin_id}` but its Root ABI reports `{actual_plugin_id}`"
157 )]
158 PluginIdentityMismatch {
159 path: String,
160 expected_plugin_id: String,
161 actual_plugin_id: String,
162 },
163 #[error(
164 "duplicate plugin identity {transport:?}:{plugin_id} from `{first_path}` and `{duplicate_path}`"
165 )]
166 DuplicatePluginIdentity {
167 transport: PluginTransport,
168 plugin_id: String,
169 first_path: String,
170 duplicate_path: String,
171 },
172 #[error(
173 "duplicate interface identity {transport:?}:{plugin_id}:{interface_id:?}:{instance_id}"
174 )]
175 DuplicateInterfaceIdentity {
176 transport: PluginTransport,
177 plugin_id: String,
178 interface_id: [u8; 16],
179 instance_id: String,
180 },
181 #[cfg(feature = "wasm")]
182 #[error("failed to initialize the WASM plugin runtime: {source}")]
183 WasmRuntime {
184 #[source]
185 source: WasmPluginRuntimeError,
186 },
187 #[cfg(feature = "wasm")]
188 #[error("failed to load WASM plugin artifact `{path}` for `{plugin_id}`: {source}")]
189 WasmLoad {
190 path: String,
191 plugin_id: String,
192 #[source]
193 source: WasmPluginLoadError,
194 },
195 #[cfg(feature = "installed-catalog")]
196 #[error("invalid verified installed plugin artifact: {message}")]
197 InstalledCatalog { message: String },
198 #[cfg(feature = "installed-catalog")]
199 #[error(
200 "installed plugin artifact `{path}` for `{plugin_id}` does not match its declared capabilities: {message}"
201 )]
202 InstalledCapabilityMismatch {
203 path: String,
204 plugin_id: String,
205 message: String,
206 },
207 #[cfg(all(feature = "installed-catalog", not(feature = "wasm")))]
208 #[error("installed WASM plugin `{plugin_id}` requires the loader `wasm` feature")]
209 InstalledWasmUnsupported { plugin_id: String },
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Default)]
214pub struct PluginRegistryReport {
215 pub total: usize,
216 pub loaded: usize,
217 pub failed: usize,
218 pub decoder_supported: usize,
219 pub decoder_unsupported: usize,
220 pub frame_processor_supported: usize,
221 pub frame_processor_unsupported: usize,
222 pub source_normalizer_supported: usize,
223 pub source_normalizer_unsupported: usize,
224 pub unsupported_kind: usize,
225 pub best_supported_decoder_name: Option<String>,
226 pub best_supported_frame_processor_name: Option<String>,
227 pub best_supported_source_normalizer_name: Option<String>,
228 pub diagnostic_notes: Vec<String>,
229}
230
231#[derive(Debug, Clone, Default)]
233pub struct PluginRegistry {
234 records: Vec<PluginDiagnosticRecord>,
235 record_references: Vec<Option<PluginReference>>,
236 plugins: HashMap<PluginIdentityKey, Arc<LoadedNativePlugin>>,
237 #[cfg(feature = "wasm")]
238 wasm_plugins: HashMap<PluginIdentityKey, Arc<LoadedWasmPlugin>>,
239 plugin_paths: HashMap<PluginIdentityKey, PathBuf>,
240 interfaces: Vec<RegisteredPluginInterface>,
241 interface_index: HashMap<PluginInterfaceKey, usize>,
242}
243
244impl PluginRegistry {
245 pub fn inspect_decoder_support_development(
247 paths: impl IntoIterator<Item = impl AsRef<Path>>,
248 request: DecoderPluginMatchRequest,
249 ) -> Self {
250 let mut registry = Self::default();
251 for path in paths {
252 let path = path.as_ref().to_path_buf();
253 let Some(plugin) = registry.load_inspected_native_development(&path) else {
254 continue;
255 };
256 let Some(references) = registry.inspected_interface_references(
257 &path,
258 &plugin,
259 player_plugin_abi::NATIVE_DECODER_INTERFACE_ID.0,
260 ) else {
261 continue;
262 };
263 if references.is_empty() {
264 registry.push_record(
265 PluginDiagnosticRecord::unsupported_native_interface(
266 path,
267 &plugin,
268 "NativeDecoder",
269 ),
270 None,
271 );
272 continue;
273 }
274 for reference in references {
275 let record = PluginDiagnosticRecord::from_native_decoder_interface(
276 path.clone(),
277 &plugin,
278 &reference,
279 &request,
280 );
281 registry.push_record(record, Some(reference));
282 }
283 }
284 registry
285 }
286
287 pub fn inspect_frame_processor_support_development(
289 paths: impl IntoIterator<Item = impl AsRef<Path>>,
290 ) -> Self {
291 let mut registry = Self::default();
292 for path in paths {
293 let path = path.as_ref().to_path_buf();
294 let Some(plugin) = registry.load_inspected_native_development(&path) else {
295 continue;
296 };
297 let Some(references) = registry.inspected_interface_references(
298 &path,
299 &plugin,
300 player_plugin_abi::FRAME_PROCESSOR_INTERFACE_ID.0,
301 ) else {
302 continue;
303 };
304 if references.is_empty() {
305 registry.push_record(
306 PluginDiagnosticRecord::unsupported_native_interface(
307 path,
308 &plugin,
309 "FrameProcessor",
310 ),
311 None,
312 );
313 continue;
314 }
315 for reference in references {
316 let record = PluginDiagnosticRecord::from_native_frame_processor_interface(
317 path.clone(),
318 &plugin,
319 &reference,
320 );
321 registry.push_record(record, Some(reference));
322 }
323 }
324 registry
325 }
326
327 pub fn inspect_frame_processor_support_artifacts(
330 artifacts: impl IntoIterator<Item = NativePluginArtifact>,
331 ) -> Self {
332 let mut registry = Self::default();
333 for artifact in artifacts {
334 let path = artifact.path.clone();
335 let Some(plugin) = registry.load_inspected_native_artifact(&artifact) else {
336 continue;
337 };
338 let Some(references) = registry.inspected_interface_references(
339 &path,
340 &plugin,
341 player_plugin_abi::FRAME_PROCESSOR_INTERFACE_ID.0,
342 ) else {
343 continue;
344 };
345 if references.is_empty() {
346 registry.push_record(
347 PluginDiagnosticRecord::unsupported_native_interface(
348 path,
349 &plugin,
350 "FrameProcessor",
351 ),
352 None,
353 );
354 continue;
355 }
356 for reference in references {
357 let record = PluginDiagnosticRecord::from_native_frame_processor_interface(
358 path.clone(),
359 &plugin,
360 &reference,
361 );
362 registry.push_record(record, Some(reference));
363 }
364 }
365 registry
366 }
367
368 pub fn inspect_source_normalizer_support_development(
370 paths: impl IntoIterator<Item = impl AsRef<Path>>,
371 ) -> Self {
372 let mut registry = Self::default();
373 for path in paths {
374 let path = path.as_ref().to_path_buf();
375 let Some(plugin) = registry.load_inspected_native_development(&path) else {
376 continue;
377 };
378 let Some(packet_references) = registry.inspected_interface_references(
379 &path,
380 &plugin,
381 player_plugin_abi::SOURCE_NORMALIZER_PACKET_INTERFACE_ID.0,
382 ) else {
383 continue;
384 };
385 let Some(resource_references) = registry.inspected_interface_references(
386 &path,
387 &plugin,
388 player_plugin_abi::SOURCE_NORMALIZER_RESOURCE_INTERFACE_ID.0,
389 ) else {
390 continue;
391 };
392 if packet_references.is_empty() && resource_references.is_empty() {
393 registry.push_record(
394 PluginDiagnosticRecord::unsupported_native_interface(
395 path,
396 &plugin,
397 "SourceNormalizerPacket or SourceNormalizerResource",
398 ),
399 None,
400 );
401 continue;
402 }
403 for reference in resource_references {
404 let record = PluginDiagnosticRecord::from_native_source_resource_interface(
405 path.clone(),
406 &plugin,
407 &reference,
408 );
409 registry.push_record(record, Some(reference));
410 }
411 for reference in packet_references {
412 let record = PluginDiagnosticRecord::from_native_source_packet_interface(
413 path.clone(),
414 &plugin,
415 &reference,
416 );
417 registry.push_record(record, Some(reference));
418 }
419 }
420 registry
421 }
422
423 pub fn inspect_source_normalizer_support_artifacts(
426 artifacts: impl IntoIterator<Item = NativePluginArtifact>,
427 ) -> Self {
428 let mut registry = Self::default();
429 for artifact in artifacts {
430 let path = artifact.path.clone();
431 let Some(plugin) = registry.load_inspected_native_artifact(&artifact) else {
432 continue;
433 };
434 let Some(packet_references) = registry.inspected_interface_references(
435 &path,
436 &plugin,
437 player_plugin_abi::SOURCE_NORMALIZER_PACKET_INTERFACE_ID.0,
438 ) else {
439 continue;
440 };
441 let Some(resource_references) = registry.inspected_interface_references(
442 &path,
443 &plugin,
444 player_plugin_abi::SOURCE_NORMALIZER_RESOURCE_INTERFACE_ID.0,
445 ) else {
446 continue;
447 };
448 if packet_references.is_empty() && resource_references.is_empty() {
449 registry.push_record(
450 PluginDiagnosticRecord::unsupported_native_interface(
451 path,
452 &plugin,
453 "SourceNormalizerPacket or SourceNormalizerResource",
454 ),
455 None,
456 );
457 continue;
458 }
459 for reference in resource_references {
460 let record = PluginDiagnosticRecord::from_native_source_resource_interface(
461 path.clone(),
462 &plugin,
463 &reference,
464 );
465 registry.push_record(record, Some(reference));
466 }
467 for reference in packet_references {
468 let record = PluginDiagnosticRecord::from_native_source_packet_interface(
469 path.clone(),
470 &plugin,
471 &reference,
472 );
473 registry.push_record(record, Some(reference));
474 }
475 }
476 registry
477 }
478
479 pub fn from_records(records: Vec<PluginDiagnosticRecord>) -> Self {
480 Self {
481 record_references: vec![None; records.len()],
482 records,
483 ..Self::default()
484 }
485 }
486
487 pub fn from_records_with_references(
494 entries: impl IntoIterator<Item = (PluginDiagnosticRecord, Option<PluginReference>)>,
495 ) -> Self {
496 let (records, record_references): (Vec<_>, Vec<_>) = entries.into_iter().unzip();
497 Self {
498 records,
499 record_references,
500 ..Self::default()
501 }
502 }
503
504 fn load_inspected_native_development(
505 &mut self,
506 path: &Path,
507 ) -> Option<Arc<LoadedNativePlugin>> {
508 let plugin = match LoadedNativePlugin::load_development(path) {
509 Ok(plugin) => Arc::new(plugin),
510 Err(error) => {
511 self.push_record(
512 PluginDiagnosticRecord::load_failed(path.to_path_buf(), error),
513 None,
514 );
515 return None;
516 }
517 };
518 self.register_inspected_native(path, plugin)
519 }
520
521 fn load_inspected_native_artifact(
522 &mut self,
523 artifact: &NativePluginArtifact,
524 ) -> Option<Arc<LoadedNativePlugin>> {
525 let path = artifact.path();
526 let plugin = match artifact.load() {
527 Ok(plugin) => plugin,
528 Err(error) => {
529 self.push_record(
530 PluginDiagnosticRecord::load_failed(path.to_path_buf(), error),
531 None,
532 );
533 return None;
534 }
535 };
536 if plugin.plugin_id() != artifact.plugin_id() {
537 let error = PluginRegistryBuildError::PluginIdentityMismatch {
538 path: path.display().to_string(),
539 expected_plugin_id: artifact.plugin_id().to_owned(),
540 actual_plugin_id: plugin.plugin_id().to_owned(),
541 };
542 self.push_record(
543 PluginDiagnosticRecord::load_failed_message(path.to_path_buf(), error.to_string()),
544 None,
545 );
546 return None;
547 }
548 self.register_inspected_native(path, plugin)
549 }
550
551 fn register_inspected_native(
552 &mut self,
553 path: &Path,
554 plugin: Arc<LoadedNativePlugin>,
555 ) -> Option<Arc<LoadedNativePlugin>> {
556 let root_diagnostics = plugin
557 .diagnostics()
558 .iter()
559 .filter(|diagnostic| diagnostic.interface.is_none())
560 .map(|diagnostic| diagnostic.message.clone())
561 .collect::<Vec<_>>();
562 if let Err(error) = self.insert_native(path.to_path_buf(), plugin.clone()) {
563 self.push_record(
564 PluginDiagnosticRecord::load_failed_message(path.to_path_buf(), error.to_string()),
565 None,
566 );
567 return None;
568 }
569 for message in root_diagnostics {
570 self.push_record(
571 PluginDiagnosticRecord::load_failed_message(path.to_path_buf(), message),
572 None,
573 );
574 }
575 Some(plugin)
576 }
577
578 fn inspected_interface_references(
579 &mut self,
580 path: &Path,
581 plugin: &LoadedNativePlugin,
582 interface_id: [u8; 16],
583 ) -> Option<Vec<PluginReference>> {
584 match interface_references(plugin, interface_id) {
585 Ok(references) => Some(references),
586 Err(error) => {
587 self.push_record(
588 PluginDiagnosticRecord::load_failed_message(
589 path.to_path_buf(),
590 format!("validated Root ABI identity could not form a reference: {error}"),
591 ),
592 None,
593 );
594 None
595 }
596 }
597 }
598
599 fn push_record(&mut self, record: PluginDiagnosticRecord, reference: Option<PluginReference>) {
600 self.records.push(record);
601 self.record_references.push(reference);
602 }
603
604 pub fn load_native_development(
606 paths: impl IntoIterator<Item = impl AsRef<Path>>,
607 ) -> Result<Self, PluginRegistryBuildError> {
608 let mut registry = Self::default();
609 for path in paths {
610 let path = path.as_ref().to_path_buf();
611 let plugin = LoadedNativePlugin::load_development(&path).map_err(|source| {
612 PluginRegistryBuildError::Load {
613 path: path.display().to_string(),
614 source,
615 }
616 })?;
617 registry.insert_native(path, Arc::new(plugin))?;
618 }
619 Ok(registry)
620 }
621
622 pub fn load_native_artifacts(
623 artifacts: impl IntoIterator<Item = NativePluginArtifact>,
624 ) -> Result<Self, PluginRegistryBuildError> {
625 let mut registry = Self::default();
626 registry.extend_native_artifacts(artifacts)?;
627 Ok(registry)
628 }
629
630 fn extend_native_artifacts(
631 &mut self,
632 artifacts: impl IntoIterator<Item = NativePluginArtifact>,
633 ) -> Result<(), PluginRegistryBuildError> {
634 for artifact in artifacts {
635 let plugin = artifact
636 .load()
637 .map_err(|source| PluginRegistryBuildError::Load {
638 path: artifact.path.display().to_string(),
639 source,
640 })?;
641 let path = artifact.path;
642 if plugin.plugin_id() != artifact.plugin_id {
643 return Err(PluginRegistryBuildError::PluginIdentityMismatch {
644 path: path.display().to_string(),
645 expected_plugin_id: artifact.plugin_id,
646 actual_plugin_id: plugin.plugin_id().to_owned(),
647 });
648 }
649 self.insert_native(path, plugin)?;
650 }
651 Ok(())
652 }
653
654 #[cfg(feature = "wasm")]
655 pub fn load_wasm_artifacts(
656 artifacts: impl IntoIterator<Item = WasmPluginArtifact>,
657 ) -> Result<Self, PluginRegistryBuildError> {
658 let mut registry = Self::default();
659 registry.extend_wasm_artifacts(artifacts)?;
660 Ok(registry)
661 }
662
663 #[cfg(feature = "wasm")]
664 pub fn load_artifacts(
665 native_artifacts: impl IntoIterator<Item = NativePluginArtifact>,
666 wasm_artifacts: impl IntoIterator<Item = WasmPluginArtifact>,
667 ) -> Result<Self, PluginRegistryBuildError> {
668 let mut registry = Self::default();
669 registry.extend_native_artifacts(native_artifacts)?;
670 registry.extend_wasm_artifacts(wasm_artifacts)?;
671 Ok(registry)
672 }
673
674 #[cfg(feature = "installed-catalog")]
675 pub fn load_verified_installed_catalog(
676 catalog: &VerifiedInstalledPluginCatalog,
677 ) -> Result<Self, PluginRegistryBuildError> {
678 let mut registry = Self::default();
679 #[cfg(feature = "wasm")]
680 let mut wasm_runtime = None;
681 for artifact in catalog.artifacts() {
682 match artifact.transport() {
683 PluginArtifactTransport::Native => {
684 let plugin = LoadedNativePlugin::load_host_verified(artifact.snapshot_path())
685 .map_err(|source| PluginRegistryBuildError::Load {
686 path: artifact.installed_path().display().to_string(),
687 source,
688 })?;
689 if plugin.plugin_id() != artifact.plugin_id() {
690 return Err(PluginRegistryBuildError::PluginIdentityMismatch {
691 path: artifact.installed_path().display().to_string(),
692 expected_plugin_id: artifact.plugin_id().to_owned(),
693 actual_plugin_id: plugin.plugin_id().to_owned(),
694 });
695 }
696 validate_installed_native_capabilities(artifact, &plugin)?;
697 registry
698 .insert_native(artifact.installed_path().to_path_buf(), Arc::new(plugin))?;
699 }
700 PluginArtifactTransport::Wasm => {
701 #[cfg(feature = "wasm")]
702 {
703 let declarations = installed_wasm_declarations(artifact)?;
704 let declared = WasmPluginArtifact::new(
705 artifact.plugin_id(),
706 artifact.snapshot_path(),
707 declarations,
708 )
709 .map_err(|error| {
710 PluginRegistryBuildError::InstalledCatalog {
711 message: error.to_string(),
712 }
713 })?;
714 let runtime = match wasm_runtime.as_ref() {
715 Some(runtime) => runtime,
716 None => {
717 wasm_runtime =
718 Some(WasmPluginRuntime::new().map_err(|source| {
719 PluginRegistryBuildError::WasmRuntime { source }
720 })?);
721 wasm_runtime.as_ref().ok_or_else(|| {
722 PluginRegistryBuildError::InstalledCatalog {
723 message: "WASM runtime initialization was lost".to_owned(),
724 }
725 })?
726 }
727 };
728 let plugin =
729 LoadedWasmPlugin::load(&declared, runtime).map_err(|source| {
730 PluginRegistryBuildError::WasmLoad {
731 path: artifact.installed_path().display().to_string(),
732 plugin_id: artifact.plugin_id().to_owned(),
733 source,
734 }
735 })?;
736 registry.insert_wasm(
737 artifact.installed_path().to_path_buf(),
738 Arc::new(plugin),
739 )?;
740 }
741 #[cfg(not(feature = "wasm"))]
742 return Err(PluginRegistryBuildError::InstalledWasmUnsupported {
743 plugin_id: artifact.plugin_id().to_owned(),
744 });
745 }
746 }
747 }
748 Ok(registry)
749 }
750
751 #[cfg(feature = "wasm")]
752 fn extend_wasm_artifacts(
753 &mut self,
754 artifacts: impl IntoIterator<Item = WasmPluginArtifact>,
755 ) -> Result<(), PluginRegistryBuildError> {
756 let mut artifacts = artifacts.into_iter().peekable();
757 if artifacts.peek().is_none() {
758 return Ok(());
759 }
760 let runtime = WasmPluginRuntime::new()
761 .map_err(|source| PluginRegistryBuildError::WasmRuntime { source })?;
762 for artifact in artifacts {
763 let path = artifact.path().to_path_buf();
764 let plugin_id = artifact.plugin_id().to_owned();
765 let plugin = LoadedWasmPlugin::load(&artifact, &runtime).map_err(|source| {
766 PluginRegistryBuildError::WasmLoad {
767 path: path.display().to_string(),
768 plugin_id,
769 source,
770 }
771 })?;
772 self.insert_wasm(path, Arc::new(plugin))?;
773 }
774 Ok(())
775 }
776
777 fn insert_native(
778 &mut self,
779 artifact_path: PathBuf,
780 plugin: Arc<LoadedNativePlugin>,
781 ) -> Result<(), PluginRegistryBuildError> {
782 let identity = PluginIdentityKey {
783 transport: PluginTransport::Native,
784 plugin_id: plugin.plugin_id().to_owned(),
785 };
786 if let Some(first_path) = self.plugin_paths.get(&identity) {
787 return Err(PluginRegistryBuildError::DuplicatePluginIdentity {
788 transport: identity.transport,
789 plugin_id: identity.plugin_id,
790 first_path: first_path.display().to_string(),
791 duplicate_path: artifact_path.display().to_string(),
792 });
793 }
794
795 let mut pending = Vec::with_capacity(plugin.interfaces().len());
796 for interface in plugin.interfaces() {
797 let key = PluginInterfaceKey {
798 transport: PluginTransport::Native,
799 plugin_id: plugin.plugin_id().to_owned(),
800 interface_id: interface.metadata.interface_id,
801 instance_id: interface.metadata.instance_id.clone(),
802 };
803 if self.interface_index.contains_key(&key)
804 || pending
805 .iter()
806 .any(|(pending_key, _): &(PluginInterfaceKey, _)| pending_key == &key)
807 {
808 return Err(PluginRegistryBuildError::DuplicateInterfaceIdentity {
809 transport: key.transport,
810 plugin_id: key.plugin_id,
811 interface_id: key.interface_id,
812 instance_id: key.instance_id,
813 });
814 }
815 pending.push((
816 key,
817 RegisteredPluginInterface {
818 artifact_path: artifact_path.clone(),
819 transport: PluginTransport::Native,
820 plugin_id: plugin.plugin_id().to_owned(),
821 interface: interface.clone(),
822 },
823 ));
824 }
825
826 self.plugin_paths.insert(identity.clone(), artifact_path);
827 self.plugins.insert(identity, plugin);
828 for (key, interface) in pending {
829 let index = self.interfaces.len();
830 self.interfaces.push(interface);
831 self.interface_index.insert(key, index);
832 }
833 Ok(())
834 }
835
836 #[cfg(feature = "wasm")]
837 fn insert_wasm(
838 &mut self,
839 artifact_path: PathBuf,
840 plugin: Arc<LoadedWasmPlugin>,
841 ) -> Result<(), PluginRegistryBuildError> {
842 let identity = PluginIdentityKey {
843 transport: PluginTransport::Wasm,
844 plugin_id: plugin.plugin_id().to_owned(),
845 };
846 if let Some(first_path) = self.plugin_paths.get(&identity) {
847 return Err(PluginRegistryBuildError::DuplicatePluginIdentity {
848 transport: identity.transport,
849 plugin_id: identity.plugin_id,
850 first_path: first_path.display().to_string(),
851 duplicate_path: artifact_path.display().to_string(),
852 });
853 }
854
855 let mut pending = Vec::with_capacity(plugin.interfaces().len());
856 for interface in plugin.interfaces() {
857 let key = PluginInterfaceKey {
858 transport: PluginTransport::Wasm,
859 plugin_id: plugin.plugin_id().to_owned(),
860 interface_id: interface.metadata.interface_id,
861 instance_id: interface.metadata.instance_id.clone(),
862 };
863 if self.interface_index.contains_key(&key)
864 || pending
865 .iter()
866 .any(|(pending_key, _): &(PluginInterfaceKey, _)| pending_key == &key)
867 {
868 return Err(PluginRegistryBuildError::DuplicateInterfaceIdentity {
869 transport: key.transport,
870 plugin_id: key.plugin_id,
871 interface_id: key.interface_id,
872 instance_id: key.instance_id,
873 });
874 }
875 pending.push((
876 key,
877 RegisteredPluginInterface {
878 artifact_path: artifact_path.clone(),
879 transport: PluginTransport::Wasm,
880 plugin_id: plugin.plugin_id().to_owned(),
881 interface: interface.clone(),
882 },
883 ));
884 }
885
886 self.plugin_paths.insert(identity.clone(), artifact_path);
887 self.wasm_plugins.insert(identity, plugin);
888 for (key, interface) in pending {
889 let index = self.interfaces.len();
890 self.interfaces.push(interface);
891 self.interface_index.insert(key, index);
892 }
893 Ok(())
894 }
895
896 pub fn registered_interfaces(&self) -> &[RegisteredPluginInterface] {
897 &self.interfaces
898 }
899
900 pub fn post_download_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
901 self.references_for_interface(player_plugin_abi::POST_DOWNLOAD_PROCESSOR_INTERFACE_ID.0)
902 }
903
904 pub fn pipeline_event_hook_references(
905 &self,
906 ) -> Result<Vec<PluginReference>, PluginSelectionError> {
907 self.references_for_interface(player_plugin_abi::PIPELINE_EVENT_HOOK_INTERFACE_ID.0)
908 }
909
910 pub fn benchmark_sink_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
911 self.references_for_interface(player_plugin_abi::BENCHMARK_SINK_INTERFACE_ID.0)
912 }
913
914 pub fn native_decoder_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
915 self.references_for_interface(player_plugin_abi::NATIVE_DECODER_INTERFACE_ID.0)
916 }
917
918 pub fn frame_processor_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
919 self.references_for_interface(player_plugin_abi::FRAME_PROCESSOR_INTERFACE_ID.0)
920 }
921
922 pub fn audio_processor_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
923 self.references_for_interface(player_plugin_abi::AUDIO_PROCESSOR_INTERFACE_ID.0)
924 }
925
926 pub fn source_packet_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
927 self.references_for_interface(player_plugin_abi::SOURCE_NORMALIZER_PACKET_INTERFACE_ID.0)
928 }
929
930 pub fn source_resource_references(&self) -> Result<Vec<PluginReference>, PluginSelectionError> {
931 self.references_for_interface(player_plugin_abi::SOURCE_NORMALIZER_RESOURCE_INTERFACE_ID.0)
932 }
933
934 fn references_for_interface(
935 &self,
936 interface_id: [u8; 16],
937 ) -> Result<Vec<PluginReference>, PluginSelectionError> {
938 self.interfaces
939 .iter()
940 .filter(|interface| {
941 interface.interface.state == PluginInterfaceState::Available
942 && interface.interface.metadata.interface_id == interface_id
943 })
944 .map(|interface| {
945 PluginReference::new(
946 interface.plugin_id.clone(),
947 Some(interface.interface.metadata.instance_id.clone()),
948 interface.transport,
949 )
950 .map_err(|_| PluginSelectionError::InvalidLoadedIdentity {
951 plugin_id: interface.plugin_id.clone(),
952 instance_id: interface.interface.metadata.instance_id.clone(),
953 })
954 })
955 .collect()
956 }
957
958 pub fn resolve_post_download(
959 &self,
960 reference: &PluginReference,
961 ) -> Result<ResolvedPluginCapability<dyn PostDownloadProcessor>, PluginSelectionError> {
962 self.validate_invocation(reference, PluginInvocationWorkload::Offline)?;
963 let plugin = self.plugin_for(reference)?;
964 let (instance_id, capability) = plugin.resolve_post_download_selected(reference)?;
965 self.resolved(reference, instance_id, capability)
966 }
967
968 pub fn resolve_pipeline_event_hook(
969 &self,
970 reference: &PluginReference,
971 ) -> Result<ResolvedPluginCapability<dyn PipelineEventHook>, PluginSelectionError> {
972 self.validate_invocation(reference, PluginInvocationWorkload::Observer)?;
973 if reference.transport() == PluginTransport::Wasm {
974 #[cfg(feature = "wasm")]
975 {
976 let plugin = self.wasm_plugin_for(reference)?;
977 plugin.select_pipeline_event_hook(reference)?;
978 return Err(PluginSelectionError::ScopeRequired {
979 plugin_id: reference.plugin_id().to_owned(),
980 interface: "PipelineEventHook",
981 });
982 }
983 #[cfg(not(feature = "wasm"))]
984 {
985 return Err(PluginSelectionError::PluginNotFound {
986 plugin_id: reference.plugin_id().to_owned(),
987 transport: PluginTransport::Wasm,
988 });
989 }
990 }
991 let plugin = self.plugin_for(reference)?;
992 let (instance_id, capability) = plugin.resolve_pipeline_event_hook_selected(reference)?;
993 self.resolved(reference, instance_id, capability)
994 }
995
996 pub fn resolve_pipeline_event_hook_in_scope(
999 &self,
1000 reference: &PluginReference,
1001 _scope: &PluginScope,
1002 ) -> Result<ResolvedPluginCapability<dyn PipelineEventHook>, PluginSelectionError> {
1003 self.validate_invocation(reference, PluginInvocationWorkload::Observer)?;
1004 if reference.transport() == PluginTransport::Wasm {
1005 #[cfg(feature = "wasm")]
1006 {
1007 let plugin = self.wasm_plugin_for(reference)?;
1008 let (instance_id, adapter) = plugin.instantiate_pipeline_event_hook(reference)?;
1009 let owner = adapter.clone();
1010 if let Err(source) = _scope.add_fallible_owner_disposer(move || {
1011 owner
1012 .close(std::time::Duration::from_millis(
1013 WASM_PLUGIN_FLUSH_TIMEOUT_MILLIS,
1014 ))
1015 .map_err(|_| PluginOwnerDisposalError)
1016 }) {
1017 let _ = adapter.close(std::time::Duration::from_millis(
1018 WASM_PLUGIN_FLUSH_TIMEOUT_MILLIS,
1019 ));
1020 return Err(PluginSelectionError::ScopeRegistration {
1021 plugin_id: reference.plugin_id().to_owned(),
1022 interface: "PipelineEventHook",
1023 source,
1024 });
1025 }
1026 let capability: Arc<dyn PipelineEventHook> = adapter;
1027 return self.resolved(reference, instance_id, capability);
1028 }
1029 #[cfg(not(feature = "wasm"))]
1030 {
1031 return Err(PluginSelectionError::PluginNotFound {
1032 plugin_id: reference.plugin_id().to_owned(),
1033 transport: PluginTransport::Wasm,
1034 });
1035 }
1036 }
1037 self.resolve_pipeline_event_hook(reference)
1038 }
1039
1040 pub fn resolve_benchmark_sink(
1041 &self,
1042 reference: &PluginReference,
1043 ) -> Result<ResolvedPluginCapability<dyn BenchmarkSink>, PluginSelectionError> {
1044 self.validate_invocation(reference, PluginInvocationWorkload::Offline)?;
1045 if reference.transport() == PluginTransport::Wasm {
1046 #[cfg(feature = "wasm")]
1047 {
1048 let plugin = self.wasm_plugin_for(reference)?;
1049 plugin.select_benchmark_sink(reference)?;
1050 return Err(PluginSelectionError::ScopeRequired {
1051 plugin_id: reference.plugin_id().to_owned(),
1052 interface: "BenchmarkSink",
1053 });
1054 }
1055 #[cfg(not(feature = "wasm"))]
1056 {
1057 return Err(PluginSelectionError::PluginNotFound {
1058 plugin_id: reference.plugin_id().to_owned(),
1059 transport: PluginTransport::Wasm,
1060 });
1061 }
1062 }
1063 let plugin = self.plugin_for(reference)?;
1064 let (instance_id, capability) = plugin.resolve_benchmark_sink_selected(reference)?;
1065 self.resolved(reference, instance_id, capability)
1066 }
1067
1068 pub fn resolve_benchmark_sink_in_scope(
1071 &self,
1072 reference: &PluginReference,
1073 _scope: &PluginScope,
1074 ) -> Result<ResolvedPluginCapability<dyn BenchmarkSink>, PluginSelectionError> {
1075 self.validate_invocation(reference, PluginInvocationWorkload::Offline)?;
1076 if reference.transport() == PluginTransport::Wasm {
1077 #[cfg(feature = "wasm")]
1078 {
1079 let plugin = self.wasm_plugin_for(reference)?;
1080 let (instance_id, adapter) = plugin.instantiate_benchmark_sink(reference)?;
1081 let owner = adapter.clone();
1082 if let Err(source) = _scope.add_fallible_owner_disposer(move || {
1083 owner
1084 .close(std::time::Duration::from_millis(
1085 WASM_PLUGIN_FLUSH_TIMEOUT_MILLIS,
1086 ))
1087 .map(|_| ())
1088 .map_err(|_| PluginOwnerDisposalError)
1089 }) {
1090 let _ = adapter.close(std::time::Duration::from_millis(
1091 WASM_PLUGIN_FLUSH_TIMEOUT_MILLIS,
1092 ));
1093 return Err(PluginSelectionError::ScopeRegistration {
1094 plugin_id: reference.plugin_id().to_owned(),
1095 interface: "BenchmarkSink",
1096 source,
1097 });
1098 }
1099 let capability: Arc<dyn BenchmarkSink> = adapter;
1100 return self.resolved(reference, instance_id, capability);
1101 }
1102 #[cfg(not(feature = "wasm"))]
1103 {
1104 return Err(PluginSelectionError::PluginNotFound {
1105 plugin_id: reference.plugin_id().to_owned(),
1106 transport: PluginTransport::Wasm,
1107 });
1108 }
1109 }
1110 self.resolve_benchmark_sink(reference)
1111 }
1112
1113 pub fn resolve_native_decoder(
1114 &self,
1115 reference: &PluginReference,
1116 ) -> Result<ResolvedPluginCapability<dyn NativeDecoderPluginFactory>, PluginSelectionError>
1117 {
1118 self.validate_invocation(reference, PluginInvocationWorkload::RealtimeMedia)?;
1119 let plugin = self.plugin_for(reference)?;
1120 let (instance_id, capability) = plugin.resolve_native_decoder_selected(reference)?;
1121 self.resolved(reference, instance_id, capability)
1122 }
1123
1124 pub fn resolve_frame_processor(
1125 &self,
1126 reference: &PluginReference,
1127 ) -> Result<ResolvedPluginCapability<dyn FrameProcessorPluginFactory>, PluginSelectionError>
1128 {
1129 self.validate_invocation(reference, PluginInvocationWorkload::RealtimeMedia)?;
1130 let plugin = self.plugin_for(reference)?;
1131 let (instance_id, capability) = plugin.resolve_frame_processor_selected(reference)?;
1132 self.resolved(reference, instance_id, capability)
1133 }
1134
1135 pub fn resolve_audio_processor(
1136 &self,
1137 reference: &PluginReference,
1138 ) -> Result<ResolvedPluginCapability<dyn AudioProcessorPluginFactory>, PluginSelectionError>
1139 {
1140 self.validate_invocation(reference, PluginInvocationWorkload::RealtimeMedia)?;
1141 let plugin = self.plugin_for(reference)?;
1142 let (instance_id, capability) = plugin.resolve_audio_processor_selected(reference)?;
1143 self.resolved(reference, instance_id, capability)
1144 }
1145
1146 pub fn resolve_source_packet(
1147 &self,
1148 reference: &PluginReference,
1149 ) -> Result<
1150 ResolvedPluginCapability<dyn SourceNormalizerPacketPluginFactory>,
1151 PluginSelectionError,
1152 > {
1153 self.validate_invocation(reference, PluginInvocationWorkload::RealtimeMedia)?;
1154 let plugin = self.plugin_for(reference)?;
1155 let (instance_id, capability) = plugin.resolve_source_packet_selected(reference)?;
1156 self.resolved(reference, instance_id, capability)
1157 }
1158
1159 pub fn resolve_source_resource(
1160 &self,
1161 reference: &PluginReference,
1162 ) -> Result<
1163 ResolvedPluginCapability<dyn SourceNormalizerResourcePluginFactory>,
1164 PluginSelectionError,
1165 > {
1166 self.validate_invocation(reference, PluginInvocationWorkload::RealtimeMedia)?;
1167 let plugin = self.plugin_for(reference)?;
1168 let (instance_id, capability) = plugin.resolve_source_resource_selected(reference)?;
1169 self.resolved(reference, instance_id, capability)
1170 }
1171
1172 fn validate_invocation(
1173 &self,
1174 reference: &PluginReference,
1175 workload: PluginInvocationWorkload,
1176 ) -> Result<(), PluginSelectionError> {
1177 PluginInvocationPolicy::standard()
1178 .validate(workload, reference.transport())
1179 .map_err(PluginSelectionError::from)
1180 }
1181
1182 pub fn native_artifact(
1186 &self,
1187 reference: &PluginReference,
1188 ) -> Result<NativePluginArtifact, PluginSelectionError> {
1189 let plugin = self.plugin_for(reference)?;
1190 let identity = PluginIdentityKey {
1191 transport: reference.transport(),
1192 plugin_id: reference.plugin_id().to_owned(),
1193 };
1194 let path = self.plugin_paths.get(&identity).cloned().ok_or_else(|| {
1195 PluginSelectionError::PluginNotFound {
1196 plugin_id: reference.plugin_id().to_owned(),
1197 transport: reference.transport(),
1198 }
1199 })?;
1200 Ok(NativePluginArtifact {
1201 plugin_id: reference.plugin_id().to_owned(),
1202 path,
1203 loaded: Some(plugin),
1204 })
1205 }
1206
1207 fn plugin_for(
1208 &self,
1209 reference: &PluginReference,
1210 ) -> Result<Arc<LoadedNativePlugin>, PluginSelectionError> {
1211 let identity = PluginIdentityKey {
1212 transport: reference.transport(),
1213 plugin_id: reference.plugin_id().to_owned(),
1214 };
1215 self.plugins
1216 .get(&identity)
1217 .cloned()
1218 .ok_or_else(|| PluginSelectionError::PluginNotFound {
1219 plugin_id: reference.plugin_id().to_owned(),
1220 transport: reference.transport(),
1221 })
1222 }
1223
1224 #[cfg(feature = "wasm")]
1225 fn wasm_plugin_for(
1226 &self,
1227 reference: &PluginReference,
1228 ) -> Result<Arc<LoadedWasmPlugin>, PluginSelectionError> {
1229 let identity = PluginIdentityKey {
1230 transport: reference.transport(),
1231 plugin_id: reference.plugin_id().to_owned(),
1232 };
1233 self.wasm_plugins.get(&identity).cloned().ok_or_else(|| {
1234 PluginSelectionError::PluginNotFound {
1235 plugin_id: reference.plugin_id().to_owned(),
1236 transport: reference.transport(),
1237 }
1238 })
1239 }
1240
1241 fn resolved<T: ?Sized>(
1242 &self,
1243 reference: &PluginReference,
1244 instance_id: String,
1245 capability: Arc<T>,
1246 ) -> Result<ResolvedPluginCapability<T>, PluginSelectionError> {
1247 let canonical = PluginReference::new(
1248 reference.plugin_id(),
1249 Some(instance_id.clone()),
1250 reference.transport(),
1251 )
1252 .map_err(|_| PluginSelectionError::InvalidLoadedIdentity {
1253 plugin_id: reference.plugin_id().to_owned(),
1254 instance_id,
1255 })?;
1256 Ok(ResolvedPluginCapability {
1257 reference: canonical,
1258 capability,
1259 })
1260 }
1261
1262 pub fn records(&self) -> &[PluginDiagnosticRecord] {
1263 &self.records
1264 }
1265
1266 pub fn reference_for_record(
1268 &self,
1269 record: &PluginDiagnosticRecord,
1270 ) -> Option<&PluginReference> {
1271 self.records
1272 .iter()
1273 .position(|candidate| std::ptr::eq(candidate, record))
1274 .and_then(|index| self.record_references.get(index))
1275 .and_then(Option::as_ref)
1276 }
1277
1278 pub fn best_decoder_for(
1279 &self,
1280 request: &DecoderPluginMatchRequest,
1281 ) -> Option<&PluginDiagnosticRecord> {
1282 self.records.iter().find(|record| {
1283 record.status == PluginDiagnosticStatus::DecoderSupported
1284 && decoder_capability_summary(record).is_some_and(|capabilities| {
1285 capabilities.typed_codecs.iter().any(|codec| {
1286 codec.media_kind == request.media_kind
1287 && codec.codec.eq_ignore_ascii_case(&request.codec)
1288 })
1289 })
1290 })
1291 }
1292
1293 pub fn best_native_decoder_for(
1294 &self,
1295 request: &DecoderPluginMatchRequest,
1296 ) -> Option<&PluginDiagnosticRecord> {
1297 self.records.iter().find(|record| {
1298 record.status == PluginDiagnosticStatus::DecoderSupported
1299 && decoder_capability_summary(record).is_some_and(|capabilities| {
1300 capabilities.supports_native_frame_output
1301 && capabilities.typed_codecs.iter().any(|codec| {
1302 codec.media_kind == request.media_kind
1303 && codec.codec.eq_ignore_ascii_case(&request.codec)
1304 })
1305 })
1306 })
1307 }
1308
1309 pub fn best_pcm_audio_decoder_for(
1310 &self,
1311 request: &DecoderPluginMatchRequest,
1312 ) -> Option<&PluginDiagnosticRecord> {
1313 if request.media_kind != DecoderMediaKind::Audio {
1314 return None;
1315 }
1316 self.records.iter().find(|record| {
1317 record.status == PluginDiagnosticStatus::DecoderSupported
1318 && decoder_capability_summary(record).is_some_and(|capabilities| {
1319 capabilities.supports_pcm_frames
1320 && capabilities.typed_codecs.iter().any(|codec| {
1321 codec.media_kind == request.media_kind
1322 && codec.codec.eq_ignore_ascii_case(&request.codec)
1323 })
1324 })
1325 })
1326 }
1327
1328 pub fn supports_decoder(&self, request: &DecoderPluginMatchRequest) -> bool {
1329 self.best_decoder_for(request).is_some()
1330 }
1331
1332 pub fn supports_native_decoder(&self, request: &DecoderPluginMatchRequest) -> bool {
1333 self.best_native_decoder_for(request).is_some()
1334 }
1335
1336 pub fn supports_pcm_audio_decoder(&self, request: &DecoderPluginMatchRequest) -> bool {
1337 self.best_pcm_audio_decoder_for(request).is_some()
1338 }
1339
1340 pub fn frame_processor_supported_plugin_names(&self) -> Vec<&str> {
1341 self.records
1342 .iter()
1343 .filter(|record| record.status == PluginDiagnosticStatus::FrameProcessorSupported)
1344 .filter_map(|record| record.plugin_name.as_deref())
1345 .collect()
1346 }
1347
1348 pub fn source_normalizer_supported_plugin_names(&self) -> Vec<&str> {
1349 self.records
1350 .iter()
1351 .filter(|record| record.status == PluginDiagnosticStatus::SourceNormalizerSupported)
1352 .filter_map(|record| record.plugin_name.as_deref())
1353 .collect()
1354 }
1355
1356 pub fn best_source_normalizer(&self) -> Option<&PluginDiagnosticRecord> {
1357 self.records
1358 .iter()
1359 .find(|record| record.status == PluginDiagnosticStatus::SourceNormalizerSupported)
1360 }
1361
1362 pub fn best_source_normalizer_packet(&self) -> Option<&PluginDiagnosticRecord> {
1363 self.records.iter().find(|record| {
1364 record.status == PluginDiagnosticStatus::SourceNormalizerSupported
1365 && source_normalizer_packet_capability_summary(record).is_some()
1366 })
1367 }
1368
1369 pub fn best_source_normalizer_packet_for_profile(
1370 &self,
1371 runtime_profile: &str,
1372 ) -> Option<&PluginDiagnosticRecord> {
1373 self.records.iter().find(|record| {
1374 record.status == PluginDiagnosticStatus::SourceNormalizerSupported
1375 && source_normalizer_packet_capability_summary(record).is_some_and(|capabilities| {
1376 capabilities
1377 .supported_runtime_profiles
1378 .iter()
1379 .any(|profile| profile.eq_ignore_ascii_case(runtime_profile))
1380 })
1381 })
1382 }
1383
1384 pub fn best_source_normalizer_resource(&self) -> Option<&PluginDiagnosticRecord> {
1385 self.records.iter().find(|record| {
1386 record.status == PluginDiagnosticStatus::SourceNormalizerSupported
1387 && source_normalizer_resource_capability_summary(record).is_some()
1388 })
1389 }
1390
1391 pub fn best_source_normalizer_resource_for_profile(
1392 &self,
1393 runtime_profile: &str,
1394 ) -> Option<&PluginDiagnosticRecord> {
1395 self.records.iter().find(|record| {
1396 record.status == PluginDiagnosticStatus::SourceNormalizerSupported
1397 && source_normalizer_resource_capability_summary(record).is_some_and(
1398 |capabilities| {
1399 capabilities
1400 .supported_runtime_profiles
1401 .iter()
1402 .any(|profile| profile.eq_ignore_ascii_case(runtime_profile))
1403 },
1404 )
1405 })
1406 }
1407
1408 pub fn best_source_normalizer_for_profile(
1409 &self,
1410 runtime_profile: &str,
1411 ) -> Option<&PluginDiagnosticRecord> {
1412 self.records.iter().find(|record| {
1413 record.status == PluginDiagnosticStatus::SourceNormalizerSupported
1414 && (source_normalizer_resource_capability_summary(record).is_some_and(
1415 |capabilities| {
1416 capabilities
1417 .supported_runtime_profiles
1418 .iter()
1419 .any(|profile| profile.eq_ignore_ascii_case(runtime_profile))
1420 },
1421 ) || source_normalizer_packet_capability_summary(record).is_some_and(
1422 |capabilities| {
1423 capabilities
1424 .supported_runtime_profiles
1425 .iter()
1426 .any(|profile| profile.eq_ignore_ascii_case(runtime_profile))
1427 },
1428 ))
1429 })
1430 }
1431
1432 pub fn decoder_supported_plugin_names(&self) -> Vec<&str> {
1433 self.records
1434 .iter()
1435 .filter(|record| record.status == PluginDiagnosticStatus::DecoderSupported)
1436 .filter_map(|record| record.plugin_name.as_deref())
1437 .collect()
1438 }
1439
1440 pub fn diagnostic_notes(&self) -> Vec<String> {
1441 self.records
1442 .iter()
1443 .filter(|record| {
1444 !matches!(
1445 record.status,
1446 PluginDiagnosticStatus::DecoderSupported
1447 | PluginDiagnosticStatus::FrameProcessorSupported
1448 | PluginDiagnosticStatus::SourceNormalizerSupported
1449 )
1450 })
1451 .map(PluginDiagnosticRecord::summary)
1452 .collect()
1453 }
1454
1455 pub fn report(&self) -> PluginRegistryReport {
1456 let mut report = PluginRegistryReport {
1457 total: self.records.len(),
1458 ..PluginRegistryReport::default()
1459 };
1460
1461 for record in &self.records {
1462 match record.status {
1463 PluginDiagnosticStatus::Loaded => {
1464 report.loaded += 1;
1465 report.diagnostic_notes.push(record.summary());
1466 }
1467 PluginDiagnosticStatus::LoadFailed => {
1468 report.failed += 1;
1469 report.diagnostic_notes.push(record.summary());
1470 }
1471 PluginDiagnosticStatus::UnsupportedKind => {
1472 report.loaded += 1;
1473 report.unsupported_kind += 1;
1474 report.diagnostic_notes.push(record.summary());
1475 }
1476 PluginDiagnosticStatus::DecoderSupported => {
1477 report.loaded += 1;
1478 report.decoder_supported += 1;
1479 if report.best_supported_decoder_name.is_none() {
1480 report.best_supported_decoder_name = record.plugin_name.clone();
1481 }
1482 }
1483 PluginDiagnosticStatus::DecoderUnsupported => {
1484 report.loaded += 1;
1485 report.decoder_unsupported += 1;
1486 report.diagnostic_notes.push(record.summary());
1487 }
1488 PluginDiagnosticStatus::FrameProcessorSupported => {
1489 report.loaded += 1;
1490 report.frame_processor_supported += 1;
1491 if report.best_supported_frame_processor_name.is_none() {
1492 report.best_supported_frame_processor_name = record.plugin_name.clone();
1493 }
1494 }
1495 PluginDiagnosticStatus::FrameProcessorUnsupported => {
1496 report.loaded += 1;
1497 report.frame_processor_unsupported += 1;
1498 report.diagnostic_notes.push(record.summary());
1499 }
1500 PluginDiagnosticStatus::SourceNormalizerSupported => {
1501 report.loaded += 1;
1502 report.source_normalizer_supported += 1;
1503 if report.best_supported_source_normalizer_name.is_none() {
1504 report.best_supported_source_normalizer_name = record.plugin_name.clone();
1505 }
1506 }
1507 PluginDiagnosticStatus::SourceNormalizerUnsupported => {
1508 report.loaded += 1;
1509 report.source_normalizer_unsupported += 1;
1510 report.diagnostic_notes.push(record.summary());
1511 }
1512 }
1513 }
1514
1515 report
1516 }
1517}
1518
1519#[cfg(feature = "installed-catalog")]
1520fn validate_installed_native_capabilities(
1521 artifact: &VerifiedInstalledArtifact,
1522 plugin: &LoadedNativePlugin,
1523) -> Result<(), PluginRegistryBuildError> {
1524 let declared = artifact
1525 .capabilities()
1526 .iter()
1527 .map(|capability| {
1528 let interface_id = uuid::Uuid::parse_str(&capability.interface_id)
1529 .map(|interface_id| *interface_id.as_bytes())
1530 .map_err(|error| PluginRegistryBuildError::InstalledCatalog {
1531 message: format!(
1532 "invalid interface UUID '{}': {error}",
1533 capability.interface_id
1534 ),
1535 })?;
1536 Ok((
1537 interface_id,
1538 capability.instance_id.clone(),
1539 capability.interface_major,
1540 capability.interface_minor,
1541 ))
1542 })
1543 .collect::<Result<BTreeSet<_>, PluginRegistryBuildError>>()?;
1544 let actual = plugin
1545 .interfaces()
1546 .iter()
1547 .filter(|interface| interface.state == PluginInterfaceState::Available)
1548 .map(|interface| {
1549 (
1550 interface.metadata.interface_id,
1551 interface.metadata.instance_id.clone(),
1552 interface.metadata.major,
1553 interface.metadata.minor,
1554 )
1555 })
1556 .collect::<BTreeSet<_>>();
1557 if declared != actual {
1558 return Err(PluginRegistryBuildError::InstalledCapabilityMismatch {
1559 path: artifact.installed_path().display().to_string(),
1560 plugin_id: artifact.plugin_id().to_owned(),
1561 message: format!("declared {declared:?}, Root ABI reported {actual:?}"),
1562 });
1563 }
1564 Ok(())
1565}
1566
1567#[cfg(all(feature = "installed-catalog", feature = "wasm"))]
1568fn installed_wasm_declarations(
1569 artifact: &VerifiedInstalledArtifact,
1570) -> Result<Vec<WasmPluginInterfaceDeclaration>, PluginRegistryBuildError> {
1571 artifact
1572 .capabilities()
1573 .iter()
1574 .map(|capability| {
1575 let interface_id = uuid::Uuid::parse_str(&capability.interface_id)
1576 .map(|interface_id| *interface_id.as_bytes())
1577 .map_err(|error| PluginRegistryBuildError::InstalledCatalog {
1578 message: format!(
1579 "invalid interface UUID '{}': {error}",
1580 capability.interface_id
1581 ),
1582 })?;
1583 Ok(WasmPluginInterfaceDeclaration::new(
1584 interface_id,
1585 capability.interface_major,
1586 capability.interface_minor,
1587 capability.instance_id.clone(),
1588 ))
1589 })
1590 .collect()
1591}