use std::any::Any;
use std::cell::RefCell;
use std::ffi::{c_char, CStr, CString};
use std::io;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::str::FromStr;
use mx_remote::{BayUid, ControlError, DeviceUid, SendError};
pub const MXR_UID_STRING_LEN: usize = 36;
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum mxr_result_t {
MXR_OK = 0,
MXR_ERR_INVALID_ARGUMENT = -1,
MXR_ERR_NOT_FOUND = -2,
MXR_ERR_PROTOCOL_TOO_OLD = -3,
MXR_ERR_NOT_CONNECTED = -4,
MXR_ERR_IO = -5,
MXR_ERR_UNSUPPORTED = -6,
MXR_ERR_NOT_REPORTED = -7,
MXR_ERR_PANIC = -8,
}
#[repr(i8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum mxr_tribool_t {
MXR_UNKNOWN = -1,
MXR_FALSE = 0,
MXR_TRUE = 1,
}
impl From<Option<bool>> for mxr_tribool_t {
fn from(value: Option<bool>) -> Self {
match value {
None => Self::MXR_UNKNOWN,
Some(false) => Self::MXR_FALSE,
Some(true) => Self::MXR_TRUE,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct mxr_uid_t {
pub bytes: [u8; 16],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct mxr_bay_uid_t {
pub device: mxr_uid_t,
pub port: u16,
}
impl From<DeviceUid> for mxr_uid_t {
fn from(uid: DeviceUid) -> Self {
Self {
bytes: *uid.as_bytes(),
}
}
}
impl From<mxr_uid_t> for DeviceUid {
fn from(uid: mxr_uid_t) -> Self {
DeviceUid::from_array(uid.bytes)
}
}
impl From<BayUid> for mxr_bay_uid_t {
fn from(bay: BayUid) -> Self {
Self {
device: bay.device.into(),
port: bay.port,
}
}
}
impl From<mxr_bay_uid_t> for BayUid {
fn from(bay: mxr_bay_uid_t) -> Self {
BayUid::new(bay.device.into(), bay.port)
}
}
pub(crate) fn bay_or_zero(bay: Option<BayUid>) -> mxr_bay_uid_t {
bay.map(mxr_bay_uid_t::from).unwrap_or_default()
}
thread_local! {
static LAST_ERROR: RefCell<CString> = RefCell::new(c"".to_owned());
}
pub(crate) fn set_last_error(message: &str) {
let text = CString::new(message).unwrap_or_else(|_| c"error text contains a NUL".to_owned());
LAST_ERROR.with(|slot| *slot.borrow_mut() = text);
}
pub(crate) fn guard<T>(fallback: T, body: impl FnOnce() -> T) -> T {
match catch_unwind(AssertUnwindSafe(body)) {
Ok(value) => value,
Err(payload) => {
set_last_error(&format!("panic: {}", panic_text(&payload)));
fallback
}
}
}
fn panic_text(payload: &Box<dyn Any + Send>) -> &str {
if let Some(s) = payload.downcast_ref::<&str>() {
return s;
}
if let Some(s) = payload.downcast_ref::<String>() {
return s;
}
"no message"
}
pub(crate) fn fail(code: mxr_result_t, message: &str) -> mxr_result_t {
set_last_error(message);
code
}
pub(crate) fn from_control(result: Result<(), ControlError>) -> mxr_result_t {
let error = match result {
Ok(()) => return mxr_result_t::MXR_OK,
Err(e) => e,
};
let code = match &error {
ControlError::UnknownDevice(_)
| ControlError::UnknownBay(_)
| ControlError::UnknownSource(_) => mxr_result_t::MXR_ERR_NOT_FOUND,
ControlError::Unsupported(_) => mxr_result_t::MXR_ERR_UNSUPPORTED,
ControlError::NotReported(_) => mxr_result_t::MXR_ERR_NOT_REPORTED,
ControlError::Send(e) => send_code(e),
_ => mxr_result_t::MXR_ERR_UNSUPPORTED,
};
fail(code, &error.to_string())
}
pub(crate) fn from_send(result: Result<(), SendError>) -> mxr_result_t {
match result {
Ok(()) => mxr_result_t::MXR_OK,
Err(e) => fail(send_code(&e), &e.to_string()),
}
}
fn send_code(error: &SendError) -> mxr_result_t {
match error {
SendError::ProtocolTooOld { .. } => mxr_result_t::MXR_ERR_PROTOCOL_TOO_OLD,
SendError::NotConnected => mxr_result_t::MXR_ERR_NOT_CONNECTED,
SendError::Io(_) => mxr_result_t::MXR_ERR_IO,
_ => mxr_result_t::MXR_ERR_IO,
}
}
pub(crate) fn from_io(result: io::Result<()>) -> mxr_result_t {
match result {
Ok(()) => mxr_result_t::MXR_OK,
Err(e) => fail(mxr_result_t::MXR_ERR_IO, &e.to_string()),
}
}
pub(crate) fn put_str(dst: &mut [c_char], text: &str) {
let room = dst.len().saturating_sub(1);
let mut end = room.min(text.len());
while end > 0 && !text.is_char_boundary(end) {
end -= 1;
}
let taken = text.as_bytes().get(..end).unwrap_or_default();
for (slot, byte) in dst.iter_mut().zip(taken) {
*slot = *byte as c_char;
}
for slot in dst.iter_mut().skip(end) {
*slot = 0;
}
}
pub(crate) unsafe fn opt_str<'a>(ptr: *const c_char) -> Result<Option<&'a str>, mxr_result_t> {
if ptr.is_null() {
return Ok(None);
}
match unsafe { CStr::from_ptr(ptr) }.to_str() {
Ok(s) => Ok(Some(s)),
Err(_) => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"string is not valid UTF-8",
)),
}
}
pub(crate) unsafe fn req_str<'a>(ptr: *const c_char) -> Result<&'a str, mxr_result_t> {
match unsafe { opt_str(ptr) }? {
Some(s) => Ok(s),
None => Err(fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"a required string argument was null",
)),
}
}
#[no_mangle]
pub extern "C" fn mxr_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}
#[no_mangle]
pub extern "C" fn mxr_last_error() -> *const c_char {
LAST_ERROR.with(|slot| slot.borrow().as_ptr())
}
#[no_mangle]
pub extern "C" fn mxr_uid_is_zero(uid: mxr_uid_t) -> bool {
uid.bytes == [0; 16]
}
#[no_mangle]
pub unsafe extern "C" fn mxr_uid_to_string(
uid: mxr_uid_t,
out: *mut c_char,
cap: usize,
) -> mxr_result_t {
guard(mxr_result_t::MXR_ERR_PANIC, || {
if out.is_null() || cap < MXR_UID_STRING_LEN {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"uid buffer is null or shorter than MXR_UID_STRING_LEN",
);
}
let dst = unsafe { std::slice::from_raw_parts_mut(out, cap) };
put_str(dst, &DeviceUid::from(uid).to_string());
mxr_result_t::MXR_OK
})
}
#[no_mangle]
pub unsafe extern "C" fn mxr_uid_from_string(
text: *const c_char,
out: *mut mxr_uid_t,
) -> mxr_result_t {
guard(mxr_result_t::MXR_ERR_PANIC, || {
if out.is_null() {
return fail(
mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
"uid output pointer is null",
);
}
let text = match unsafe { req_str(text) } {
Ok(s) => s,
Err(code) => return code,
};
match DeviceUid::from_str(text) {
Ok(uid) => {
unsafe { *out = uid.into() };
mxr_result_t::MXR_OK
}
Err(e) => fail(mxr_result_t::MXR_ERR_INVALID_ARGUMENT, &e.to_string()),
}
})
}