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