1use std::any::Any;
8use std::cell::RefCell;
9use std::ffi::{c_char, CStr, CString};
10use std::io;
11use std::panic::{catch_unwind, AssertUnwindSafe};
12use std::str::FromStr;
13
14use mx_remote::{BayUid, ControlError, DeviceUid, SendError};
15
16pub const MXR_UID_STRING_LEN: usize = 36;
18
19#[repr(i32)]
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum mxr_result_t {
26 MXR_OK = 0,
28 MXR_ERR_INVALID_ARGUMENT = -1,
30 MXR_ERR_NOT_FOUND = -2,
35 MXR_ERR_PROTOCOL_TOO_OLD = -3,
39 MXR_ERR_NOT_CONNECTED = -4,
41 MXR_ERR_IO = -5,
43 MXR_ERR_UNSUPPORTED = -6,
45 MXR_ERR_NOT_REPORTED = -7,
47 MXR_ERR_PANIC = -8,
49}
50
51#[repr(i8)]
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum mxr_tribool_t {
58 MXR_UNKNOWN = -1,
60 MXR_FALSE = 0,
62 MXR_TRUE = 1,
64}
65
66impl From<Option<bool>> for mxr_tribool_t {
67 fn from(value: Option<bool>) -> Self {
68 match value {
69 None => Self::MXR_UNKNOWN,
70 Some(false) => Self::MXR_FALSE,
71 Some(true) => Self::MXR_TRUE,
72 }
73 }
74}
75
76#[repr(C)]
81#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82pub struct mxr_uid_t {
83 pub bytes: [u8; 16],
85}
86
87#[repr(C)]
89#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
90pub struct mxr_bay_uid_t {
91 pub device: mxr_uid_t,
93 pub port: u16,
95}
96
97impl From<DeviceUid> for mxr_uid_t {
98 fn from(uid: DeviceUid) -> Self {
99 Self {
100 bytes: *uid.as_bytes(),
101 }
102 }
103}
104
105impl From<mxr_uid_t> for DeviceUid {
106 fn from(uid: mxr_uid_t) -> Self {
107 DeviceUid::from_array(uid.bytes)
108 }
109}
110
111impl From<BayUid> for mxr_bay_uid_t {
112 fn from(bay: BayUid) -> Self {
113 Self {
114 device: bay.device.into(),
115 port: bay.port,
116 }
117 }
118}
119
120impl From<mxr_bay_uid_t> for BayUid {
121 fn from(bay: mxr_bay_uid_t) -> Self {
122 BayUid::new(bay.device.into(), bay.port)
123 }
124}
125
126pub(crate) fn bay_or_zero(bay: Option<BayUid>) -> mxr_bay_uid_t {
131 bay.map(mxr_bay_uid_t::from).unwrap_or_default()
132}
133
134thread_local! {
135 static LAST_ERROR: RefCell<CString> = RefCell::new(c"".to_owned());
138}
139
140pub(crate) fn set_last_error(message: &str) {
142 let text = CString::new(message).unwrap_or_else(|_| c"error text contains a NUL".to_owned());
145 LAST_ERROR.with(|slot| *slot.borrow_mut() = text);
146}
147
148pub(crate) fn guard<T>(fallback: T, body: impl FnOnce() -> T) -> T {
155 match catch_unwind(AssertUnwindSafe(body)) {
156 Ok(value) => value,
157 Err(payload) => {
158 set_last_error(&format!("panic: {}", panic_text(&payload)));
159 fallback
160 }
161 }
162}
163
164fn panic_text(payload: &Box<dyn Any + Send>) -> &str {
166 if let Some(s) = payload.downcast_ref::<&str>() {
167 return s;
168 }
169 if let Some(s) = payload.downcast_ref::<String>() {
170 return s;
171 }
172 "no message"
173}
174
175pub(crate) fn fail(code: mxr_result_t, message: &str) -> mxr_result_t {
177 set_last_error(message);
178 code
179}
180
181pub(crate) fn from_control(result: Result<(), ControlError>) -> mxr_result_t {
183 let error = match result {
184 Ok(()) => return mxr_result_t::MXR_OK,
185 Err(e) => e,
186 };
187 let code = match &error {
188 ControlError::UnknownDevice(_)
189 | ControlError::UnknownBay(_)
190 | ControlError::UnknownSource(_) => mxr_result_t::MXR_ERR_NOT_FOUND,
191 ControlError::Unsupported(_) => mxr_result_t::MXR_ERR_UNSUPPORTED,
192 ControlError::InvalidRequest(_) => mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
195 ControlError::NotReported(_) => mxr_result_t::MXR_ERR_NOT_REPORTED,
196 ControlError::Send(e) => send_code(e),
197 _ => mxr_result_t::MXR_ERR_UNSUPPORTED,
200 };
201 fail(code, &error.to_string())
202}
203
204pub(crate) fn from_send(result: Result<(), SendError>) -> mxr_result_t {
206 match result {
207 Ok(()) => mxr_result_t::MXR_OK,
208 Err(e) => fail(send_code(&e), &e.to_string()),
209 }
210}
211
212fn send_code(error: &SendError) -> mxr_result_t {
213 match error {
214 SendError::ProtocolTooOld { .. } => mxr_result_t::MXR_ERR_PROTOCOL_TOO_OLD,
215 SendError::NotConnected => mxr_result_t::MXR_ERR_NOT_CONNECTED,
216 SendError::Io(_) => mxr_result_t::MXR_ERR_IO,
217 SendError::UnknownOpcode { .. } => mxr_result_t::MXR_ERR_UNSUPPORTED,
218 _ => mxr_result_t::MXR_ERR_IO,
219 }
220}
221
222pub(crate) fn from_io(result: io::Result<()>) -> mxr_result_t {
224 match result {
225 Ok(()) => mxr_result_t::MXR_OK,
226 Err(e) => fail(mxr_result_t::MXR_ERR_IO, &e.to_string()),
227 }
228}
229
230pub(crate) fn put_str(dst: &mut [c_char], text: &str) {
236 let room = dst.len().saturating_sub(1);
237 let mut end = room.min(text.len());
238 while end > 0 && !text.is_char_boundary(end) {
239 end -= 1;
240 }
241 let taken = text.as_bytes().get(..end).unwrap_or_default();
242 for (slot, byte) in dst.iter_mut().zip(taken) {
243 *slot = *byte as c_char;
244 }
245 for slot in dst.iter_mut().skip(end) {
246 *slot = 0;
247 }
248}
249
250pub(crate) unsafe fn opt_str<'a>(ptr: *const c_char) -> Result<Option<&'a str>, mxr_result_t> {
256 if ptr.is_null() {
257 return Ok(None);
258 }
259 match unsafe { CStr::from_ptr(ptr) }.to_str() {
261 Ok(s) => Ok(Some(s)),
262 Err(_) => Err(fail(
263 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
264 "string is not valid UTF-8",
265 )),
266 }
267}
268
269pub(crate) unsafe fn req_str<'a>(ptr: *const c_char) -> Result<&'a str, mxr_result_t> {
275 match unsafe { opt_str(ptr) }? {
277 Some(s) => Ok(s),
278 None => Err(fail(
279 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
280 "a required string argument was null",
281 )),
282 }
283}
284
285#[no_mangle]
289pub extern "C" fn mxr_version() -> *const c_char {
290 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
292}
293
294#[no_mangle]
303pub extern "C" fn mxr_last_error() -> *const c_char {
304 LAST_ERROR.with(|slot| slot.borrow().as_ptr())
307}
308
309#[no_mangle]
314pub extern "C" fn mxr_uid_is_zero(uid: mxr_uid_t) -> bool {
315 uid.bytes == [0; 16]
316}
317
318#[no_mangle]
325pub unsafe extern "C" fn mxr_uid_to_string(
326 uid: mxr_uid_t,
327 out: *mut c_char,
328 cap: usize,
329) -> mxr_result_t {
330 guard(mxr_result_t::MXR_ERR_PANIC, || {
331 if out.is_null() || cap < MXR_UID_STRING_LEN {
332 return fail(
333 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
334 "uid buffer is null or shorter than MXR_UID_STRING_LEN",
335 );
336 }
337 let dst = unsafe { std::slice::from_raw_parts_mut(out, cap) };
339 put_str(dst, &DeviceUid::from(uid).to_string());
340 mxr_result_t::MXR_OK
341 })
342}
343
344#[no_mangle]
351pub unsafe extern "C" fn mxr_uid_from_string(
352 text: *const c_char,
353 out: *mut mxr_uid_t,
354) -> mxr_result_t {
355 guard(mxr_result_t::MXR_ERR_PANIC, || {
356 if out.is_null() {
357 return fail(
358 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
359 "uid output pointer is null",
360 );
361 }
362 let text = match unsafe { req_str(text) } {
364 Ok(s) => s,
365 Err(code) => return code,
366 };
367 match DeviceUid::from_str(text) {
368 Ok(uid) => {
369 unsafe { *out = uid.into() };
371 mxr_result_t::MXR_OK
372 }
373 Err(e) => fail(mxr_result_t::MXR_ERR_INVALID_ARGUMENT, &e.to_string()),
374 }
375 })
376}