#![no_std] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(any(feature = "std", test))] extern crate std; use core::time::Duration; use num_enum::{IntoPrimitive, TryFromPrimitive}; use bitflags::bitflags; use ffi::{csp_conn_s, csp_packet_s, csp_socket_s}; use libcsp_sys as ffi; #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum ReservedPort { Cmp = 0, Ping = 1, Ps = 2, Memfree = 3, Reboot = 4, BufFree = 5, Uptime = 6, } #[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)] #[repr(i32)] pub enum CspError { None = 0, NoMem = -1, Inval = -2, TimedOut = -3, Used = -4, NotSup = -5, Busy = -6, Already = -7, Reset = -8, NoBufs = -9, Tx = -10, Driver = -11, Again = -12, NoSys = -38, Hmac = -100, Crc32 = -102, Sfp = -103, } /// Listen on all ports, primarily used with [csp_bind] pub const CSP_ANY: u8 = 255; pub const CSP_LOOPBACK: u16 = 0; bitflags! { pub struct SocketFlags: u32 { const NONE = 0x0000; /// RDP required. const RDPREQ = 0x0001; /// RDP prohibited. const RDPPROHIB = 0x0002; /// HMAC required const HMACREQ = 0x0004; /// HMAC prohibited. const HMACPROHIB = 0x0008; /// CRC32 required. const CRC32REQ = 0x0040; const CRC32PROHIB = 0x0080; const CONN_LESS = 0x0100; /// Copy opts from incoming packets. Only applies to [csp_sendto_reply] const SAME = 0x8000; // The source may set any bits const _ = !0; } } bitflags! { pub struct ConnectOpts: u32 { const NONE = SocketFlags::NONE.bits(); const RDP = SocketFlags::RDPREQ.bits(); const NORDP = SocketFlags::RDPPROHIB.bits(); const HMAC = SocketFlags::HMACREQ.bits(); const NOHMAC = SocketFlags::HMACPROHIB.bits(); const CRC32 = SocketFlags::CRC32REQ.bits(); const NOCRC32 = SocketFlags::CRC32PROHIB.bits(); const SAME = SocketFlags::SAME.bits(); // The source may set any bits const _ = !0; } } #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum ConnState { Closed = 0, Open = 1, } #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum ConnType { Client = 0, Server = 1, } #[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(u32)] pub enum RdpState { Closed = 0, SynSent = 1, SynRcvd = 2, Open = 3, CloseWait = 4, } #[derive(Debug, PartialEq, Eq, Copy, Clone, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum MsgPriority { Critical = 0, High = 1, Normal = 2, Low = 3, } pub struct CspPacket(pub csp_packet_s); pub struct CspPacketRef(*mut csp_packet_s); pub struct CspPacketMut(*mut csp_packet_s); impl From for CspPacketRef { fn from(value: CspPacketMut) -> Self { Self(value.0) } } impl CspPacketRef { pub fn packet_data(&self) -> &[u8] { unsafe { &(*self.0).packet_data_union.data[..self.packet_length()] } } pub fn whole_data(&self) -> &[u8; ffi::CSP_BUFFER_SIZE] { unsafe { &(*self.0).packet_data_union.data } } pub fn packet_length(&self) -> usize { unsafe { (*self.0).length.into() } } pub fn inner(&self) -> *const csp_packet_s { self.0 } } pub struct CspPacketRefGuard(Option); impl Drop for CspPacketRefGuard { fn drop(&mut self) { if let Some(packet) = self.0.take() { csp_buffer_free(packet) } } } impl CspPacketRefGuard { /// Take the packet out of the guard, preventing it from being freed. pub fn take(mut self) -> CspPacketRef { self.0.take().unwrap() } } impl AsRef for CspPacketRefGuard { fn as_ref(&self) -> &CspPacketRef { self.0.as_ref().unwrap() } } impl CspPacketMut { pub fn packet_data(&self) -> &[u8] { unsafe { &(*self.0).packet_data_union.data[..self.packet_length()] } } pub fn whole_data(&self) -> &[u8; ffi::CSP_BUFFER_SIZE] { unsafe { &(*self.0).packet_data_union.data } } pub fn packet_length(&self) -> usize { unsafe { (*self.0).length.into() } } pub fn inner(&self) -> *const csp_packet_s { self.0 } pub fn whole_data_mut(&mut self) -> &mut [u8; ffi::CSP_BUFFER_SIZE] { unsafe { &mut (*self.0).packet_data_union.data } } pub fn set_data(&mut self, data: &[u8]) -> bool { if data.len() > self.whole_data().len() { return false; } self.whole_data_mut()[0..data.len()].copy_from_slice(data); unsafe { (*self.0).length = data.len() as u16; } true } pub fn inner_mut(&self) -> *mut csp_packet_s { self.0 } } impl CspPacket { pub fn new() -> Self { Self::default() } } impl Default for CspPacket { fn default() -> Self { Self(csp_packet_s { packet_info: Default::default(), length: Default::default(), id: Default::default(), next: core::ptr::null_mut(), header: Default::default(), packet_data_union: Default::default(), }) } } #[derive(Default)] pub struct CspSocket(pub csp_socket_s); impl CspSocket { pub fn inner_as_mut_ptr(&mut self) -> *mut csp_socket_s { &mut self.0 } } /// Rust wrapper for [ffi::csp_init]. Initialize the CSP stack. /// /// # Safety /// /// - You must call this function only once. pub unsafe fn csp_init() { // SAFETY: FFI call unsafe { ffi::csp_init(); } } /// Rust wrapper for [ffi::csp_bind]. pub fn csp_bind(socket: &mut CspSocket, port: u8) { // SAFETY: FFI call unsafe { ffi::csp_bind(socket.inner_as_mut_ptr(), port); } } /// Rust wrapper for [ffi::csp_listen]. pub fn csp_listen(socket: &mut CspSocket, backlog: usize) { // SAFETY: FFI call unsafe { ffi::csp_listen(socket.inner_as_mut_ptr(), backlog); } } /// Rust wrapper for [ffi::csp_route_work]. pub fn csp_route_work_raw() -> i32 { unsafe { ffi::csp_route_work() } } /// Rust wrapper for [ffi::csp_route_work] which also converts errors to the [CspError] type. /// This function will panic if the returned error type is not among the known values of /// [CspError]. /// /// [csp_route_work_raw] can be used if this is not acceptable. pub fn csp_route_work() -> Result<(), CspError> { let result = unsafe { ffi::csp_route_work() }; if result == CspError::None as i32 { return Ok(()); } Err(CspError::try_from(result) .unwrap_or_else(|_| panic!("unexpected error value {} from csp_route_work", result))) } #[derive(Debug, Copy, Clone)] pub struct CspConnRef(*mut csp_conn_s); pub struct CspConnGuard(pub CspConnRef); impl Drop for CspConnGuard { fn drop(&mut self) { csp_close(self.0); } } impl AsRef for CspConnGuard { fn as_ref(&self) -> &CspConnRef { &self.0 } } impl AsMut for CspConnGuard { fn as_mut(&mut self) -> &mut CspConnRef { &mut self.0 } } pub fn csp_accept_guarded(socket: &mut CspSocket, timeout: Duration) -> Option { Some(CspConnGuard(csp_accept(socket, timeout)?)) } /// Rust wrapper for [ffi::csp_accept]. pub fn csp_accept(socket: &mut CspSocket, timeout: Duration) -> Option { let timeout_millis = timeout.as_millis(); if timeout_millis > u32::MAX as u128 { return None; } Some(CspConnRef(unsafe { let addr = ffi::csp_accept(socket.inner_as_mut_ptr(), timeout_millis as u32); if addr.is_null() { return None; } addr })) } /// Rust wrapper for [ffi::csp_read]. pub fn csp_read(conn: &mut CspConnRef, timeout: Duration) -> Option { let timeout_millis = timeout.as_millis(); if timeout_millis > u32::MAX as u128 { return None; } let opt_packet = unsafe { ffi::csp_read(conn.0, timeout_millis as u32) }; if opt_packet.is_null() { return None; } // SAFETY: FFI pointer. Some(CspPacketRef(unsafe { &mut *opt_packet })) } /// Rust wrapper for [ffi::csp_read] which returns a guarded packet reference. This packet /// will cleaned up automatically with [csp_buffer_free] on drop. pub fn csp_read_guarded(conn: &mut CspConnRef, timeout: Duration) -> Option { Some(CspPacketRefGuard(Some(csp_read(conn, timeout)?))) } /// Rust wrapper for [ffi::csp_conn_dport]. pub fn csp_conn_dport(conn: &CspConnRef) -> i32 { // SAFETY: FFI call. unsafe { ffi::csp_conn_dport(conn.0) } } pub fn csp_service_handler(packet: CspPacketRef) { // SAFETY: FFI call. unsafe { ffi::csp_service_handler(&mut *packet.0) } } /// Rust wrapper for [ffi::csp_close]. pub fn csp_close(conn: CspConnRef) -> i32 { // SAFETY: FFI call. unsafe { ffi::csp_close(conn.0) } } /// Rust wrapper for [ffi::csp_ping], returns the result code directly. pub fn csp_ping_raw(node: u16, timeout: Duration, size: usize, opts: SocketFlags) -> i32 { // SAFETY: FFI call. unsafe { ffi::csp_ping( node, timeout.as_millis() as u32, size as u32, opts.bits() as u8, ) } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct PingError; /// Rust wrapper for [ffi::csp_ping]. pub fn csp_ping( node: u16, timeout: Duration, size: usize, opts: SocketFlags, ) -> Result { let result = csp_ping_raw(node, timeout, size, opts); if result < 0 { return Err(PingError); } Ok(Duration::from_millis(result as u64)) } /// Rust wrapper for [ffi::csp_reboot]. pub fn csp_reboot(node: u16) { // SAFETY: FFI call. unsafe { ffi::csp_reboot(node) } } /// Rust wrapper for [ffi::csp_connect]. pub fn csp_connect( prio: MsgPriority, dst: u16, dst_port: u8, timeout: Duration, opts: ConnectOpts, ) -> Option { // SAFETY: FFI call. let conn = unsafe { ffi::csp_connect( prio as u8, dst, dst_port, timeout.as_millis() as u32, opts.bits(), ) }; if conn.is_null() { return None; } // SAFETY: We checked that the pointer is valid. Some(CspConnRef(conn)) } /// Rust wrapper for [ffi::csp_connect] which returns a guard structure. The connection will be /// be closed automatically when the guard structure is dropped. pub fn csp_connect_guarded( prio: MsgPriority, dst: u16, dst_port: u8, timeout: Duration, opts: ConnectOpts, ) -> Option { Some(CspConnGuard(csp_connect( prio, dst, dst_port, timeout, opts, )?)) } /// Rust wrapper for [ffi::csp_buffer_get]. pub fn csp_buffer_get() -> Option { let packet_ref = unsafe { // The size argument is unused ffi::csp_buffer_get(0) }; if packet_ref.is_null() { return None; } // SAFETY: We checked that the pointer is valid. Some(CspPacketMut(unsafe { &mut *packet_ref })) } /// Rust wrapper for [ffi::csp_send]. pub fn csp_send(conn: &mut CspConnRef, packet: impl Into) { // SAFETY: FFI call. unsafe { ffi::csp_send(conn.0, packet.into().0) } } /// Rust wrapper for [ffi::csp_conn_print_table]. pub fn csp_conn_print_table() { // SAFETY: FFI call. unsafe { ffi::csp_conn_print_table() } } /// Rust wrapper for [ffi::csp_iflist_print]. pub fn csp_iflist_print() { // SAFETY: FFI call. unsafe { ffi::csp_iflist_print() } } /// Rust wrapper for [ffi::csp_buffer_free]. pub fn csp_buffer_free(packet: impl Into) { // SAFETY: FFI call and the Rust type system actually ensure the correct type // is free'd here, while also taking the packet by value. unsafe { ffi::csp_buffer_free(packet.into().0 as *mut libc::c_void) } } /// Rust wrapper for [ffi::csp_transaction_persistent]. /// /// # Parameters /// /// * `in_len`: Use [None] if the length is unknown, and the expected reply length otherwise. /// /// # Returns /// /// 1 or reply size on success, 0 otherwise. pub fn csp_transaction_persistent( conn: &mut CspConnRef, timeout: Duration, out_data: &[u8], in_data: &mut [u8], in_len: Option, ) -> i32 { unsafe { ffi::csp_transaction_persistent( conn.0, timeout.as_millis() as u32, out_data.as_ptr() as *const core::ffi::c_void, out_data.len() as i32, in_data.as_ptr() as *mut core::ffi::c_void, in_len.map(|v| v as i32).unwrap_or(-1), ) } }