wascc_host/
capability.rs

1use crate::Result;
2use libloading::Library;
3use libloading::Symbol;
4use std::ffi::OsStr;
5use wascc_codec::{
6    capabilities::{CapabilityDescriptor, CapabilityProvider, OP_GET_CAPABILITY_DESCRIPTOR},
7    deserialize, SYSTEM_ACTOR,
8};
9
10/// Represents a native capability provider compiled as a shared object library.
11/// These plugins are OS- and architecture-specific, so they will be `.so` files on Linux, `.dylib`
12/// files on macOS, etc.
13pub struct NativeCapability {
14    pub(crate) plugin: Box<dyn CapabilityProvider>,
15    pub(crate) binding_name: String,
16    pub(crate) descriptor: CapabilityDescriptor,
17    // This field is solely used to keep the FFI library instance allocated for the same
18    // lifetime as the boxed plugin
19    #[allow(dead_code)]
20    library: Option<Library>,
21}
22
23impl NativeCapability {
24    /// Reads a capability provider from a file. The capability provider must implement the
25    /// correct FFI interface to support waSCC plugins. See [wascc.dev](https://wascc.dev) for
26    /// documentation and tutorials on how to create a native capability provider
27    pub fn from_file<P: AsRef<OsStr>>(
28        filename: P,
29        binding_target_name: Option<String>,
30    ) -> Result<Self> {
31        type PluginCreate = unsafe fn() -> *mut dyn CapabilityProvider;
32
33        let library = Library::new(filename.as_ref())?;
34
35        let plugin = unsafe {
36            let constructor: Symbol<PluginCreate> = library.get(b"__capability_provider_create")?;
37            let boxed_raw = constructor();
38
39            Box::from_raw(boxed_raw)
40        };
41        let descriptor = get_descriptor(&plugin)?;
42        let binding = binding_target_name.unwrap_or("default".to_string());
43        info!(
44            "Loaded native capability provider '{}' v{} ({}) for {}/{}",
45            descriptor.name, descriptor.version, descriptor.revision, descriptor.id, binding
46        );
47
48        Ok(NativeCapability {
49            plugin,
50            descriptor,
51            binding_name: binding,
52            library: Some(library),
53        })
54    }
55
56    /// This function is to be used for _capability embedding_. If you are building a custom
57    /// waSCC host and have a fixed set of capabilities that you want to always be available
58    /// to actors, then you can declare a dependency on the capability provider, enable
59    /// the `static_plugin` feature, and provide an instance of that provider. Be sure to check
60    /// that the provider supports capability embedding.    
61    pub fn from_instance(
62        instance: impl CapabilityProvider,
63        binding_target_name: Option<String>,
64    ) -> Result<Self> {
65        let b: Box<dyn CapabilityProvider> = Box::new(instance);
66        let descriptor = get_descriptor(&b)?;
67        let binding = binding_target_name.unwrap_or("default".to_string());
68
69        info!(
70            "Loaded native capability provider '{}' v{} ({}) for {}/{}",
71            descriptor.name, descriptor.version, descriptor.revision, descriptor.id, binding
72        );
73        Ok(NativeCapability {
74            descriptor,
75            plugin: b,
76            binding_name: binding,
77            library: None,
78        })
79    }
80
81    /// Returns the capability ID (namespace) of the provider
82    pub fn id(&self) -> String {
83        self.descriptor.id.to_string()
84    }
85
86    /// Returns the human-friendly name of the provider
87    pub fn name(&self) -> String {
88        self.descriptor.name.to_string()
89    }
90
91    /// Returns the full descriptor for the capability provider
92    pub fn descriptor(&self) -> &CapabilityDescriptor {
93        &self.descriptor
94    }
95}
96
97fn get_descriptor(plugin: &Box<dyn CapabilityProvider>) -> Result<CapabilityDescriptor> {
98    let res = plugin.handle_call(SYSTEM_ACTOR, OP_GET_CAPABILITY_DESCRIPTOR, &[])?;
99    let descriptor: CapabilityDescriptor = deserialize(&res)?;
100    Ok(descriptor)
101}