libcsp/
lib.rs

1//! This crate provides a (mostly) safe and ergonomic Rust API for the
2//! [`libcsp` C library](https://github.com/libcsp/libcsp) on top of the
3//! [`libcsp-sys`](https://crates.io/crates/libcsp-sys) crate.
4//!
5//! You can find some more high-level information and examples in the
6//! [main repository](https://egit.irs.uni-stuttgart.de/rust/libcsp-rust).
7#![no_std]
8#[cfg(feature = "alloc")]
9extern crate alloc;
10#[cfg(any(feature = "std", test))]
11extern crate std;
12
13use core::time::Duration;
14
15use num_enum::{IntoPrimitive, TryFromPrimitive};
16
17use bitflags::bitflags;
18use ffi::{csp_conn_s, csp_packet_s, csp_socket_s};
19pub use libcsp_sys as ffi;
20
21#[derive(Debug, PartialEq, Eq, Copy, Clone)]
22pub enum ReservedPort {
23    Cmp = 0,
24    Ping = 1,
25    Ps = 2,
26    Memfree = 3,
27    Reboot = 4,
28    BufFree = 5,
29    Uptime = 6,
30}
31
32#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)]
33#[repr(i32)]
34pub enum CspError {
35    None = 0,
36    NoMem = -1,
37    Inval = -2,
38    TimedOut = -3,
39    Used = -4,
40    NotSup = -5,
41    Busy = -6,
42    Already = -7,
43    Reset = -8,
44    NoBufs = -9,
45    Tx = -10,
46    Driver = -11,
47    Again = -12,
48    NoSys = -38,
49    Hmac = -100,
50    Crc32 = -102,
51    Sfp = -103,
52}
53
54/// Listen on all ports, primarily used with [csp_bind]
55pub const CSP_ANY: u8 = 255;
56pub const CSP_LOOPBACK: u16 = 0;
57
58bitflags! {
59    pub struct SocketFlags: u32 {
60        const NONE = 0x0000;
61        /// RDP required.
62        const RDPREQ = 0x0001;
63        /// RDP prohibited.
64        const RDPPROHIB = 0x0002;
65        /// HMAC required
66        const HMACREQ = 0x0004;
67        /// HMAC prohibited.
68        const HMACPROHIB = 0x0008;
69        /// CRC32 required.
70        const CRC32REQ = 0x0040;
71        const CRC32PROHIB = 0x0080;
72        const CONN_LESS = 0x0100;
73        /// Copy opts from incoming packets. Only applies to [csp_sendto_reply]
74        const SAME = 0x8000;
75
76        // The source may set any bits
77        const _ = !0;
78    }
79}
80
81bitflags! {
82    pub struct ConnectOpts: u32 {
83        const NONE = SocketFlags::NONE.bits();
84
85        const RDP = SocketFlags::RDPREQ.bits();
86        const NORDP = SocketFlags::RDPPROHIB.bits();
87        const HMAC = SocketFlags::HMACREQ.bits();
88        const NOHMAC = SocketFlags::HMACPROHIB.bits();
89        const CRC32 = SocketFlags::CRC32REQ.bits();
90        const NOCRC32 = SocketFlags::CRC32PROHIB.bits();
91        const SAME = SocketFlags::SAME.bits();
92
93        // The source may set any bits
94        const _ = !0;
95    }
96}
97
98#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
99#[repr(u8)]
100pub enum ConnState {
101    Closed = 0,
102    Open = 1,
103}
104
105#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
106#[repr(u8)]
107pub enum ConnType {
108    Client = 0,
109    Server = 1,
110}
111
112#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
113#[repr(u32)]
114pub enum RdpState {
115    Closed = 0,
116    SynSent = 1,
117    SynRcvd = 2,
118    Open = 3,
119    CloseWait = 4,
120}
121
122#[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)]
123#[repr(u8)]
124pub enum MsgPriority {
125    Critical = 0,
126    High = 1,
127    Normal = 2,
128    Low = 3,
129}
130
131pub struct CspPacket(pub csp_packet_s);
132
133pub struct CspPacketRef(*mut csp_packet_s);
134
135pub struct CspPacketMut(*mut csp_packet_s);
136
137impl From<CspPacketMut> for CspPacketRef {
138    fn from(value: CspPacketMut) -> Self {
139        Self(value.0)
140    }
141}
142
143impl CspPacketRef {
144    pub fn packet_data(&self) -> &[u8] {
145        unsafe { &(*self.0).packet_data_union.data[..self.packet_length()] }
146    }
147
148    pub fn whole_data(&self) -> &[u8; ffi::CSP_BUFFER_SIZE] {
149        unsafe { &(*self.0).packet_data_union.data }
150    }
151
152    pub fn packet_length(&self) -> usize {
153        unsafe { (*self.0).length.into() }
154    }
155
156    pub fn inner(&self) -> *const csp_packet_s {
157        self.0
158    }
159}
160
161pub struct CspPacketRefGuard(Option<CspPacketRef>);
162
163impl Drop for CspPacketRefGuard {
164    fn drop(&mut self) {
165        if let Some(packet) = self.0.take() {
166            csp_buffer_free(packet)
167        }
168    }
169}
170
171impl CspPacketRefGuard {
172    /// Take the packet out of the guard, preventing it from being freed.
173    pub fn take(mut self) -> CspPacketRef {
174        self.0.take().unwrap()
175    }
176}
177
178impl AsRef<CspPacketRef> for CspPacketRefGuard {
179    fn as_ref(&self) -> &CspPacketRef {
180        self.0.as_ref().unwrap()
181    }
182}
183
184impl CspPacketMut {
185    pub fn packet_data(&self) -> &[u8] {
186        unsafe { &(*self.0).packet_data_union.data[..self.packet_length()] }
187    }
188
189    pub fn whole_data(&self) -> &[u8; ffi::CSP_BUFFER_SIZE] {
190        unsafe { &(*self.0).packet_data_union.data }
191    }
192
193    pub fn packet_length(&self) -> usize {
194        unsafe { (*self.0).length.into() }
195    }
196
197    pub fn inner(&self) -> *const csp_packet_s {
198        self.0
199    }
200
201    pub fn whole_data_mut(&mut self) -> &mut [u8; ffi::CSP_BUFFER_SIZE] {
202        unsafe { &mut (*self.0).packet_data_union.data }
203    }
204
205    pub fn set_data(&mut self, data: &[u8]) -> bool {
206        if data.len() > self.whole_data().len() {
207            return false;
208        }
209        self.whole_data_mut()[0..data.len()].copy_from_slice(data);
210        unsafe {
211            (*self.0).length = data.len() as u16;
212        }
213        true
214    }
215
216    pub fn inner_mut(&self) -> *mut csp_packet_s {
217        self.0
218    }
219}
220
221impl CspPacket {
222    pub fn new() -> Self {
223        Self::default()
224    }
225}
226
227impl Default for CspPacket {
228    fn default() -> Self {
229        Self(csp_packet_s {
230            packet_info: Default::default(),
231            length: Default::default(),
232            id: Default::default(),
233            next: core::ptr::null_mut(),
234            header: Default::default(),
235            packet_data_union: Default::default(),
236        })
237    }
238}
239
240#[derive(Default)]
241pub struct CspSocket(pub csp_socket_s);
242
243impl CspSocket {
244    pub fn inner_as_mut_ptr(&mut self) -> *mut csp_socket_s {
245        &mut self.0
246    }
247}
248
249/// Rust wrapper for [ffi::csp_init]. Initialize the CSP stack.
250///
251/// # Safety
252///
253/// - You must call this function only once.
254pub unsafe fn csp_init() {
255    // SAFETY: FFI call
256    unsafe {
257        ffi::csp_init();
258    }
259}
260
261/// Rust wrapper for [ffi::csp_bind].
262pub fn csp_bind(socket: &mut CspSocket, port: u8) {
263    // SAFETY: FFI call
264    unsafe {
265        ffi::csp_bind(socket.inner_as_mut_ptr(), port);
266    }
267}
268
269/// Rust wrapper for [ffi::csp_listen].
270pub fn csp_listen(socket: &mut CspSocket, backlog: usize) {
271    // SAFETY: FFI call
272    unsafe {
273        ffi::csp_listen(socket.inner_as_mut_ptr(), backlog);
274    }
275}
276
277/// Rust wrapper for [ffi::csp_route_work].
278pub fn csp_route_work_raw() -> i32 {
279    unsafe { ffi::csp_route_work() }
280}
281
282/// Rust wrapper for [ffi::csp_route_work] which also converts errors to the [CspError] type.
283/// This function will panic if the returned error type is not among the known values of
284/// [CspError].
285///
286/// [csp_route_work_raw] can be used if this is not acceptable.
287pub fn csp_route_work() -> Result<(), CspError> {
288    let result = unsafe { ffi::csp_route_work() };
289    if result == CspError::None as i32 {
290        return Ok(());
291    }
292    Err(CspError::try_from(result)
293        .unwrap_or_else(|_| panic!("unexpected error value {} from csp_route_work", result)))
294}
295
296#[derive(Debug, Copy, Clone)]
297pub struct CspConnRef(*mut csp_conn_s);
298
299impl CspConnRef {
300    pub fn inner(&mut self) -> Option<&csp_conn_s> {
301        // SAFETY: Raw pointer access, we return [None] if the pointers is NULL.
302        unsafe { self.0.as_ref() }
303    }
304
305    pub fn inner_mut(&mut self) -> Option<&mut csp_conn_s> {
306        // SAFETY: Raw pointer access, we return [None] if the pointers is NULL.
307        unsafe { self.0.as_mut() }
308    }
309}
310
311pub struct CspConnGuard(pub CspConnRef);
312
313impl Drop for CspConnGuard {
314    fn drop(&mut self) {
315        csp_close(self.0);
316    }
317}
318
319impl AsRef<CspConnRef> for CspConnGuard {
320    fn as_ref(&self) -> &CspConnRef {
321        &self.0
322    }
323}
324
325impl AsMut<CspConnRef> for CspConnGuard {
326    fn as_mut(&mut self) -> &mut CspConnRef {
327        &mut self.0
328    }
329}
330
331#[derive(Default)]
332pub struct CspInterface(pub ffi::csp_iface_t);
333
334impl CspInterface {
335    pub fn new(host_addr: u16, is_default: bool) -> Self {
336        Self(ffi::csp_iface_t {
337            addr: host_addr,
338            netmask: Default::default(),
339            name: core::ptr::null(),
340            interface_data: core::ptr::null_mut(),
341            driver_data: core::ptr::null_mut(),
342            nexthop: None,
343            is_default: is_default as u8,
344            tx: Default::default(),
345            rx: Default::default(),
346            tx_error: Default::default(),
347            rx_error: Default::default(),
348            drop: Default::default(),
349            autherr: Default::default(),
350            frame: Default::default(),
351            txbytes: Default::default(),
352            rxbytes: Default::default(),
353            irq: Default::default(),
354            next: core::ptr::null_mut(),
355        })
356    }
357}
358
359#[derive(Default)]
360pub struct CspUdpConf(pub ffi::csp_if_udp_conf_t);
361
362impl CspUdpConf {
363    pub fn new(addr: &'static str, lport: u16, rport: u16) -> Self {
364        Self(ffi::csp_if_udp_conf_t {
365            host: addr.as_ptr() as *mut i8,
366            lport: lport.into(),
367            rport: rport.into(),
368            server_handle: Default::default(),
369            peer_addr: libc::sockaddr_in {
370                sin_family: Default::default(),
371                sin_port: Default::default(),
372                sin_addr: libc::in_addr {
373                    s_addr: Default::default(),
374                },
375                sin_zero: Default::default(),
376            },
377            sockfd: Default::default(),
378        })
379    }
380}
381
382pub fn csp_accept_guarded(socket: &mut CspSocket, timeout: Duration) -> Option<CspConnGuard> {
383    Some(CspConnGuard(csp_accept(socket, timeout)?))
384}
385
386/// Rust wrapper for [ffi::csp_accept].
387pub fn csp_accept(socket: &mut CspSocket, timeout: Duration) -> Option<CspConnRef> {
388    let timeout_millis = timeout.as_millis();
389    if timeout_millis > u32::MAX as u128 {
390        return None;
391    }
392    Some(CspConnRef(unsafe {
393        let addr = ffi::csp_accept(socket.inner_as_mut_ptr(), timeout_millis as u32);
394        if addr.is_null() {
395            return None;
396        }
397        addr
398    }))
399}
400
401/// Rust wrapper for [ffi::csp_read].
402pub fn csp_read(conn: &mut CspConnRef, timeout: Duration) -> Option<CspPacketRef> {
403    let timeout_millis = timeout.as_millis();
404    if timeout_millis > u32::MAX as u128 {
405        return None;
406    }
407    let opt_packet = unsafe { ffi::csp_read(conn.0, timeout_millis as u32) };
408    if opt_packet.is_null() {
409        return None;
410    }
411    // SAFETY: FFI pointer.
412    Some(CspPacketRef(unsafe { &mut *opt_packet }))
413}
414
415/// Rust wrapper for [ffi::csp_read] which returns a guarded packet reference. This packet
416/// will cleaned up automatically with [csp_buffer_free] on drop.
417pub fn csp_read_guarded(conn: &mut CspConnRef, timeout: Duration) -> Option<CspPacketRefGuard> {
418    Some(CspPacketRefGuard(Some(csp_read(conn, timeout)?)))
419}
420
421/// Rust wrapper for [ffi::csp_recvfrom].
422pub fn csp_recvfrom(socket: &mut CspSocket, timeout: u32) -> Option<CspPacketRef> {
423    let opt_packet = unsafe { ffi::csp_recvfrom(&mut socket.0, timeout) };
424    if opt_packet.is_null() {
425        return None;
426    }
427    Some(CspPacketRef(unsafe { &mut *opt_packet }))
428}
429
430/// Rust wrapper for [ffi::csp_recvfrom] which returns a guarded packet reference. This packet
431/// will cleaned up automatically with [csp_buffer_free] on drop.
432pub fn csp_recvfrom_guarded(socket: &mut CspSocket, timeout: u32) -> Option<CspPacketRefGuard> {
433    Some(CspPacketRefGuard(Some(csp_recvfrom(socket, timeout)?)))
434}
435
436/// Rust wrapper for [ffi::csp_conn_dport].
437pub fn csp_conn_dport(conn: &CspConnRef) -> i32 {
438    // SAFETY: FFI call.
439    unsafe { ffi::csp_conn_dport(conn.0) }
440}
441
442/// Rust wrapper for [ffi::csp_conn_sport].
443pub fn csp_conn_sport(conn: &CspConnRef) -> i32 {
444    // SAFETY: FFI call.
445    unsafe { ffi::csp_conn_sport(conn.0) }
446}
447
448/// Rust wrapper for [ffi::csp_conn_dst].
449pub fn csp_conn_dst(conn: &CspConnRef) -> i32 {
450    // SAFETY: FFI call.
451    unsafe { ffi::csp_conn_dst(conn.0) }
452}
453
454/// Rust wrapper for [ffi::csp_conn_src].
455pub fn csp_conn_src(conn: &CspConnRef) -> i32 {
456    // SAFETY: FFI call.
457    unsafe { ffi::csp_conn_src(conn.0) }
458}
459
460/// Rust wrapper for [ffi::csp_conn_src].
461pub fn csp_conn_flags(conn: &CspConnRef) -> i32 {
462    // SAFETY: FFI call.
463    unsafe { ffi::csp_conn_flags(conn.0) }
464}
465
466/// Rust wrapper for [ffi::csp_conn_src] which also tries to convert the options to
467/// a [ConnectOpts] bitfield.
468pub fn csp_conn_flags_typed(conn: &CspConnRef) -> Option<ConnectOpts> {
469    let flags_raw = csp_conn_flags(conn);
470    if flags_raw < 0 {
471        return None;
472    }
473    // SAFETY: FFI call.
474    ConnectOpts::from_bits(flags_raw as u32)
475}
476
477pub fn csp_service_handler(packet: CspPacketRef) {
478    // SAFETY: FFI call.
479    unsafe { ffi::csp_service_handler(&mut *packet.0) }
480}
481
482/// Rust wrapper for [ffi::csp_close].
483pub fn csp_close(conn: CspConnRef) -> i32 {
484    // SAFETY: FFI call.
485    unsafe { ffi::csp_close(conn.0) }
486}
487
488/// Rust wrapper for [ffi::csp_ping], returns the result code directly.
489pub fn csp_ping_raw(node: u16, timeout: Duration, size: usize, opts: SocketFlags) -> i32 {
490    // SAFETY: FFI call.
491    unsafe {
492        ffi::csp_ping(
493            node,
494            timeout.as_millis() as u32,
495            size as u32,
496            opts.bits() as u8,
497        )
498    }
499}
500
501#[derive(Debug, Copy, Clone, PartialEq, Eq)]
502pub struct PingError;
503
504/// Rust wrapper for [ffi::csp_ping].
505pub fn csp_ping(
506    node: u16,
507    timeout: Duration,
508    size: usize,
509    opts: SocketFlags,
510) -> Result<Duration, PingError> {
511    let result = csp_ping_raw(node, timeout, size, opts);
512    if result < 0 {
513        return Err(PingError);
514    }
515    Ok(Duration::from_millis(result as u64))
516}
517
518/// Rust wrapper for [ffi::csp_reboot].
519pub fn csp_reboot(node: u16) {
520    // SAFETY: FFI call.
521    unsafe { ffi::csp_reboot(node) }
522}
523
524/// Rust wrapper for [ffi::csp_connect].
525pub fn csp_connect(
526    prio: MsgPriority,
527    dst: u16,
528    dst_port: u8,
529    timeout: Duration,
530    opts: ConnectOpts,
531) -> Option<CspConnRef> {
532    // SAFETY: FFI call.
533    let conn = unsafe {
534        ffi::csp_connect(
535            prio as u8,
536            dst,
537            dst_port,
538            timeout.as_millis() as u32,
539            opts.bits(),
540        )
541    };
542    if conn.is_null() {
543        return None;
544    }
545    // SAFETY: We checked that the pointer is valid.
546    Some(CspConnRef(conn))
547}
548
549/// Rust wrapper for [ffi::csp_connect] which returns a guard structure. The connection will be
550/// be closed automatically when the guard structure is dropped.
551pub fn csp_connect_guarded(
552    prio: MsgPriority,
553    dst: u16,
554    dst_port: u8,
555    timeout: Duration,
556    opts: ConnectOpts,
557) -> Option<CspConnGuard> {
558    Some(CspConnGuard(csp_connect(
559        prio, dst, dst_port, timeout, opts,
560    )?))
561}
562
563/// Rust wrapper for [ffi::csp_buffer_get].
564pub fn csp_buffer_get() -> Option<CspPacketMut> {
565    let packet_ref = unsafe {
566        // The size argument is unused
567        ffi::csp_buffer_get(0)
568    };
569    if packet_ref.is_null() {
570        return None;
571    }
572    // SAFETY: We checked that the pointer is valid.
573    Some(CspPacketMut(unsafe { &mut *packet_ref }))
574}
575
576/// Rust wrapper for [ffi::csp_send].
577pub fn csp_send(conn: &mut CspConnRef, packet: impl Into<CspPacketRef>) {
578    // SAFETY: FFI call.
579    unsafe { ffi::csp_send(conn.0, packet.into().0) }
580}
581
582/// Rust wrapper for [ffi::csp_conn_print_table].
583pub fn csp_conn_print_table() {
584    // SAFETY: FFI call.
585    unsafe { ffi::csp_conn_print_table() }
586}
587
588/// Rust wrapper for [ffi::csp_buffer_free].
589pub fn csp_buffer_free(packet: impl Into<CspPacketRef>) {
590    // SAFETY: FFI call and the Rust type system actually ensure the correct type
591    // is free'd here, while also taking the packet by value.
592    unsafe { ffi::csp_buffer_free(packet.into().0 as *mut libc::c_void) }
593}
594
595/// Rust wrapper for [ffi::csp_transaction_persistent].
596///
597/// # Parameters
598///
599/// * `in_len`: Use [None] if the length is unknown, and the expected reply length otherwise.
600///
601/// # Returns
602///
603/// 1 or reply size on success, 0 otherwise.
604pub fn csp_transaction_persistent(
605    conn: &mut CspConnRef,
606    timeout: Duration,
607    out_data: &[u8],
608    in_data: &mut [u8],
609    in_len: Option<usize>,
610) -> i32 {
611    unsafe {
612        ffi::csp_transaction_persistent(
613            conn.0,
614            timeout.as_millis() as u32,
615            out_data.as_ptr() as *const core::ffi::c_void,
616            out_data.len() as i32,
617            in_data.as_ptr() as *mut core::ffi::c_void,
618            in_len.map(|v| v as i32).unwrap_or(-1),
619        )
620    }
621}
622
623/// Rust wrapper for [ffi::csp_transaction_w_opts].
624///
625/// # Parameters
626///
627/// * `in_len`: Use [None] if the length is unknown, and the expected reply length otherwise.
628///
629/// # Returns
630///
631/// 1 or reply size on success, 0 otherwise.
632#[allow(clippy::too_many_arguments)]
633pub fn csp_transaction_w_opts(
634    prio: MsgPriority,
635    dst: u16,
636    dst_port: u8,
637    timeout: Duration,
638    out_data: &[u8],
639    in_data: &mut [u8],
640    in_len: Option<usize>,
641    opts: ConnectOpts,
642) -> i32 {
643    unsafe {
644        ffi::csp_transaction_w_opts(
645            prio as u8,
646            dst,
647            dst_port,
648            timeout.as_millis() as u32,
649            out_data.as_ptr() as *const core::ffi::c_void,
650            out_data.len() as i32,
651            in_data.as_ptr() as *mut core::ffi::c_void,
652            in_len.map(|v| v as i32).unwrap_or(-1),
653            opts.bits(),
654        )
655    }
656}
657
658/// Calls [csp_transaction_w_opts] with [ConnectOpts::NONE].
659pub fn csp_transaction(
660    prio: MsgPriority,
661    dst: u16,
662    dst_port: u8,
663    timeout: Duration,
664    out_data: &[u8],
665    in_data: &mut [u8],
666    in_len: Option<usize>,
667) -> i32 {
668    csp_transaction_w_opts(
669        prio,
670        dst,
671        dst_port,
672        timeout,
673        out_data,
674        in_data,
675        in_len,
676        ConnectOpts::NONE,
677    )
678}
679
680pub mod udp {
681    use super::*;
682
683    /// Rust wrapper for [ffi::udp::csp_if_udp_init].
684    pub fn csp_if_udp_init(iface: &mut CspInterface, ifconf: &mut CspUdpConf) {
685        unsafe { ffi::udp::csp_if_udp_init(&mut iface.0, &mut ifconf.0) }
686    }
687}
688
689pub mod iflist {
690    use super::*;
691
692    /// Rust wrapper for [ffi::iflist::csp_iflist_print].
693    pub fn csp_iflist_print() {
694        // SAFETY: FFI call.
695        unsafe { ffi::iflist::csp_iflist_print() }
696    }
697
698    /// Rust wrapper for [ffi::iflist::csp_iflist_add].
699    pub fn csp_iflist_add(iface: &mut CspInterface) -> i32 {
700        unsafe { ffi::iflist::csp_iflist_add(&mut iface.0) }
701    }
702}