Skip to main content

sim_lib_stream_host/
provider.rs

1//! Loadable audio provider registration seam.
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::sync::Arc;
5
6use sim_kernel::{
7    CapabilityName, Cx, Error, Export, Lib, LibManifest, LibSource, LoaderRegistry, Result, Symbol,
8};
9
10use crate::{AudioRouter, AudioSite};
11
12/// Stable entry symbol exported by a loadable audio provider.
13///
14/// The host resolves this symbol after the kernel loader acquires the provider
15/// artifact, then calls the entry point once with an [`AudioProviderRegistrar`].
16pub const AUDIO_PROVIDER_ENTRY_V1: &str = "sim_audio_provider_v1";
17
18/// ABI version currently accepted by the host-side audio provider registrar.
19pub const AUDIO_PROVIDER_ABI_VERSION: u32 = 1;
20
21/// Capability required before a native audio provider can be loaded.
22pub fn native_audio_provider_capability() -> CapabilityName {
23    CapabilityName::new("audio.provider.native")
24}
25
26/// Host-supplied registration surface for loadable audio providers.
27pub trait AudioProviderRegistrar {
28    /// Registers or replaces a native audio site owned by the provider.
29    fn register_site(&mut self, site: Arc<dyn AudioSite>);
30
31    /// Returns the host ABI version accepted by this registrar.
32    fn host_abi_version(&self) -> u32;
33}
34
35/// Host-callable audio provider entry point.
36pub type AudioProviderEntry = fn(&mut dyn AudioProviderRegistrar) -> Result<()>;
37
38/// Proof-only table of provider entry points keyed by provider library id.
39///
40/// Native provider discovery is driven by `Export::Site` manifest records. This
41/// table exists for in-process tests and trusted host-registered proof loaders
42/// that need an FFI-free way to call the provider entry symbol.
43#[derive(Clone, Default)]
44pub struct AudioProviderProofEntries {
45    entries: BTreeMap<Symbol, AudioProviderEntry>,
46}
47
48impl AudioProviderProofEntries {
49    /// Builds an empty proof entry table.
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Adds a proof provider entry, builder-style.
55    pub fn with_proof_entry(mut self, provider: Symbol, entry: AudioProviderEntry) -> Self {
56        self.insert(provider, entry);
57        self
58    }
59
60    /// Adds or replaces a proof provider entry.
61    pub fn insert(&mut self, provider: Symbol, entry: AudioProviderEntry) {
62        self.entries.insert(provider, entry);
63    }
64
65    fn entry(&self, provider: &Symbol) -> Result<AudioProviderEntry> {
66        self.entries.get(provider).copied().ok_or_else(|| {
67            Error::HostError(format!(
68                "audio provider {} has no proof entry for {}",
69                provider, AUDIO_PROVIDER_ENTRY_V1
70            ))
71        })
72    }
73}
74
75/// Host that loads audio providers through the kernel loader and registers sites.
76pub struct AudioProviderHost<'a> {
77    cx: &'a mut Cx,
78    loaders: &'a LoaderRegistry,
79    proof_entries: AudioProviderProofEntries,
80}
81
82impl<'a> AudioProviderHost<'a> {
83    /// Builds a provider host over a context and loader registry.
84    pub fn new(cx: &'a mut Cx, loaders: &'a LoaderRegistry) -> Self {
85        Self {
86            cx,
87            loaders,
88            proof_entries: AudioProviderProofEntries::new(),
89        }
90    }
91
92    /// Adds a proof-only provider entry, builder-style.
93    pub fn with_proof_entry(mut self, provider: Symbol, entry: AudioProviderEntry) -> Self {
94        self.proof_entries.insert(provider, entry);
95        self
96    }
97
98    /// Compatibility alias for existing in-process proof loaders.
99    ///
100    /// New proof code should use [`Self::with_proof_entry`]. Native provider
101    /// discovery still requires declared `audio/site` exports before this
102    /// entry point is called.
103    pub fn with_entry(self, provider: Symbol, entry: AudioProviderEntry) -> Self {
104        self.with_proof_entry(provider, entry)
105    }
106
107    /// Acquires a provider through the loader registry and registers its sites.
108    pub fn load_into(&mut self, source: LibSource, router: &mut AudioRouter) -> Result<()> {
109        self.cx.require(&native_audio_provider_capability())?;
110        let lib = self.loaders.load_lib(self.cx, source)?;
111        register_provider_lib(lib.as_ref(), &self.proof_entries, router)
112    }
113}
114
115fn register_provider_lib(
116    lib: &dyn Lib,
117    proof_entries: &AudioProviderProofEntries,
118    router: &mut AudioRouter,
119) -> Result<()> {
120    let manifest = lib.manifest();
121    let site_exports = AudioProviderSiteExports::from_manifest(&manifest)?;
122    let entry = proof_entries.entry(&manifest.id)?;
123    let mut registrar =
124        RouterAudioProviderRegistrar::for_provider(router, manifest.id, site_exports.symbols);
125    entry(&mut registrar)?;
126    registrar.finish()
127}
128
129struct AudioProviderSiteExports {
130    symbols: BTreeSet<Symbol>,
131}
132
133impl AudioProviderSiteExports {
134    fn from_manifest(manifest: &LibManifest) -> Result<Self> {
135        let mut symbols = BTreeSet::new();
136        for export in &manifest.exports {
137            if let Export::Site { symbol, .. } = export {
138                if symbol.namespace.as_deref() != Some("audio/site") {
139                    return Err(Error::HostError(format!(
140                        "audio provider {} declared invalid site export {}; expected audio/site",
141                        manifest.id, symbol
142                    )));
143                }
144                symbols.insert(symbol.clone());
145            }
146        }
147        if symbols.is_empty() {
148            return Err(Error::HostError(format!(
149                "audio provider {} declared no audio/site exports",
150                manifest.id
151            )));
152        }
153        Ok(Self { symbols })
154    }
155}
156
157/// Registrar that wires provider sites into an [`AudioRouter`].
158pub struct RouterAudioProviderRegistrar<'a> {
159    router: &'a mut AudioRouter,
160    owner: Symbol,
161    allowed_sites: Option<BTreeSet<Symbol>>,
162    registration_error: Option<Error>,
163}
164
165impl<'a> RouterAudioProviderRegistrar<'a> {
166    /// Builds a proof registrar over the supplied audio router.
167    pub fn new(router: &'a mut AudioRouter) -> Self {
168        Self {
169            router,
170            owner: Symbol::qualified("audio/provider", "proof"),
171            allowed_sites: None,
172            registration_error: None,
173        }
174    }
175
176    /// Builds a provider-owned registrar constrained to declared site exports.
177    pub fn for_provider(
178        router: &'a mut AudioRouter,
179        owner: Symbol,
180        allowed_sites: BTreeSet<Symbol>,
181    ) -> Self {
182        Self {
183            router,
184            owner,
185            allowed_sites: Some(allowed_sites),
186            registration_error: None,
187        }
188    }
189
190    /// Returns the first registration error recorded by this registrar.
191    pub fn finish(self) -> Result<()> {
192        match self.registration_error {
193            Some(err) => Err(err),
194            None => Ok(()),
195        }
196    }
197
198    fn record_error(&mut self, error: Error) {
199        if self.registration_error.is_none() {
200            self.registration_error = Some(error);
201        }
202    }
203}
204
205impl AudioProviderRegistrar for RouterAudioProviderRegistrar<'_> {
206    fn register_site(&mut self, site: Arc<dyn AudioSite>) {
207        if self.registration_error.is_some() {
208            return;
209        }
210        let site_symbol = site.key().0.clone();
211        if let Some(allowed_sites) = &self.allowed_sites
212            && !allowed_sites.contains(&site_symbol)
213        {
214            self.record_error(Error::HostError(format!(
215                "audio provider {} registered undeclared site {}",
216                self.owner, site_symbol
217            )));
218            return;
219        }
220        if let Err(err) = self.router.register_owned(self.owner.clone(), site) {
221            self.record_error(err);
222        }
223    }
224
225    fn host_abi_version(&self) -> u32 {
226        AUDIO_PROVIDER_ABI_VERSION
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use std::sync::Arc;
233
234    use crate::{
235        AudioDeviceCard, AudioProviderRegistrar, AudioRouter, AudioSiteKey, FakeBackend,
236        ModeledAudioSite, RouterAudioProviderRegistrar,
237    };
238
239    #[test]
240    fn provider_seam_registers_site_into_router() {
241        let key = AudioSiteKey::new("audio/native/jack-spike");
242        let card = AudioDeviceCard::modeled(key.clone(), "JACK Provider Spike");
243        let site = Arc::new(ModeledAudioSite::new(card, Arc::new(FakeBackend::new())));
244        let mut router = AudioRouter::new();
245
246        {
247            let mut registrar = RouterAudioProviderRegistrar::new(&mut router);
248            assert_eq!(registrar.host_abi_version(), 1);
249            registrar.register_site(site);
250            registrar.finish().unwrap();
251        }
252
253        assert!(router.site(&key).is_some());
254    }
255}