From 671eaaa490a27fb21b534a8a2016c1d8de6713ed Mon Sep 17 00:00:00 2001 From: Siddharth Chandrasekaran Date: Sat, 9 Mar 2024 20:47:05 +0100 Subject: [PATCH] libosdp: Update to latest version and rework channel API Signed-off-by: Siddharth Chandrasekaran --- libosdp/Cargo.toml | 11 +- libosdp/examples/cp.rs | 45 +++- libosdp/examples/pd.rs | 45 +++- libosdp/src/channel.rs | 67 ++++++ libosdp/src/channel/mod.rs | 220 ------------------ libosdp/src/{types => }/commands.rs | 5 +- libosdp/src/cp.rs | 5 +- libosdp/src/{types => }/events.rs | 1 - libosdp/src/lib.rs | 74 +++++- libosdp/src/mod.rs | 13 ++ libosdp/src/pd.rs | 8 +- libosdp/src/{types => }/pdcap.rs | 0 libosdp/src/{types => }/pdid.rs | 0 libosdp/src/{types => }/pdinfo.rs | 64 ++++- libosdp/src/types/mod.rs | 72 ------ libosdp/tests/commands.rs | 9 +- libosdp/tests/common/device.rs | 13 +- .../common}/memory_channel.rs | 32 +-- libosdp/tests/common/mod.rs | 12 + libosdp/tests/common/threadbus.rs | 32 +-- .../channel => tests/common}/unix_channel.rs | 28 +-- 21 files changed, 353 insertions(+), 403 deletions(-) create mode 100644 libosdp/src/channel.rs delete mode 100644 libosdp/src/channel/mod.rs rename libosdp/src/{types => }/commands.rs (99%) rename libosdp/src/{types => }/events.rs (99%) create mode 100644 libosdp/src/mod.rs rename libosdp/src/{types => }/pdcap.rs (100%) rename libosdp/src/{types => }/pdid.rs (100%) rename libosdp/src/{types => }/pdinfo.rs (61%) delete mode 100644 libosdp/src/types/mod.rs rename libosdp/{src/channel => tests/common}/memory_channel.rs (75%) rename libosdp/{src/channel => tests/common}/unix_channel.rs (78%) diff --git a/libosdp/Cargo.toml b/libosdp/Cargo.toml index d69179e..f8c47d3 100644 --- a/libosdp/Cargo.toml +++ b/libosdp/Cargo.toml @@ -14,20 +14,17 @@ categories = ["development-tools", "embedded"] [dependencies] bitflags = "2.4.0" -embedded-io = "0.6.1" -libosdp-sys = "0.1.5" +libosdp-sys = "3.0.2" log = "0.4.20" -multiqueue = "0.3.2" once_cell = "1.18.0" -parking_lot = { version = "0.12.1", optional = true } -ringbuf = "0.3.3" serde = { version = "1.0.192", features = ["derive"] } -spin = "0.9.8" thiserror = { version = "1.0.50", optional = true } [dev-dependencies] env_logger = "0.10.0" +multiqueue = "0.3.2" +ringbuf = "0.3.3" [features] default = ["std"] -std = ["thiserror", "parking_lot"] +std = ["thiserror"] diff --git a/libosdp/examples/cp.rs b/libosdp/examples/cp.rs index 0154959..b745d2b 100644 --- a/libosdp/examples/cp.rs +++ b/libosdp/examples/cp.rs @@ -4,10 +4,41 @@ // SPDX-License-Identifier: Apache-2.0 use libosdp::{ - channel::{OsdpChannel, UnixChannel}, - ControlPanel, OsdpError, OsdpFlag, PdInfo, + OsdpError, OsdpFlag, PdInfo, Channel, ChannelError, }; -use std::{result::Result, thread, time::Duration, path::PathBuf, str::FromStr}; +use std::{env, thread, time::Duration}; + +struct OsdpChannel; + +impl OsdpChannel { + fn new(_path: &str) -> Self { + // setup device + Self { + } + } +} + +/// Read documentation for each member in [libosdp::Channel]. +impl Channel for OsdpChannel { + fn get_id(&self) -> i32 { + 0 + } + + fn read(&mut self, _buf: &mut [u8]) -> Result { + // TODO: Read from device + Ok(0) + } + + fn write(&mut self, buf: &[u8]) -> Result { + // TODO: Write from device + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), ChannelError> { + // TODO: flush device + Ok(()) + } +} fn main() -> Result<(), OsdpError> { env_logger::builder() @@ -15,20 +46,20 @@ fn main() -> Result<(), OsdpError> { .format_target(false) .format_timestamp(None) .init(); - let path = PathBuf::from_str("/tmp/conn-1")?; - let stream = UnixChannel::connect(&path)?; + let args: Vec = env::args().collect(); + let channel = OsdpChannel::new(&args[1]); let pd_info = vec![PdInfo::for_cp( "PD 101", 101, 115200, OsdpFlag::EnforceSecure, - OsdpChannel::new::(Box::new(stream)), + Box::new(channel), [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, ], )]; - let mut cp = ControlPanel::new(pd_info)?; + let mut cp = libosdp::ControlPanel::new(pd_info)?; loop { cp.refresh(); thread::sleep(Duration::from_millis(50)); diff --git a/libosdp/examples/pd.rs b/libosdp/examples/pd.rs index a3ce672..8a4b148 100644 --- a/libosdp/examples/pd.rs +++ b/libosdp/examples/pd.rs @@ -4,10 +4,41 @@ // SPDX-License-Identifier: Apache-2.0 use libosdp::{ - channel::{OsdpChannel, UnixChannel}, - OsdpError, OsdpFlag, PdCapEntity, PdCapability, PdId, PdInfo, PeripheralDevice, + Channel, ChannelError, OsdpError, OsdpFlag, PdCapEntity, PdCapability, PdId, PdInfo, }; -use std::{result::Result, thread, time::Duration, path::PathBuf, str::FromStr}; +use std::{thread, time::Duration}; + +struct OsdpChannel; + +impl OsdpChannel { + pub fn new(_path: &str) -> Self { + // setup device + Self { + } + } +} + +/// Read documentation for each member in [libosdp::Channel]. +impl Channel for OsdpChannel { + fn get_id(&self) -> i32 { + 0 + } + + fn read(&mut self, _buf: &mut [u8]) -> Result { + // TODO: Read from device + Ok(0) + } + + fn write(&mut self, buf: &[u8]) -> Result { + // TODO: Write from device + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), ChannelError> { + // TODO: flush device + Ok(()) + } +} fn main() -> Result<(), OsdpError> { env_logger::builder() @@ -15,8 +46,8 @@ fn main() -> Result<(), OsdpError> { .format_target(false) .format_timestamp(None) .init(); - let path = PathBuf::from_str("/tmp/conn-1")?; - let stream = UnixChannel::new(&path)?; + let args: Vec = std::env::args().collect(); + let channel = OsdpChannel::new(&args[1]); let pd_info = PdInfo::for_pd( "PD 101", 101, @@ -24,13 +55,13 @@ fn main() -> Result<(), OsdpError> { OsdpFlag::EnforceSecure, PdId::from_number(101), vec![PdCapability::CommunicationSecurity(PdCapEntity::new(1, 1))], - OsdpChannel::new::(Box::new(stream)), + Box::new(channel), [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, ], ); - let mut pd = PeripheralDevice::new(pd_info)?; + let mut pd = libosdp::PeripheralDevice::new(pd_info)?; pd.set_command_callback(|_| { println!("Received command!"); 0 diff --git a/libosdp/src/channel.rs b/libosdp/src/channel.rs new file mode 100644 index 0000000..536cd74 --- /dev/null +++ b/libosdp/src/channel.rs @@ -0,0 +1,67 @@ +// +// Copyright (c) 2023-2024 Siddharth Chandrasekaran +// +// SPDX-License-Identifier: Apache-2.0 + +//! The OSDP specification defines that communication between OSDP devices +//! happen over an RS-485 connection. For testing and development purpose this +//! can be limiting so LibOSDP defines a notion called "Channel" which is a +//! software representation (abstraction) of the physical transport medium. +//! +//! Since RS-485 is stream based protocol, we can think of it to be something +//! that we can read from and write to (which in turn is Read and Write traits +//! in rust). This allows us to run OSDP devices over many IPC schemes such as +//! Unix socket and message queues. +//! +//! This module provides a way to define an OSDP channel and export it to +//! LibOSDP. + +/// OSDP channel errors +#[derive(Clone, Debug)] +pub enum ChannelError { + /// Channel is temporarily unavailable (could have blocked until it was + /// ready but LibOSDP required channel to be non-blocking so return "I would + /// have blocked" instead) + WouldBlock, + /// Channel failed irrecoverably. + TransportError, +} + +impl From for ChannelError { + fn from(value: std::io::Error) -> Self { + match value.kind() { + std::io::ErrorKind::WouldBlock => ChannelError::WouldBlock, + _ => ChannelError::TransportError, + } + } +} + +/// The Channel trait acts as an interface for all channel implementors. See +/// module description for the definition of a "channel" in LibOSDP. +pub trait Channel: Send + Sync { + /// Since OSDP channels can be multi-drop (more than one PD can talk to a + /// CP on the same channel) and LibOSDP supports mixing multi-drop + /// connections among PDs it needs a way to identify each unique channel by + /// a channel ID. Implementors of this trait must also provide a method + /// which returns a unique i32 per channel. + fn get_id(&self) -> i32; + + /// Pull as many bytes into buffer as possible; returns the number of bytes + /// were read. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Write a buffer into this writer, returning how many bytes were written. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + fn flush(&mut self) -> Result<(), ChannelError>; +} + +impl core::fmt::Debug for dyn Channel { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Channel") + .field("id", &self.get_id()) + .finish() + } +} \ No newline at end of file diff --git a/libosdp/src/channel/mod.rs b/libosdp/src/channel/mod.rs deleted file mode 100644 index 2934a5b..0000000 --- a/libosdp/src/channel/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -// -// Copyright (c) 2023-2024 Siddharth Chandrasekaran -// -// SPDX-License-Identifier: Apache-2.0 - -//! The OSDP specification defines that communication between OSDP devices -//! happen over an RS-485 connection. For testing and development purpose this -//! can be limiting so LibOSDP defines a notion called "Channel" which is a -//! software representation (abstraction) of the physical transport medium. -//! -//! Since RS-485 is stream based protocol, we can think of it to be something -//! that we can read from and write to (which in turn is Read and Write traits -//! in rust). This allows us to run OSDP devices over many IPC schemes such as -//! Unix socket and message queues. -//! -//! This module provides a way to define an OSDP channel and export it to -//! LibOSDP. - -mod memory_channel; -#[cfg(feature = "std")] -mod unix_channel; - -#[cfg(feature = "std")] -pub use memory_channel::MemoryChannel; -use once_cell::sync::Lazy; -#[cfg(feature = "std")] -pub use unix_channel::UnixChannel; - -#[cfg(not(feature = "std"))] -use alloc::collections::BTreeMap as HashMap; -use alloc::{boxed::Box, format, sync::Arc, vec}; -use core::ffi::c_void; -#[cfg(feature = "std")] -use std::{ - collections::{hash_map::DefaultHasher, HashMap}, - hash::{Hash, Hasher}, -}; - -#[cfg(feature = "std")] -use parking_lot::Mutex; -#[cfg(not(feature = "std"))] -use spin::Mutex; - -static CHANNELS: Lazy>>>>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -impl alloc::fmt::Debug for OsdpChannel { - fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result { - f.debug_struct("OsdpChannel") - .field("stream", &format!("{}", self.stream.lock().get_id())) - .finish() - } -} - -unsafe extern "C" fn raw_read(data: *mut c_void, buf: *mut u8, len: i32) -> i32 { - let key = data as i32; - let mut channels = CHANNELS.lock(); - let mut stream = channels.get_mut(&key).unwrap().lock(); - let mut read_buf = vec![0u8; len as usize]; - match stream.read(&mut read_buf) { - Ok(n) => { - let src_ptr = read_buf.as_mut_ptr(); - core::ptr::copy_nonoverlapping(src_ptr, buf, len as usize); - n as i32 - } - Err(_) => -1, - } -} - -unsafe extern "C" fn raw_write(data: *mut c_void, buf: *mut u8, len: i32) -> i32 { - let key = data as i32; - let mut channels = CHANNELS.lock(); - let mut stream = channels.get_mut(&key).unwrap().lock(); - let mut write_buf = vec![0u8; len as usize]; - core::ptr::copy_nonoverlapping(buf, write_buf.as_mut_ptr(), len as usize); - match stream.write(&write_buf) { - Ok(n) => n as i32, - Err(_) => -1, - } -} - -unsafe extern "C" fn raw_flush(data: *mut c_void) { - let key = data as i32; - let mut channels = CHANNELS.lock(); - let mut stream = channels.get_mut(&key).unwrap().lock(); - let _ = stream.flush(); -} - -#[cfg(not(feature = "std"))] -/// The error type for I/O operations of the [`Read`] and [`Write`] associated traits. -pub type RWError = Box; -#[cfg(feature = "std")] -/// The error type for I/O operations of the [`Read`] and [`Write`] associated traits. -pub type RWError = std::io::Error; - -/// The `Read` trait allows for reading bytes from a source. -/// -/// Wrapper around either [`std::io::Read`] or [`embedded_io::Read`] depending on the availability of `std`. -pub trait Read { - /// Pull some bytes from this source into the specified buffer, returning - /// how many bytes were read. - /// - /// Wrapper around either [`std::io::Read::read`] or [`embedded_io::Read::read`] depending on the availability of `std`. - fn read(&mut self, buf: &mut [u8]) -> Result; -} - -/// A trait for objects which are byte-oriented sinks. -/// -/// Wrapper around either [`std::io::Write`] or [`embedded_io::Write`] depending on the availability of `std`. -pub trait Write { - /// Write a buffer into this writer, returning how many bytes were written. - /// - /// Wrapper around either [`std::io::Write::write`] or [`embedded_io::Write::write`] depending on the availability of `std`. - fn write(&mut self, buf: &[u8]) -> Result; - /// Flush this output stream, ensuring that all intermediately buffered - /// contents reach their destination. - /// - /// Wrapper around either [`std::io::Write::flush`] or [`embedded_io::Write::flush`] depending on the availability of `std`. - fn flush(&mut self) -> Result<(), RWError>; -} - -#[cfg(feature = "std")] -impl Read for T { - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> Result { - self.read(buf) - } -} - -#[cfg(feature = "std")] -impl Write for T { - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf) - } - #[inline(always)] - fn flush(&mut self) -> Result<(), RWError> { - self.flush() - } -} - -#[cfg(not(feature = "std"))] -impl Read for T -where - T::Error: 'static, -{ - #[inline(always)] - fn read(&mut self, buf: &mut [u8]) -> Result { - self.read(buf) - .map_err(|e| Box::new(e) as Box) - } -} - -#[cfg(not(feature = "std"))] -impl Write for T -where - T::Error: 'static, -{ - #[inline(always)] - fn write(&mut self, buf: &[u8]) -> Result { - self.write(buf) - .map_err(|e| Box::new(e) as Box) - } - #[inline(always)] - fn flush(&mut self) -> Result<(), RWError> { - self.flush() - .map_err(|e| Box::new(e) as Box) - } -} - -/// The Channel trait acts as an interface for all channel implementors. See -/// module description for the definition of a "channel" in LibOSDP. -pub trait Channel: Read + Write + Send + Sync { - /// Since OSDP channels can be multi-drop (more than one PD can talk to a - /// CP on the same channel) and LibOSDP supports mixing multi-drop - /// connections among PDs it needs a way to identify each unique channel by - /// a channel ID. Implementors of this trait must also provide a method - /// which returns a unique i32 per channel. - fn get_id(&self) -> i32; -} - -/// A wrapper to hold anything that implements the Channel trait. It is used to -/// export the inner value to LibOSDP in a format that it expects it to be in. -pub struct OsdpChannel { - stream: Arc>>, -} - -impl OsdpChannel { - /// Create an instance of OsdpChannel for a given object that implements - /// the Channel Trait. - pub fn new(stream: Box) -> OsdpChannel { - Self { - stream: Arc::new(Mutex::new(stream)), - } - } - - /// For internal use; in as_struct() of [`crate::PdInfo`]. This methods - /// exports the channel to LibOSDP as a C struct. - pub fn as_struct(&self) -> libosdp_sys::osdp_channel { - let stream = self.stream.clone(); - let id = stream.lock().get_id(); - CHANNELS.lock().insert(id, stream); - libosdp_sys::osdp_channel { - id, - data: id as *mut c_void, - recv: Some(raw_read), - send: Some(raw_write), - flush: Some(raw_flush), - } - } -} - -#[cfg(feature = "std")] -fn str_to_channel_id(key: &str) -> i32 { - let mut hasher = DefaultHasher::new(); - key.hash(&mut hasher); - let mut id = hasher.finish(); - id = (id >> 32) ^ id & 0xffffffff; - id as i32 -} diff --git a/libosdp/src/types/commands.rs b/libosdp/src/commands.rs similarity index 99% rename from libosdp/src/types/commands.rs rename to libosdp/src/commands.rs index dc83f35..21ee1a0 100644 --- a/libosdp/src/types/commands.rs +++ b/libosdp/src/commands.rs @@ -7,8 +7,7 @@ //! are specified by OSDP specification. This module is responsible to handling //! such commands though [`OsdpCommand`]. -use crate::types::OsdpStatusReport; -use alloc::vec::Vec; +use crate::OsdpStatusReport; use serde::{Deserialize, Serialize}; use super::ConvertEndian; @@ -639,7 +638,7 @@ impl From for OsdpCommand { #[cfg(test)] mod tests { - use crate::types::OsdpCommandMfg; + use crate::OsdpCommandMfg; use libosdp_sys::osdp_cmd_mfg; #[test] diff --git a/libosdp/src/cp.rs b/libosdp/src/cp.rs index 35460a0..22d36b7 100644 --- a/libosdp/src/cp.rs +++ b/libosdp/src/cp.rs @@ -9,7 +9,6 @@ #[cfg(feature = "std")] use crate::file::{impl_osdp_file_ops_for, OsdpFile, OsdpFileOps}; use crate::{OsdpCommand, OsdpError, OsdpEvent, OsdpFlag, PdCapability, PdId, PdInfo}; -use alloc::vec::Vec; use core::ffi::c_void; use log::{debug, error, info, warn}; @@ -102,12 +101,12 @@ impl ControlPanel { /// ]; /// let mut cp = ControlPanel::new(pd_info).unwrap(); /// ``` - pub fn new(pd_info: Vec) -> Result { + pub fn new(mut pd_info: Vec) -> Result { if pd_info.len() > 126 { return Err(OsdpError::PdInfo("max PD count exceeded")); } let info: Vec = - pd_info.iter().map(|i| i.as_struct()).collect(); + pd_info.iter_mut().map(|i| i.as_struct()).collect(); unsafe { libosdp_sys::osdp_set_log_callback(Some(log_handler)) }; Ok(Self { ctx: cp_setup(info)?, diff --git a/libosdp/src/types/events.rs b/libosdp/src/events.rs similarity index 99% rename from libosdp/src/types/events.rs rename to libosdp/src/events.rs index 1885ae7..dcbd271 100644 --- a/libosdp/src/types/events.rs +++ b/libosdp/src/events.rs @@ -9,7 +9,6 @@ //! module is responsible to handling such events though [`OsdpEvent`]. use crate::OsdpError; -use alloc::vec::Vec; use serde::{Deserialize, Serialize}; use super::ConvertEndian; diff --git a/libosdp/src/lib.rs b/libosdp/src/lib.rs index e511434..2548607 100644 --- a/libosdp/src/lib.rs +++ b/libosdp/src/lib.rs @@ -126,12 +126,24 @@ extern crate alloc; -pub mod channel; mod cp; pub mod file; #[cfg(feature = "std")] mod pd; -mod types; +mod commands; +mod events; +mod pdcap; +mod pdid; +mod pdinfo; +mod channel; + +// Re-export for convenience +pub use channel::*; +pub use commands::*; +pub use events::*; +pub use pdcap::*; +pub use pdid::*; +pub use pdinfo::*; #[allow(unused_imports)] use alloc::{ @@ -144,7 +156,6 @@ use thiserror::Error; pub use cp::ControlPanel; pub use pd::PeripheralDevice; -pub use types::*; /// OSDP public errors #[derive(Debug, Default)] @@ -202,6 +213,63 @@ impl From for OsdpError { } } +impl From for OsdpError { + fn from(value: ChannelError) -> OsdpError { + match value { + ChannelError::WouldBlock => OsdpError::Channel("WouldBlock"), + ChannelError::TransportError => OsdpError::Channel("TransportError"), + } + } +} + +/// Trait to convert between BigEndian and LittleEndian types +pub trait ConvertEndian { + /// Return `Self` as BigEndian + fn as_be(&self) -> u32; + /// Return `Self` as LittleEndian + fn as_le(&self) -> u32; +} + +bitflags::bitflags! { + /// OSDP setup flags + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct OsdpFlag: u32 { + /// Make security conscious assumptions where possible. Fail where these + /// assumptions don't hold. The following restrictions are enforced in + /// this mode: + /// + /// - Don't allow use of SCBK-D (implies no INSTALL_MODE) + /// - Assume that a KEYSET was successful at an earlier time + /// - Disallow master key based SCBK derivation + const EnforceSecure = libosdp_sys::OSDP_FLAG_ENFORCE_SECURE; + + /// When set, the PD would allow one session of secure channel to be + /// setup with SCBK-D. + /// + /// In this mode, the PD is in a vulnerable state, the application is + /// responsible for making sure that the device enters this mode only + /// during controlled/provisioning-time environments. + const InstallMode = libosdp_sys::OSDP_FLAG_INSTALL_MODE; + + /// When set, CP will not error and fail when the PD sends an unknown, + /// unsolicited response. In PD mode this flag has no use. + const IgnoreUnsolicited = libosdp_sys::OSDP_FLAG_IGN_UNSOLICITED; + } +} + +impl FromStr for OsdpFlag { + type Err = OsdpError; + + fn from_str(s: &str) -> Result { + match s { + "EnforceSecure" => Ok(OsdpFlag::EnforceSecure), + "InstallMode" => Ok(OsdpFlag::InstallMode), + "IgnoreUnsolicited" => Ok(OsdpFlag::IgnoreUnsolicited), + _ => Err(OsdpError::Parse(format!("OsdpFlag: {s}"))), + } + } +} + fn cstr_to_string(s: *const ::core::ffi::c_char) -> String { let s = unsafe { core::ffi::CStr::from_ptr(s) }; s.to_str().unwrap().to_owned() diff --git a/libosdp/src/mod.rs b/libosdp/src/mod.rs new file mode 100644 index 0000000..a740ffc --- /dev/null +++ b/libosdp/src/mod.rs @@ -0,0 +1,13 @@ +// +// Copyright (c) 2023-2024 Siddharth Chandrasekaran +// +// SPDX-License-Identifier: Apache-2.0 + +///! OSDP Command and Event Types +///! +///! This module is responsible to creating various types that can move between +///! C and Rust code. + +use crate::OsdpError; +use core::str::FromStr; + diff --git a/libosdp/src/pd.rs b/libosdp/src/pd.rs index 7a8b281..4d66312 100644 --- a/libosdp/src/pd.rs +++ b/libosdp/src/pd.rs @@ -12,13 +12,12 @@ //! happens on the PD itself (such as card read, key press, etc.,) snd sends it //! to the CP. -use crate::types::{PdCapability, PdInfo}; +use crate::{PdCapability, PdInfo}; #[cfg(feature = "std")] use crate::{ file::{impl_osdp_file_ops_for, OsdpFile, OsdpFileOps}, OsdpCommand, OsdpError, OsdpEvent, }; -use alloc::vec::Vec; use core::ffi::c_void; use log::{debug, error, info, warn}; @@ -63,9 +62,8 @@ where trampoline:: } -fn pd_setup(info: PdInfo) -> Result<*mut c_void> { - let info = info.as_struct(); - let ctx = unsafe { libosdp_sys::osdp_pd_setup(&info) }; +fn pd_setup(mut info: PdInfo) -> Result<*mut c_void> { + let ctx = unsafe { libosdp_sys::osdp_pd_setup(&info.as_struct()) }; if ctx.is_null() { Err(OsdpError::Setup) } else { diff --git a/libosdp/src/types/pdcap.rs b/libosdp/src/pdcap.rs similarity index 100% rename from libosdp/src/types/pdcap.rs rename to libosdp/src/pdcap.rs diff --git a/libosdp/src/types/pdid.rs b/libosdp/src/pdid.rs similarity index 100% rename from libosdp/src/types/pdid.rs rename to libosdp/src/pdid.rs diff --git a/libosdp/src/types/pdinfo.rs b/libosdp/src/pdinfo.rs similarity index 61% rename from libosdp/src/types/pdinfo.rs rename to libosdp/src/pdinfo.rs index 5c2f616..34846bc 100644 --- a/libosdp/src/types/pdinfo.rs +++ b/libosdp/src/pdinfo.rs @@ -4,10 +4,54 @@ // SPDX-License-Identifier: Apache-2.0 use alloc::ffi::CString; +use core::ffi::c_void; -use crate::channel::OsdpChannel; +use super::{OsdpFlag, PdCapability, PdId, Channel, ChannelError}; -use super::{OsdpFlag, PdCapability, PdId}; +unsafe extern "C" fn raw_read(data: *mut c_void, buf: *mut u8, len: i32) -> i32 { + let channel: *mut Box = data as *mut _; + let channel = channel.as_mut().unwrap(); + let mut read_buf = vec![0u8; len as usize]; + match channel.read(&mut read_buf) { + Ok(n) => { + let src_ptr = read_buf.as_mut_ptr(); + core::ptr::copy_nonoverlapping(src_ptr, buf, len as usize); + n as i32 + } + Err(ChannelError::WouldBlock) => 0, + Err(_) => -1, + } +} + +unsafe extern "C" fn raw_write(data: *mut c_void, buf: *mut u8, len: i32) -> i32 { + let channel: *mut Box = data as *mut _; + let channel = channel.as_mut().unwrap(); + let mut write_buf = vec![0u8; len as usize]; + core::ptr::copy_nonoverlapping(buf, write_buf.as_mut_ptr(), len as usize); + match channel.as_mut().write(&write_buf) { + Ok(n) => n as i32, + Err(ChannelError::WouldBlock) => 0, + Err(_) => -1, + } +} + +unsafe extern "C" fn raw_flush(data: *mut c_void) { + let channel: *mut Box = data as *mut _; + let channel = channel.as_mut().unwrap(); + let _ = channel.as_mut().flush(); +} + +fn into_osdp_channel(channel: Box) -> libosdp_sys::osdp_channel { + let id = channel.get_id(); + let data = Box::into_raw(Box::new(channel)); + libosdp_sys::osdp_channel { + id, + data: data as *mut c_void, + recv: Some(raw_read), + send: Some(raw_write), + flush: Some(raw_flush), + } +} /// OSDP PD Information. This struct is used to describe a PD to LibOSDP #[derive(Debug)] @@ -18,7 +62,7 @@ pub struct PdInfo { flags: OsdpFlag, id: PdId, cap: Vec, - channel: OsdpChannel, + channel: Option>, scbk: [u8; 16], } @@ -46,7 +90,7 @@ impl PdInfo { flags: OsdpFlag, id: PdId, cap: Vec, - channel: OsdpChannel, + channel: Box, scbk: [u8; 16], ) -> Self { let name = CString::new(name).unwrap(); @@ -58,7 +102,7 @@ impl PdInfo { flags, id, cap, - channel, + channel: Some(channel), scbk, } } @@ -80,7 +124,7 @@ impl PdInfo { address: i32, baud_rate: i32, flags: OsdpFlag, - channel: OsdpChannel, + channel: Box, scbk: [u8; 16], ) -> Self { let name = CString::new(name).unwrap(); @@ -91,13 +135,13 @@ impl PdInfo { flags, id: PdId::default(), cap: vec![], - channel, + channel: Some(channel), scbk, } } - /// Returns the equivalent C struct - pub fn as_struct(&self) -> libosdp_sys::osdp_pd_info_t { + pub fn as_struct(&mut self) -> libosdp_sys::osdp_pd_info_t { + let channel = into_osdp_channel(self.channel.take().unwrap()); libosdp_sys::osdp_pd_info_t { name: self.name.as_ptr(), baud_rate: self.baud_rate, @@ -105,7 +149,7 @@ impl PdInfo { flags: self.flags.bits() as i32, id: self.id.clone().into(), cap: self.cap.as_ptr(), - channel: self.channel.as_struct(), + channel, scbk: self.scbk.as_ptr(), } } diff --git a/libosdp/src/types/mod.rs b/libosdp/src/types/mod.rs deleted file mode 100644 index a47da82..0000000 --- a/libosdp/src/types/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright (c) 2023-2024 Siddharth Chandrasekaran -// -// SPDX-License-Identifier: Apache-2.0 - -///! OSDP Command and Event Types -///! -///! This module is responsible to creating various types that can move between -///! C and Rust code. -mod commands; -mod events; -mod pdcap; -mod pdid; -mod pdinfo; - -use crate::OsdpError; -use core::str::FromStr; - -// Re-export for convenience -pub use commands::*; -pub use events::*; -pub use pdcap::*; -pub use pdid::*; -pub use pdinfo::*; - -/// Trait to convert between BigEndian and LittleEndian types -pub trait ConvertEndian { - /// Return `Self` as BigEndian - fn as_be(&self) -> u32; - /// Return `Self` as LittleEndian - fn as_le(&self) -> u32; -} - -bitflags::bitflags! { - /// OSDP setup flags - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct OsdpFlag: u32 { - /// Make security conscious assumptions where possible. Fail where these - /// assumptions don't hold. The following restrictions are enforced in - /// this mode: - /// - /// - Don't allow use of SCBK-D (implies no INSTALL_MODE) - /// - Assume that a KEYSET was successful at an earlier time - /// - Disallow master key based SCBK derivation - const EnforceSecure = libosdp_sys::OSDP_FLAG_ENFORCE_SECURE; - - /// When set, the PD would allow one session of secure channel to be - /// setup with SCBK-D. - /// - /// In this mode, the PD is in a vulnerable state, the application is - /// responsible for making sure that the device enters this mode only - /// during controlled/provisioning-time environments. - const InstallMode = libosdp_sys::OSDP_FLAG_INSTALL_MODE; - - /// When set, CP will not error and fail when the PD sends an unknown, - /// unsolicited response. In PD mode this flag has no use. - const IgnoreUnsolicited = libosdp_sys::OSDP_FLAG_IGN_UNSOLICITED; - } -} - -impl FromStr for OsdpFlag { - type Err = OsdpError; - - fn from_str(s: &str) -> Result { - match s { - "EnforceSecure" => Ok(OsdpFlag::EnforceSecure), - "InstallMode" => Ok(OsdpFlag::InstallMode), - "IgnoreUnsolicited" => Ok(OsdpFlag::IgnoreUnsolicited), - _ => Err(OsdpError::Parse(format!("OsdpFlag: {s}"))), - } - } -} diff --git a/libosdp/tests/commands.rs b/libosdp/tests/commands.rs index 60c9ab9..07ce002 100644 --- a/libosdp/tests/commands.rs +++ b/libosdp/tests/commands.rs @@ -6,11 +6,10 @@ use std::{sync::MutexGuard, thread, time}; use libosdp::{ - channel::{MemoryChannel, Read, Write}, - ControlPanel, OsdpCommand, OsdpCommandBuzzer, OsdpEvent, OsdpEventCardRead, PeripheralDevice, + ControlPanel, OsdpCommand, OsdpCommandBuzzer, OsdpEvent, OsdpEventCardRead, PeripheralDevice, Channel, }; -use crate::common::{device::CpDevice, device::PdDevice, threadbus::ThreadBus}; +use crate::common::{device::CpDevice, device::PdDevice, threadbus::ThreadBus, memory_channel::MemoryChannel}; mod common; @@ -42,8 +41,8 @@ fn test_thread_bus_channel() -> Result<()> { fn test_commands() -> Result<()> { common::setup(); let (cp_bus, pd_bus) = MemoryChannel::new(); - let pd = PdDevice::new(pd_bus)?; - let cp = CpDevice::new(cp_bus)?; + let pd = PdDevice::new(Box::new(pd_bus))?; + let cp = CpDevice::new(Box::new(cp_bus))?; thread::sleep(time::Duration::from_secs(2)); loop { diff --git a/libosdp/tests/common/device.rs b/libosdp/tests/common/device.rs index 868e758..5fb588b 100644 --- a/libosdp/tests/common/device.rs +++ b/libosdp/tests/common/device.rs @@ -9,9 +9,8 @@ use std::{ }; use libosdp::{ - channel::{Channel, OsdpChannel}, - ControlPanel, OsdpCommand, OsdpEvent, OsdpFlag, PdCapEntity, PdCapability, PdId, PdInfo, - PeripheralDevice, + ControlPanel, OsdpCommand, OsdpEvent, OsdpFlag, + PdCapEntity, PdCapability, PdId, PdInfo, PeripheralDevice, }; type Result = core::result::Result; @@ -21,13 +20,13 @@ pub struct CpDevice { } impl CpDevice { - pub fn new(bus: T) -> Result { + pub fn new(bus: Box) -> Result { let pd_info = vec![PdInfo::for_cp( "PD 101", 101, 115200, OsdpFlag::empty(), - OsdpChannel::new::(Box::new(bus)), + bus, [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -74,7 +73,7 @@ pub struct PdDevice { } impl PdDevice { - pub fn new(bus: T) -> Result { + pub fn new(bus: Box) -> Result { let pd_info = PdInfo::for_pd( "PD 101", 101, @@ -86,7 +85,7 @@ impl PdDevice { PdCapability::AudibleOutput(PdCapEntity::new(1, 1)), PdCapability::LedControl(PdCapEntity::new(1, 1)), ], - OsdpChannel::new::(Box::new(bus)), + bus, [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, diff --git a/libosdp/src/channel/memory_channel.rs b/libosdp/tests/common/memory_channel.rs similarity index 75% rename from libosdp/src/channel/memory_channel.rs rename to libosdp/tests/common/memory_channel.rs index eec225f..9f0dcac 100644 --- a/libosdp/src/channel/memory_channel.rs +++ b/libosdp/tests/common/memory_channel.rs @@ -3,10 +3,10 @@ // // SPDX-License-Identifier: Apache-2.0 -use alloc::sync::Arc; -use ringbuf::HeapRb; +use std::{sync::Arc, io::{Write, Read}}; -use super::Channel; +use libosdp::ChannelError; +use ringbuf::HeapRb; /// An in-memory OSDP channel suitable for testing pub struct MemoryChannel { @@ -45,22 +45,22 @@ impl MemoryChannel { } } -impl super::Write for MemoryChannel { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.sender.write(buf) - } - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) +impl libosdp::Channel for MemoryChannel { + fn get_id(&self) -> i32 { + self.id } -} -impl super::Read for MemoryChannel { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + + fn read(&mut self, buf: &mut [u8]) -> Result { self.receiver.read(buf) + .map_err(ChannelError::from) } -} -impl Channel for MemoryChannel { - fn get_id(&self) -> i32 { - self.id + fn write(&mut self, buf: &[u8]) -> Result { + self.sender.write(buf) + .map_err(ChannelError::from) + } + + fn flush(&mut self) -> Result<(), libosdp::ChannelError> { + Ok(()) } } diff --git a/libosdp/tests/common/mod.rs b/libosdp/tests/common/mod.rs index 7b6a9cb..3766263 100644 --- a/libosdp/tests/common/mod.rs +++ b/libosdp/tests/common/mod.rs @@ -3,8 +3,12 @@ // // SPDX-License-Identifier: Apache-2.0 +use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; + pub mod device; pub mod threadbus; +pub mod memory_channel; +pub mod unix_channel; pub fn setup() { env_logger::builder() @@ -13,3 +17,11 @@ pub fn setup() { .format_timestamp(None) .init(); } + +pub fn str_to_channel_id(key: &str) -> i32 { + let mut hasher = DefaultHasher::new(); + key.hash(&mut hasher); + let mut id: u64 = hasher.finish(); + id = (id >> 32) ^ id & 0xffffffff; + id as i32 +} diff --git a/libosdp/tests/common/threadbus.rs b/libosdp/tests/common/threadbus.rs index c5914df..b72afb5 100644 --- a/libosdp/tests/common/threadbus.rs +++ b/libosdp/tests/common/threadbus.rs @@ -5,22 +5,12 @@ use multiqueue::{BroadcastReceiver, BroadcastSender}; use std::{ - collections::hash_map::DefaultHasher, fmt::Debug, - hash::{Hash, Hasher}, io::Error, io::ErrorKind, sync::Mutex, }; -fn str_to_channel_id(key: &str) -> i32 { - let mut hasher = DefaultHasher::new(); - key.hash(&mut hasher); - let mut id: u64 = hasher.finish(); - id = (id >> 32) ^ id & 0xffffffff; - id as i32 -} - pub struct ThreadBus { name: String, id: i32, @@ -33,7 +23,7 @@ impl ThreadBus { let (send, recv) = multiqueue::broadcast_queue(4); Self { name: name.into(), - id: str_to_channel_id(name), + id: super::str_to_channel_id(name), send: Mutex::new(send), recv: Mutex::new(recv), } @@ -62,8 +52,12 @@ impl Debug for ThreadBus { } } -impl std::io::Read for ThreadBus { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { +impl libosdp::Channel for ThreadBus { + fn get_id(&self) -> i32 { + self.id + } + + fn read(&mut self, buf: &mut [u8]) -> Result { let v = self.recv.lock().unwrap().try_recv().map_err(|e| match e { std::sync::mpsc::TryRecvError::Empty => Error::new(ErrorKind::WouldBlock, "No data"), std::sync::mpsc::TryRecvError::Disconnected => { @@ -73,10 +67,8 @@ impl std::io::Read for ThreadBus { buf[..v.len()].copy_from_slice(&v[..]); Ok(v.len()) } -} -impl std::io::Write for ThreadBus { - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> Result { let v = buf.into(); self.send.lock().unwrap().try_send(v).map_err(|e| match e { std::sync::mpsc::TrySendError::Full(_) => Error::new(ErrorKind::WouldBlock, "No space"), @@ -87,13 +79,7 @@ impl std::io::Write for ThreadBus { Ok(buf.len()) } - fn flush(&mut self) -> std::io::Result<()> { + fn flush(&mut self) -> Result<(), libosdp::ChannelError> { Ok(()) } } - -impl libosdp::channel::Channel for ThreadBus { - fn get_id(&self) -> i32 { - self.id - } -} diff --git a/libosdp/src/channel/unix_channel.rs b/libosdp/tests/common/unix_channel.rs similarity index 78% rename from libosdp/src/channel/unix_channel.rs rename to libosdp/tests/common/unix_channel.rs index 8c13e47..092b48b 100644 --- a/libosdp/src/channel/unix_channel.rs +++ b/libosdp/tests/common/unix_channel.rs @@ -5,7 +5,6 @@ //! OSDP unix channel -use super::Channel; use core::time::Duration; use std::{ io::{Read, Write}, @@ -15,7 +14,9 @@ use std::{ thread, }; -type Result = std::result::Result; +use libosdp::ChannelError; + +type Result = std::result::Result; /// A reference OSDP channel implementation for unix domain socket. #[derive(Debug)] @@ -59,24 +60,23 @@ impl UnixChannel { } } -impl Read for UnixChannel { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { +impl libosdp::Channel for UnixChannel { + fn get_id(&self) -> i32 { + self.id + } + + fn read(&mut self, buf: &mut [u8]) -> std::prelude::v1::Result { self.stream.read(buf) + .map_err(ChannelError::from) } -} -impl Write for UnixChannel { - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> std::prelude::v1::Result { self.stream.write(buf) + .map_err(ChannelError::from) } - fn flush(&mut self) -> std::io::Result<()> { + fn flush(&mut self) -> std::prelude::v1::Result<(), libosdp::ChannelError> { self.stream.flush() - } -} - -impl Channel for UnixChannel { - fn get_id(&self) -> i32 { - self.id + .map_err(ChannelError::from) } }