///|
const ABI_VERSION_V1 : UInt = 1
///|
const ABI_VERSION_V2 : UInt = 2
///|
const MAX_MANIFEST_BYTES : Int = 1024 * 1024
///|
const MAX_COMMAND_BYTES : Int = 1024
///|
const MAX_REQUEST_BYTES : Int = 16 * 1024 * 1024
///|
const MAX_RESPONSE_BYTES : Int = 16 * 1024 * 1024
///|
const WRAPPER_MALFORMED_RESPONSE : Int = -2147483648
///|
const WRAPPER_INVALID_INPUT : Int = -2147483647
///|
/// A failure reported by the fixed Orbit plugin ABI bridge.
pub(all) enum AbiError {
InvalidAddress
IncompatibleAbi(UInt)
ManifestUnavailable
InvalidManifestUtf8
InvalidCommand
RequestTooLarge(Int)
InvalidResponse
PluginStatus(Int)
InstanceUnavailable
Destroyed
ExecutorRequired
ExecutorUnsupported
ExecutorUnavailable
ExecutorQueueFull
DuplicateInvocation
ExecutorClosed
InvocationUnavailable
MalformedEvent
ExecutorStatus(Int)
} derive(Eq, Debug)
///|
pub(all) enum AbiVersion {
V1
V2
} derive(Eq, Debug)
///|
/// The five required addresses obtained from Nanaloveyuki/dynlib symbols.
///
/// Every address is validated before a native call is attempted. The owning
/// dynamic library must remain open until every PluginInstance is destroyed.
pub struct PluginAddresses {
priv abi_version_address : UInt64
priv manifest_address : UInt64
priv create_address : UInt64
priv invoke_address : UInt64
priv destroy_address : UInt64
}
///|
/// A validated plugin ABI descriptor with its copied manifest.
pub struct Plugin {
priv addresses : PluginAddresses
priv manifest : String
priv abi_version : AbiVersion
}
///|
/// A plugin instance created through the fixed v1 ABI.
///
/// Destroying is idempotent. Do not close the source dynlib Library before
/// calling destroy.
pub struct PluginInstance {
priv invoke_address : UInt64
priv destroy_address : UInt64
priv mut handle : UInt64
}
///|
/// Returns the supported plugin ABI version.
pub fn supported_abi_version() -> UInt {
ABI_VERSION_V2
}
///|
/// Returns every ABI version accepted by this host bridge.
pub fn supported_abi_versions() -> Array[UInt] {
[ABI_VERSION_V1, ABI_VERSION_V2]
}
///|
/// Constructs the complete v1 symbol set.
///
/// Use Symbol::address from the published dynlib package for each address.
pub fn PluginAddresses::new(
abi_version_address : UInt64,
manifest_address : UInt64,
create_address : UInt64,
invoke_address : UInt64,
destroy_address : UInt64,
) -> Result[PluginAddresses, AbiError] {
if abi_version_address == 0UL ||
manifest_address == 0UL ||
create_address == 0UL ||
invoke_address == 0UL ||
destroy_address == 0UL {
return Err(InvalidAddress)
}
Ok({
abi_version_address,
manifest_address,
create_address,
invoke_address,
destroy_address,
})
}
///|
/// Obtains the five fixed ABI addresses from published dynlib symbols.
///
/// The backing Library must remain open until every PluginInstance is
/// destroyed. A closed source symbol is rejected before any ABI call.
pub fn PluginAddresses::from_symbols(
abi_version : @dynlib.Symbol,
manifest : @dynlib.Symbol,
create : @dynlib.Symbol,
invoke : @dynlib.Symbol,
destroy : @dynlib.Symbol,
) -> Result[PluginAddresses, AbiError] {
let abi_version_address = match abi_version.address() {
Ok(address) => address
Err(_) => return Err(InvalidAddress)
}
let manifest_address = match manifest.address() {
Ok(address) => address
Err(_) => return Err(InvalidAddress)
}
let create_address = match create.address() {
Ok(address) => address
Err(_) => return Err(InvalidAddress)
}
let invoke_address = match invoke.address() {
Ok(address) => address
Err(_) => return Err(InvalidAddress)
}
let destroy_address = match destroy.address() {
Ok(address) => address
Err(_) => return Err(InvalidAddress)
}
PluginAddresses::new(
abi_version_address, manifest_address, create_address, invoke_address, destroy_address,
)
}
///|
/// Calls only the fixed ABI-version and manifest signatures to validate a
/// plugin. It copies the manifest and verifies its UTF-8, but does not parse
/// JSON.
pub fn Plugin::open(addresses : PluginAddresses) -> Result[Plugin, AbiError] {
let reported_version = native_abi_version(addresses.abi_version_address)
let abi_version = match reported_version {
1 => V1
2 => V2
version => return Err(IncompatibleAbi(version))
}
let manifest_bytes = native_manifest(addresses.manifest_address)
if manifest_bytes.is_empty() || manifest_bytes.length() > MAX_MANIFEST_BYTES {
return Err(ManifestUnavailable)
}
let manifest = @utf8.decode(manifest_bytes) catch {
_ => return Err(InvalidManifestUtf8)
}
Ok({ addresses, manifest, abi_version })
}
///|
/// Returns the exact ABI version reported by the plugin.
pub fn Plugin::abi_version(self : Plugin) -> AbiVersion {
self.abi_version
}
///|
/// Returns the static plugin manifest copied during open.
pub fn Plugin::manifest_json(self : Plugin) -> String {
self.manifest
}
///|
/// Creates one plugin instance with a process-static HostV1. The plugin may
/// retain that host pointer until destroy is called.
pub fn Plugin::create(self : Plugin) -> Result[PluginInstance, AbiError] {
if self.abi_version != V1 {
return Err(ExecutorRequired)
}
let frame = native_create(self.addresses.create_address)
if frame.length() != 16 {
return Err(InvalidResponse)
}
let status = match frame {
[u32le(value), ..] => value.reinterpret_as_int()
_ => return Err(InvalidResponse)
}
if status != 0 {
return Err(PluginStatus(status))
}
let handle = match frame {
[_, _, _, _, _, _, _, _, u64le(value)] => value
_ => return Err(InvalidResponse)
}
if handle == 0UL {
return Err(InstanceUnavailable)
}
Ok({
invoke_address: self.addresses.invoke_address,
destroy_address: self.addresses.destroy_address,
handle,
})
}
///|
fn command_is_valid(command : String, encoded : Bytes) -> Bool {
!command.is_empty() &&
!command.contains_code_unit(0) &&
encoded.length() <= MAX_COMMAND_BYTES
}
///|
/// Invokes the fixed v1 plugin command signature.
///
/// Command is UTF-8 without embedded NUL. Empty requests are passed as a
/// non-null zero-length byte buffer. Successful responses are copied before
/// the host allocator frees the plugin-owned OrbitBuffer.
pub fn PluginInstance::invoke(
self : PluginInstance,
command : String,
request : Bytes,
) -> Result[Bytes, AbiError] {
if self.handle == 0UL {
return Err(Destroyed)
}
if request.length() > MAX_REQUEST_BYTES {
return Err(RequestTooLarge(request.length()))
}
let command_bytes = @utf8.encode(command, bom=false)
if !command_is_valid(command, command_bytes) {
return Err(InvalidCommand)
}
let frame = native_invoke(
self.invoke_address,
self.handle,
command_bytes,
request,
)
if frame.length() < 4 || frame.length() - 4 > MAX_RESPONSE_BYTES {
return Err(InvalidResponse)
}
let status = match frame {
[u32le(value), ..] => value.reinterpret_as_int()
_ => return Err(InvalidResponse)
}
if status == WRAPPER_MALFORMED_RESPONSE || status == WRAPPER_INVALID_INPUT {
return Err(InvalidResponse)
}
if status != 0 {
return Err(PluginStatus(status))
}
Ok(frame.view(start=4).to_owned())
}
///|
/// Destroys the native plugin instance once. Repeated calls are no-ops.
pub fn PluginInstance::destroy(self : PluginInstance) -> Unit {
if self.handle != 0UL {
native_destroy(self.destroy_address, self.handle)
self.handle = 0UL
}
}
///|
/// Returns whether the instance has already been destroyed.
pub fn PluginInstance::is_destroyed(self : PluginInstance) -> Bool {
self.handle == 0UL
}