1use std::os::raw::c_char;
13use std::panic::{catch_unwind, AssertUnwindSafe};
14use std::ptr;
15use std::time::Duration;
16
17use pamoja_can::bus::{BusError, CanBus, CanBusKind, Filter, OpenError};
18use pamoja_can::CanId;
19
20use crate::can::PamojaCanFrame;
21use crate::{read_str, set_last_error, PamojaStatus};
22
23pub const PAMOJA_CAN_BUS_DEVICE: u8 = 0;
25
26pub const PAMOJA_CAN_BUS_SIMULATED: u8 = 1;
28
29pub struct PamojaCanBus {
31 bus: CanBus,
32}
33
34#[repr(C)]
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct PamojaCanFilter {
39 pub id: u32,
41 pub mask: u32,
43 pub extended: u8,
45}
46
47impl From<Filter> for PamojaCanFilter {
48 fn from(filter: Filter) -> Self {
49 PamojaCanFilter {
50 id: filter.id().raw(),
51 mask: filter.mask(),
52 extended: u8::from(filter.id().is_extended()),
53 }
54 }
55}
56
57impl PamojaCanFilter {
58 fn filter(self) -> Filter {
59 Filter::new(identifier(self.id, self.extended != 0), self.mask)
60 }
61}
62
63fn identifier(id: u32, extended: bool) -> CanId {
64 if extended {
65 CanId::extended(id)
66 } else {
67 CanId::standard(id as u16)
68 }
69}
70
71fn into_raw(bus: CanBus) -> *mut PamojaCanBus {
72 Box::into_raw(Box::new(PamojaCanBus { bus }))
73}
74
75fn opened(result: Result<CanBus, OpenError>, out_bus: &mut *mut PamojaCanBus) -> PamojaStatus {
76 match result {
77 Ok(bus) => {
78 *out_bus = into_raw(bus);
79 PamojaStatus::Ok
80 }
81 Err(error) => {
82 let status = match error {
83 OpenError::Unsupported => PamojaStatus::Unsupported,
84 OpenError::Device { .. } => PamojaStatus::Io,
85 };
86 set_last_error(error.to_string());
87 status
88 }
89 }
90}
91
92unsafe fn on_bus(
98 bus: *const PamojaCanBus,
99 call: impl FnOnce(&CanBus) -> Result<(), BusError>,
100) -> PamojaStatus {
101 let Some(bus) = bus.as_ref() else {
102 set_last_error("bus must not be null".to_owned());
103 return PamojaStatus::InvalidArgument;
104 };
105 match catch_unwind(AssertUnwindSafe(|| call(&bus.bus))) {
106 Ok(Ok(())) => PamojaStatus::Ok,
107 Ok(Err(error)) => {
108 set_last_error(error.to_string());
109 PamojaStatus::Io
110 }
111 Err(_) => {
112 set_last_error("panic at the FFI boundary".to_owned());
113 PamojaStatus::Panic
114 }
115 }
116}
117
118#[no_mangle]
124pub extern "C" fn pamoja_can_bus_simulated() -> *mut PamojaCanBus {
125 into_raw(CanBus::simulated())
126}
127
128#[no_mangle]
147pub unsafe extern "C" fn pamoja_can_bus_open(
148 interface: *const c_char,
149 out_bus: *mut *mut PamojaCanBus,
150) -> PamojaStatus {
151 let Some(out_bus) = out_bus.as_mut() else {
152 set_last_error("out_bus must not be null".to_owned());
153 return PamojaStatus::InvalidArgument;
154 };
155 *out_bus = ptr::null_mut();
156 let Some(interface) = read_str(interface, "interface") else {
157 return PamojaStatus::InvalidArgument;
158 };
159 opened(CanBus::open(interface), out_bus)
160}
161
162#[no_mangle]
178pub unsafe extern "C" fn pamoja_can_bus_join(
179 bus: *const PamojaCanBus,
180 out_bus: *mut *mut PamojaCanBus,
181) -> PamojaStatus {
182 let Some(out_bus) = out_bus.as_mut() else {
183 set_last_error("out_bus must not be null".to_owned());
184 return PamojaStatus::InvalidArgument;
185 };
186 *out_bus = ptr::null_mut();
187 let Some(bus) = bus.as_ref() else {
188 set_last_error("bus must not be null".to_owned());
189 return PamojaStatus::InvalidArgument;
190 };
191 opened(bus.bus.join(), out_bus)
192}
193
194#[no_mangle]
201pub unsafe extern "C" fn pamoja_can_bus_kind(bus: *const PamojaCanBus) -> u8 {
202 match bus.as_ref().map(|bus| bus.bus.kind()) {
203 Some(CanBusKind::Device) => PAMOJA_CAN_BUS_DEVICE,
204 _ => PAMOJA_CAN_BUS_SIMULATED,
205 }
206}
207
208#[no_mangle]
225pub unsafe extern "C" fn pamoja_can_bus_send(
226 bus: *const PamojaCanBus,
227 frame: *const PamojaCanFrame,
228) -> PamojaStatus {
229 let Some(frame) = frame.as_ref() else {
230 set_last_error("frame must not be null".to_owned());
231 return PamojaStatus::InvalidArgument;
232 };
233 let frame = frame.frame;
234 on_bus(bus, |bus| bus.send(&frame))
235}
236
237#[no_mangle]
255pub unsafe extern "C" fn pamoja_can_bus_receive(
256 bus: *const PamojaCanBus,
257 timeout_micros: u64,
258 out_frame: *mut *mut PamojaCanFrame,
259) -> PamojaStatus {
260 let Some(out_frame) = out_frame.as_mut() else {
261 set_last_error("out_frame must not be null".to_owned());
262 return PamojaStatus::InvalidArgument;
263 };
264 *out_frame = ptr::null_mut();
265 on_bus(bus, |bus| {
266 if let Some(frame) = bus.receive(Duration::from_micros(timeout_micros))? {
267 *out_frame = Box::into_raw(Box::new(PamojaCanFrame { frame }));
268 }
269 Ok(())
270 })
271}
272
273#[no_mangle]
290pub unsafe extern "C" fn pamoja_can_bus_set_filters(
291 bus: *const PamojaCanBus,
292 filters: *const PamojaCanFilter,
293 len: usize,
294) -> PamojaStatus {
295 let filters: Vec<Filter> = if len == 0 {
296 Vec::new()
297 } else if filters.is_null() {
298 set_last_error("filters must not be null when len is non-zero".to_owned());
299 return PamojaStatus::InvalidArgument;
300 } else {
301 std::slice::from_raw_parts(filters, len)
302 .iter()
303 .map(|filter| filter.filter())
304 .collect()
305 };
306 on_bus(bus, |bus| bus.set_filters(&filters))
307}
308
309#[no_mangle]
319pub unsafe extern "C" fn pamoja_can_bus_clear_filters(bus: *const PamojaCanBus) -> PamojaStatus {
320 on_bus(bus, CanBus::clear_filters)
321}
322
323#[no_mangle]
329pub unsafe extern "C" fn pamoja_can_bus_sent(bus: *const PamojaCanBus) -> usize {
330 bus.as_ref().map_or(0, |bus| bus.bus.sent())
331}
332
333#[no_mangle]
339pub unsafe extern "C" fn pamoja_can_bus_received(bus: *const PamojaCanBus) -> usize {
340 bus.as_ref().map_or(0, |bus| bus.bus.received())
341}
342
343#[no_mangle]
350pub unsafe extern "C" fn pamoja_can_bus_waited_micros(bus: *const PamojaCanBus) -> u64 {
351 bus.as_ref().map_or(0, |bus| bus.bus.waited_micros())
352}
353
354#[no_mangle]
360pub unsafe extern "C" fn pamoja_can_bus_free(bus: *mut PamojaCanBus) {
361 if !bus.is_null() {
362 drop(Box::from_raw(bus));
363 }
364}
365
366#[no_mangle]
377pub extern "C" fn pamoja_can_filter_exact(id: u32, extended: bool) -> PamojaCanFilter {
378 Filter::exact(identifier(id, extended)).into()
379}
380
381#[no_mangle]
392pub extern "C" fn pamoja_can_filter_pgn(pgn: u32) -> PamojaCanFilter {
393 Filter::pgn(pgn).into()
394}
395
396#[no_mangle]
408pub extern "C" fn pamoja_can_filter_matches(
409 filter: PamojaCanFilter,
410 id: u32,
411 extended: bool,
412) -> bool {
413 filter.filter().matches(identifier(id, extended))
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use crate::can::{pamoja_can_frame_data_len, pamoja_can_frame_free, pamoja_can_frame_new};
420
421 #[test]
422 fn two_nodes_trade_a_frame_through_the_c_abi() {
423 unsafe {
424 let engine = pamoja_can_bus_simulated();
425 let mut gateway = ptr::null_mut();
426 assert_eq!(pamoja_can_bus_join(engine, &mut gateway), PamojaStatus::Ok);
427 assert_eq!(pamoja_can_bus_kind(gateway), PAMOJA_CAN_BUS_SIMULATED);
428 let filter = pamoja_can_filter_pgn(61_444);
429 assert_eq!(
430 pamoja_can_bus_set_filters(gateway, &filter, 1),
431 PamojaStatus::Ok
432 );
433
434 let speed = pamoja_can::J1939Id::broadcast(3, 61_444, 0).to_id().raw();
435 let data = [0xFFu8; 8];
436 let mut frame = ptr::null_mut();
437 assert_eq!(
438 pamoja_can_frame_new(speed, true, data.as_ptr(), 8, &mut frame),
439 PamojaStatus::Ok
440 );
441 assert_eq!(pamoja_can_bus_send(engine, frame), PamojaStatus::Ok);
442 pamoja_can_frame_free(frame);
443
444 let mut heard = ptr::null_mut();
445 assert_eq!(
446 pamoja_can_bus_receive(gateway, 1_000, &mut heard),
447 PamojaStatus::Ok
448 );
449 assert!(!heard.is_null());
450 assert_eq!(pamoja_can_frame_data_len(heard), 8);
451 pamoja_can_frame_free(heard);
452
453 assert_eq!(
454 pamoja_can_bus_receive(gateway, 250_000, &mut heard),
455 PamojaStatus::Ok
456 );
457 assert!(heard.is_null());
458 assert_eq!(pamoja_can_bus_waited_micros(gateway), 250_000);
459 assert_eq!(
460 (
461 pamoja_can_bus_sent(engine),
462 pamoja_can_bus_received(gateway)
463 ),
464 (1, 1)
465 );
466 assert!(pamoja_can_filter_matches(filter, speed, true));
467 assert!(!pamoja_can_filter_matches(filter, speed, false));
468
469 pamoja_can_bus_free(gateway);
470 pamoja_can_bus_free(engine);
471 }
472 }
473
474 #[test]
475 fn opening_an_interface_off_linux_is_unsupported() {
476 if cfg!(not(target_os = "linux")) {
477 let mut bus = ptr::null_mut();
478 let name = c"can0";
479 assert_eq!(
480 unsafe { pamoja_can_bus_open(name.as_ptr(), &mut bus) },
481 PamojaStatus::Unsupported
482 );
483 assert!(bus.is_null());
484 }
485 }
486}