use std::{
cell::RefCell,
ffi::{CString, c_char, c_void},
sync::LazyLock,
};
use url::Url;
use crate::{Error, Id, moq_protocol_error};
#[allow(non_camel_case_types)]
pub type moq_status_callback = Option<extern "C" fn(user_data: *mut c_void, code: i32)>;
pub static RUNTIME: LazyLock<tokio::runtime::Handle> = LazyLock::new(|| {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let handle = runtime.handle().clone();
std::thread::Builder::new()
.name("libmoq".into())
.spawn(move || {
runtime.block_on(std::future::pending::<()>());
})
.expect("failed to spawn runtime thread");
handle
});
pub fn enter<C: ReturnCode, F: FnOnce() -> C>(f: F) -> i32 {
let _guard = RUNTIME.enter();
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(ret) => {
record_error(&ret);
ret.code()
}
Err(_) => {
record_error(&Error::Panic);
Error::Panic.code()
}
}
}
#[derive(Clone, Copy)]
pub struct OnStatus {
user_data: *mut c_void,
on_status: extern "C" fn(user_data: *mut c_void, code: i32),
}
impl OnStatus {
pub unsafe fn new(user_data: *mut c_void, on_status: moq_status_callback) -> Result<Self, Error> {
Ok(Self {
user_data,
on_status: on_status.ok_or(Error::InvalidPointer)?,
})
}
pub fn call<C: ReturnCode>(&self, ret: C) {
record_error(&ret);
let code = ret.code();
(self.on_status)(self.user_data, code);
}
}
unsafe impl Send for OnStatus {}
pub trait ReturnCode {
fn code(&self) -> i32;
fn error(&self) -> Option<&Error> {
None
}
}
impl ReturnCode for () {
fn code(&self) -> i32 {
0
}
}
impl ReturnCode for i32 {
fn code(&self) -> i32 {
*self
}
}
impl ReturnCode for Result<i32, Error> {
fn code(&self) -> i32 {
match self {
Ok(code) if *code < 0 => Error::InvalidCode.code(),
Ok(code) => *code,
Err(e) => e.code(),
}
}
fn error(&self) -> Option<&Error> {
self.as_ref().err()
}
}
impl ReturnCode for Result<usize, Error> {
fn code(&self) -> i32 {
match self {
Ok(code) => i32::try_from(*code).unwrap_or_else(|_| Error::InvalidCode.code()),
Err(e) => e.code(),
}
}
fn error(&self) -> Option<&Error> {
self.as_ref().err()
}
}
impl ReturnCode for Result<Id, Error> {
fn code(&self) -> i32 {
match self {
Ok(id) => i32::from(*id),
Err(e) => e.code(),
}
}
fn error(&self) -> Option<&Error> {
self.as_ref().err()
}
}
impl ReturnCode for Result<(), Error> {
fn code(&self) -> i32 {
match self {
Ok(()) => 0,
Err(e) => e.code(),
}
}
fn error(&self) -> Option<&Error> {
self.as_ref().err()
}
}
impl ReturnCode for usize {
fn code(&self) -> i32 {
i32::try_from(*self).unwrap_or_else(|_| Error::InvalidCode.code())
}
}
impl ReturnCode for Id {
fn code(&self) -> i32 {
i32::from(*self)
}
}
struct LastError {
message: CString,
protocol: Option<moq_protocol_error>,
}
thread_local! {
static LAST_ERROR: RefCell<Option<LastError>> = const { RefCell::new(None) };
}
fn record_error<C: ReturnCode>(ret: &C) {
let Some(err) = ret.error() else { return };
if let Ok(msg) = CString::new(err.to_string()) {
LAST_ERROR.with(|cell| {
*cell.borrow_mut() = Some(LastError {
message: msg,
protocol: err.protocol(),
});
});
}
}
pub fn last_error_ptr() -> *const c_char {
LAST_ERROR.with(|cell| {
cell.borrow()
.as_ref()
.map_or(std::ptr::null(), |err| err.message.as_ptr())
})
}
pub fn last_protocol(out: &mut moq_protocol_error) -> bool {
LAST_ERROR.with(|cell| match cell.borrow().as_ref().and_then(|err| err.protocol) {
Some(protocol) => {
*out = protocol;
true
}
None => false,
})
}
pub fn parse_id(id: u32) -> Result<Id, Error> {
Id::try_from(id)
}
pub fn parse_id_optional(id: u32) -> Result<Option<Id>, Error> {
match id {
0 => Ok(None),
id => Ok(Some(parse_id(id)?)),
}
}
pub fn parse_url(url: *const c_char, url_len: usize) -> Result<Url, Error> {
let url = unsafe { parse_str(url, url_len)? };
Ok(Url::parse(url)?)
}
pub unsafe fn parse_str<'a>(cstr: *const c_char, cstr_len: usize) -> Result<&'a str, Error> {
let slice = unsafe { parse_slice(cstr.cast::<u8>(), cstr_len)? };
let string = std::str::from_utf8(slice)?;
Ok(string)
}
pub unsafe fn parse_str_optional<'a>(cstr: *const c_char, cstr_len: usize) -> Result<Option<&'a str>, Error> {
if cstr.is_null() {
return Ok(None);
}
let string = unsafe { parse_str(cstr, cstr_len)? };
Ok((!string.is_empty()).then_some(string))
}
pub unsafe fn parse_strings(items: *const crate::moq_string, count: usize) -> Result<Vec<String>, Error> {
if items.is_null() {
if count == 0 {
return Ok(Vec::new());
}
return Err(Error::InvalidPointer);
}
let items = unsafe { std::slice::from_raw_parts(items, count) };
items
.iter()
.map(|item| Ok(unsafe { parse_str(item.data, item.len)? }.to_string()))
.collect()
}
pub unsafe fn parse_slice<'a>(data: *const u8, size: usize) -> Result<&'a [u8], Error> {
if data.is_null() {
if size == 0 {
return Ok(&[]);
}
return Err(Error::InvalidPointer);
}
let data = unsafe { std::slice::from_raw_parts(data, size) };
Ok(data)
}