use crate::{
NR_ENTRY_UNKNOWN, NrBufferLease, NrBytes, NrHostVTable, NrOwnedBytes, NrStatus, NrStr, NrVec,
};
use std::ffi::c_void;
use std::future::Future;
use std::sync::atomic::{AtomicPtr, Ordering};
static HOST_CTX: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static HOST_VTABLE: AtomicPtr<NrHostVTable> = AtomicPtr::new(std::ptr::null_mut());
fn host() -> Option<(*mut c_void, &'static NrHostVTable)> {
let ctx = HOST_CTX.load(Ordering::Acquire);
let vtable = HOST_VTABLE.load(Ordering::Acquire);
if ctx.is_null() || vtable.is_null() {
return None;
}
Some((ctx, unsafe { &*vtable }))
}
pub fn send_result(sid: u64, status: NrStatus, data: NrVec<u8>) -> NrStatus {
let Some((ctx, vtable)) = host() else {
return NrStatus::Invalid;
};
unsafe { (vtable.send_result)(ctx, sid, status, data) }
}
pub fn send_result_owned(sid: u64, status: NrStatus, data: NrOwnedBytes) -> NrStatus {
let Some((ctx, vtable)) = host() else {
return NrStatus::Invalid;
};
unsafe { (vtable.send_result_owned)(ctx, sid, status, data) }
}
pub fn acquire_result_buffer(sid: u64, capacity: u64) -> NrBufferLease {
let Some((ctx, vtable)) = host() else {
return NrBufferLease::failed();
};
unsafe { (vtable.acquire_result_buffer)(ctx, sid, capacity) }
}
pub fn commit_result_buffer(
sid: u64,
status: NrStatus,
token: u64,
initialized_len: u64,
) -> NrStatus {
let Some((ctx, vtable)) = host() else {
return NrStatus::Invalid;
};
unsafe { (vtable.commit_result_buffer)(ctx, sid, status, token, initialized_len) }
}
pub struct Session<'a> {
sid: u64,
entry_id: u32,
entry: &'static str,
payload: &'a [u8],
}
impl Session<'_> {
pub fn sid(&self) -> u64 {
self.sid
}
pub fn entry(&self) -> &'static str {
self.entry
}
pub fn entry_id(&self) -> u32 {
self.entry_id
}
pub fn payload(&self) -> &[u8] {
self.payload
}
pub fn send_frame(&mut self, data: Vec<u8>) -> NrStatus {
send_result(self.sid, NrStatus::Ok, NrVec::from_vec(data))
}
pub fn end_stream(&mut self, data: Vec<u8>) -> NrStatus {
send_result(self.sid, NrStatus::StreamEnd, NrVec::from_vec(data))
}
}
pub enum Reply {
Bytes(Vec<u8>),
Text(String),
Static(&'static [u8]),
None,
Fail(NrStatus),
}
pub struct AsyncSession {
sid: u64,
entry_id: u32,
entry: &'static str,
payload: Vec<u8>,
}
impl AsyncSession {
pub fn sid(&self) -> u64 {
self.sid
}
pub fn entry(&self) -> &'static str {
self.entry
}
pub fn entry_id(&self) -> u32 {
self.entry_id
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
pub fn into_payload(self) -> Vec<u8> {
self.payload
}
pub fn send_frame(&self, data: Vec<u8>) -> NrStatus {
send_result(self.sid, NrStatus::Ok, NrVec::from_vec(data))
}
pub fn end_stream(&self, data: Vec<u8>) -> NrStatus {
send_result(self.sid, NrStatus::StreamEnd, NrVec::from_vec(data))
}
}
pub type AsyncTask = std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
pub trait Plugin: Send + Sync + 'static {
type Ctx: Send + 'static;
const ENTRIES: &'static [&'static str];
const ASYNC_ENTRIES: &'static [&'static str] = &[];
fn new() -> Self
where
Self: Sized;
fn new_ctx(&self) -> Self::Ctx;
fn on_call(&self, session: &mut Session<'_>, ctx: &mut Self::Ctx) -> Reply;
fn on_async_call(
&self,
session: AsyncSession,
ctx: Self::Ctx,
) -> impl Future<Output = Reply> + Send {
let _ = (session, ctx);
async { Reply::Fail(NrStatus::Unsupported) }
}
fn spawn_async(&self, task: AsyncTask) {
std::thread::spawn(move || block_on(task));
}
fn on_stream_data(&self, _sid: u64, _data: &[u8]) -> NrStatus {
NrStatus::Unsupported
}
fn on_stream_close(&self, _sid: u64) -> NrStatus {
NrStatus::Unsupported
}
fn on_shutdown(&self) {}
}
fn block_on(mut task: AsyncTask) {
struct ThreadWaker(std::thread::Thread);
impl std::task::Wake for ThreadWaker {
fn wake(self: std::sync::Arc<Self>) {
self.0.unpark();
}
}
let waker = std::task::Waker::from(std::sync::Arc::new(ThreadWaker(std::thread::current())));
let mut cx = std::task::Context::from_waker(&waker);
while task.as_mut().poll(&mut cx).is_pending() {
std::thread::park();
}
}
#[doc(hidden)]
pub mod __glue {
use super::*;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::OnceLock;
pub fn init<P: Plugin>(
instance: &'static OnceLock<P>,
host_ctx: *mut c_void,
host_vtable: *const NrHostVTable,
) -> NrStatus {
catch_unwind(AssertUnwindSafe(|| {
if host_ctx.is_null() || host_vtable.is_null() {
return NrStatus::Invalid;
}
HOST_CTX.store(host_ctx, Ordering::Release);
HOST_VTABLE.store(host_vtable.cast_mut(), Ordering::Release);
instance.get_or_init(P::new);
NrStatus::Ok
}))
.unwrap_or(NrStatus::Panic)
}
pub fn shutdown<P: Plugin>(instance: &'static OnceLock<P>) {
let _ = catch_unwind(AssertUnwindSafe(|| {
if let Some(plugin) = instance.get() {
plugin.on_shutdown();
}
HOST_VTABLE.store(std::ptr::null_mut(), Ordering::Release);
HOST_CTX.store(std::ptr::null_mut(), Ordering::Release);
}));
}
fn entry_index<P: Plugin>(entry_bytes: &[u8]) -> Option<u32> {
if let Some(id) = P::ENTRIES
.iter()
.position(|name| name.as_bytes() == entry_bytes)
{
return Some(id as u32);
}
P::ASYNC_ENTRIES
.iter()
.position(|name| name.as_bytes() == entry_bytes)
.map(|index| (P::ENTRIES.len() + index) as u32)
}
pub fn resolve_entry<P: Plugin>(entry: NrStr) -> u32 {
catch_unwind(AssertUnwindSafe(|| {
let entry_bytes = match unsafe { entry.as_bytes() } {
Ok(bytes) => bytes,
Err(_) => return NR_ENTRY_UNKNOWN,
};
entry_index::<P>(entry_bytes).unwrap_or(NR_ENTRY_UNKNOWN)
}))
.unwrap_or(NR_ENTRY_UNKNOWN)
}
pub unsafe fn handle<P: Plugin>(
instance: &'static OnceLock<P>,
entry: NrStr,
sid: u64,
payload: NrBytes,
) -> NrStatus {
catch_unwind(AssertUnwindSafe(|| {
let entry_bytes = match unsafe { entry.as_bytes() } {
Ok(bytes) => bytes,
Err(_) => return NrStatus::Invalid,
};
let Some(id) = entry_index::<P>(entry_bytes) else {
return NrStatus::Invalid;
};
unsafe { dispatch::<P>(instance, id, sid, payload) }
}))
.unwrap_or(NrStatus::Panic)
}
pub unsafe fn handle_by_id<P: Plugin>(
instance: &'static OnceLock<P>,
id: u32,
sid: u64,
payload: NrBytes,
) -> NrStatus {
catch_unwind(AssertUnwindSafe(|| {
if id as usize >= P::ENTRIES.len() + P::ASYNC_ENTRIES.len() {
return NrStatus::Invalid;
}
unsafe { dispatch::<P>(instance, id, sid, payload) }
}))
.unwrap_or(NrStatus::Panic)
}
fn deliver(sid: u64, reply: Reply) -> NrStatus {
match reply {
Reply::Bytes(data) => send_result(sid, NrStatus::Ok, NrVec::from_vec(data)),
Reply::Text(text) => send_result(sid, NrStatus::Ok, NrVec::from_string(text)),
Reply::Static(data) => {
send_result_owned(sid, NrStatus::Ok, NrOwnedBytes::from_static(data))
}
Reply::None => NrStatus::Ok,
Reply::Fail(status) => status,
}
}
unsafe fn dispatch<P: Plugin>(
instance: &'static OnceLock<P>,
id: u32,
sid: u64,
payload: NrBytes,
) -> NrStatus {
let Some(plugin) = instance.get() else {
return NrStatus::Invalid;
};
let payload = match unsafe { payload.as_slice() } {
Ok(payload) => payload,
Err(_) => return NrStatus::Invalid,
};
let sync_count = P::ENTRIES.len() as u32;
if id < sync_count {
let mut session = Session {
sid,
entry_id: id,
entry: P::ENTRIES[id as usize],
payload,
};
let mut ctx = plugin.new_ctx();
return deliver(sid, plugin.on_call(&mut session, &mut ctx));
}
let session = AsyncSession {
sid,
entry_id: id,
entry: P::ASYNC_ENTRIES[(id - sync_count) as usize],
payload: payload.to_vec(),
};
let ctx = plugin.new_ctx();
let handler = plugin.on_async_call(session, ctx);
let mut task: AsyncTask = Box::pin(async move {
match handler.await {
Reply::Fail(status) => {
let _ = send_result(sid, status, NrVec::from_vec(Vec::new()));
}
reply => {
let _ = deliver(sid, reply);
}
}
});
let mut poll_ctx = std::task::Context::from_waker(std::task::Waker::noop());
if task.as_mut().poll(&mut poll_ctx).is_ready() {
return NrStatus::Ok;
}
plugin.spawn_async(task);
NrStatus::Ok
}
pub unsafe fn stream_data<P: Plugin>(
instance: &'static OnceLock<P>,
sid: u64,
data: NrBytes,
) -> NrStatus {
catch_unwind(AssertUnwindSafe(|| {
let Some(plugin) = instance.get() else {
return NrStatus::Invalid;
};
let data = match unsafe { data.as_slice() } {
Ok(data) => data,
Err(_) => return NrStatus::Invalid,
};
plugin.on_stream_data(sid, data)
}))
.unwrap_or(NrStatus::Panic)
}
pub fn stream_close<P: Plugin>(instance: &'static OnceLock<P>, sid: u64) -> NrStatus {
catch_unwind(AssertUnwindSafe(|| {
let Some(plugin) = instance.get() else {
return NrStatus::Invalid;
};
plugin.on_stream_close(sid)
}))
.unwrap_or(NrStatus::Panic)
}
}
#[macro_export]
macro_rules! export_plugin {
($ty:ty) => {
static NY_PLUGIN_INSTANCE: std::sync::OnceLock<$ty> = std::sync::OnceLock::new();
static PLUGIN_VTABLE: $crate::NrPluginVTable = $crate::NrPluginVTable {
init: Some(ny_plugin_init_wrapper),
handle: Some(ny_plugin_handle_wrapper),
shutdown: Some(ny_plugin_shutdown_wrapper),
stream_data: Some(ny_plugin_stream_data_wrapper),
stream_close: Some(ny_plugin_stream_close_wrapper),
resolve_entry: Some(ny_plugin_resolve_entry_wrapper),
handle_by_id: Some(ny_plugin_handle_by_id_wrapper),
};
static PLUGIN_INFO: $crate::NrPluginInfo = $crate::NrPluginInfo {
abi_version: $crate::ABI_VERSION,
struct_size: std::mem::size_of::<$crate::NrPluginInfo>() as u32,
name: $crate::NrStr::from_static(env!("CARGO_PKG_NAME")),
version: $crate::NrStr::from_static(env!("CARGO_PKG_VERSION")),
plugin_ctx: std::ptr::null_mut(),
vtable: &PLUGIN_VTABLE,
};
#[unsafe(no_mangle)]
pub extern "C" fn nylon_ring_get_plugin() -> *const $crate::NrPluginInfo {
&PLUGIN_INFO
}
unsafe extern "C" fn ny_plugin_init_wrapper(
host_ctx: *mut std::ffi::c_void,
host_vtable: *const $crate::NrHostVTable,
) -> $crate::NrStatus {
$crate::plugin::__glue::init::<$ty>(&NY_PLUGIN_INSTANCE, host_ctx, host_vtable)
}
unsafe extern "C" fn ny_plugin_shutdown_wrapper() {
$crate::plugin::__glue::shutdown::<$ty>(&NY_PLUGIN_INSTANCE);
}
unsafe extern "C" fn ny_plugin_handle_wrapper(
entry: $crate::NrStr,
sid: u64,
payload: $crate::NrBytes,
) -> $crate::NrStatus {
unsafe {
$crate::plugin::__glue::handle::<$ty>(&NY_PLUGIN_INSTANCE, entry, sid, payload)
}
}
unsafe extern "C" fn ny_plugin_handle_by_id_wrapper(
id: u32,
sid: u64,
payload: $crate::NrBytes,
) -> $crate::NrStatus {
unsafe {
$crate::plugin::__glue::handle_by_id::<$ty>(&NY_PLUGIN_INSTANCE, id, sid, payload)
}
}
unsafe extern "C" fn ny_plugin_resolve_entry_wrapper(entry: $crate::NrStr) -> u32 {
$crate::plugin::__glue::resolve_entry::<$ty>(entry)
}
unsafe extern "C" fn ny_plugin_stream_data_wrapper(
sid: u64,
data: $crate::NrBytes,
) -> $crate::NrStatus {
unsafe { $crate::plugin::__glue::stream_data::<$ty>(&NY_PLUGIN_INSTANCE, sid, data) }
}
unsafe extern "C" fn ny_plugin_stream_close_wrapper(sid: u64) -> $crate::NrStatus {
$crate::plugin::__glue::stream_close::<$ty>(&NY_PLUGIN_INSTANCE, sid)
}
};
}