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::NotReported(_) => mxr_result_t::MXR_ERR_NOT_REPORTED,
193 ControlError::Send(e) => send_code(e),
194 _ => mxr_result_t::MXR_ERR_UNSUPPORTED,
197 };
198 fail(code, &error.to_string())
199}
200
201pub(crate) fn from_send(result: Result<(), SendError>) -> mxr_result_t {
203 match result {
204 Ok(()) => mxr_result_t::MXR_OK,
205 Err(e) => fail(send_code(&e), &e.to_string()),
206 }
207}
208
209fn send_code(error: &SendError) -> mxr_result_t {
210 match error {
211 SendError::ProtocolTooOld { .. } => mxr_result_t::MXR_ERR_PROTOCOL_TOO_OLD,
212 SendError::NotConnected => mxr_result_t::MXR_ERR_NOT_CONNECTED,
213 SendError::Io(_) => mxr_result_t::MXR_ERR_IO,
214 _ => mxr_result_t::MXR_ERR_IO,
215 }
216}
217
218pub(crate) fn from_io(result: io::Result<()>) -> mxr_result_t {
220 match result {
221 Ok(()) => mxr_result_t::MXR_OK,
222 Err(e) => fail(mxr_result_t::MXR_ERR_IO, &e.to_string()),
223 }
224}
225
226pub(crate) fn put_str(dst: &mut [c_char], text: &str) {
232 let room = dst.len().saturating_sub(1);
233 let mut end = room.min(text.len());
234 while end > 0 && !text.is_char_boundary(end) {
235 end -= 1;
236 }
237 let taken = text.as_bytes().get(..end).unwrap_or_default();
238 for (slot, byte) in dst.iter_mut().zip(taken) {
239 *slot = *byte as c_char;
240 }
241 for slot in dst.iter_mut().skip(end) {
242 *slot = 0;
243 }
244}
245
246pub(crate) unsafe fn opt_str<'a>(ptr: *const c_char) -> Result<Option<&'a str>, mxr_result_t> {
252 if ptr.is_null() {
253 return Ok(None);
254 }
255 match unsafe { CStr::from_ptr(ptr) }.to_str() {
257 Ok(s) => Ok(Some(s)),
258 Err(_) => Err(fail(
259 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
260 "string is not valid UTF-8",
261 )),
262 }
263}
264
265pub(crate) unsafe fn req_str<'a>(ptr: *const c_char) -> Result<&'a str, mxr_result_t> {
271 match unsafe { opt_str(ptr) }? {
273 Some(s) => Ok(s),
274 None => Err(fail(
275 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
276 "a required string argument was null",
277 )),
278 }
279}
280
281#[no_mangle]
285pub extern "C" fn mxr_version() -> *const c_char {
286 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
288}
289
290#[no_mangle]
299pub extern "C" fn mxr_last_error() -> *const c_char {
300 LAST_ERROR.with(|slot| slot.borrow().as_ptr())
303}
304
305#[no_mangle]
310pub extern "C" fn mxr_uid_is_zero(uid: mxr_uid_t) -> bool {
311 uid.bytes == [0; 16]
312}
313
314#[no_mangle]
321pub unsafe extern "C" fn mxr_uid_to_string(
322 uid: mxr_uid_t,
323 out: *mut c_char,
324 cap: usize,
325) -> mxr_result_t {
326 guard(mxr_result_t::MXR_ERR_PANIC, || {
327 if out.is_null() || cap < MXR_UID_STRING_LEN {
328 return fail(
329 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
330 "uid buffer is null or shorter than MXR_UID_STRING_LEN",
331 );
332 }
333 let dst = unsafe { std::slice::from_raw_parts_mut(out, cap) };
335 put_str(dst, &DeviceUid::from(uid).to_string());
336 mxr_result_t::MXR_OK
337 })
338}
339
340#[no_mangle]
347pub unsafe extern "C" fn mxr_uid_from_string(
348 text: *const c_char,
349 out: *mut mxr_uid_t,
350) -> mxr_result_t {
351 guard(mxr_result_t::MXR_ERR_PANIC, || {
352 if out.is_null() {
353 return fail(
354 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
355 "uid output pointer is null",
356 );
357 }
358 let text = match unsafe { req_str(text) } {
360 Ok(s) => s,
361 Err(code) => return code,
362 };
363 match DeviceUid::from_str(text) {
364 Ok(uid) => {
365 unsafe { *out = uid.into() };
367 mxr_result_t::MXR_OK
368 }
369 Err(e) => fail(mxr_result_t::MXR_ERR_INVALID_ARGUMENT, &e.to_string()),
370 }
371 })
372}