diff --git a/libosdp/Cargo.toml b/libosdp/Cargo.toml index 2ab42c3..365ce87 100644 --- a/libosdp/Cargo.toml +++ b/libosdp/Cargo.toml @@ -23,7 +23,9 @@ thiserror = { version = "1.0.50", optional = true } [dev-dependencies] env_logger = "0.11.3" multiqueue = "0.3.2" +rand = "0.8.5" ringbuf = "0.3.3" +sha256 = "1.5.0" [features] default = ["std"] diff --git a/libosdp/tests/commands.rs b/libosdp/tests/commands.rs index 07278ba..cb2489a 100644 --- a/libosdp/tests/commands.rs +++ b/libosdp/tests/commands.rs @@ -3,6 +3,9 @@ // // SPDX-License-Identifier: Apache-2.0 +mod common; +type Result = core::result::Result; + use std::{sync::MutexGuard, thread, time}; use libosdp::{ @@ -14,10 +17,6 @@ use crate::common::{ device::CpDevice, device::PdDevice, memory_channel::MemoryChannel, threadbus::ThreadBus, }; -mod common; - -type Result = core::result::Result; - fn send_command(mut cp: MutexGuard<'_, ControlPanel>, command: OsdpCommand) -> Result<()> { cp.send_command(0, command) } @@ -64,8 +63,8 @@ fn test_commands() -> Result<()> { notify_event(pd.get_device(), event.clone())?; assert_eq!( cp.receiver.recv().unwrap(), - (0 as i32, event), - "Cardread event check failed" + (0_i32, event), + "Card read event check failed" ); Ok(()) diff --git a/libosdp/tests/common/mod.rs b/libosdp/tests/common/mod.rs index 3e17252..0412949 100644 --- a/libosdp/tests/common/mod.rs +++ b/libosdp/tests/common/mod.rs @@ -3,27 +3,14 @@ // // SPDX-License-Identifier: Apache-2.0 -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; - pub mod device; pub mod memory_channel; pub mod threadbus; pub fn setup() { env_logger::builder() - .filter_level(log::LevelFilter::Debug) + .filter_level(log::LevelFilter::Info) .format_target(false) .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 5e8a401..aa43251 100644 --- a/libosdp/tests/common/threadbus.rs +++ b/libosdp/tests/common/threadbus.rs @@ -4,7 +4,14 @@ // SPDX-License-Identifier: Apache-2.0 use multiqueue::{BroadcastReceiver, BroadcastSender}; -use std::{fmt::Debug, io::Error, io::ErrorKind, sync::Mutex}; +use std::{ + collections::hash_map::DefaultHasher, + fmt::Debug, + hash::{Hash, Hasher}, + io::Error, + io::ErrorKind, + sync::Mutex, +}; pub struct ThreadBus { name: String, @@ -13,12 +20,20 @@ pub struct ThreadBus { recv: 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 +} + impl ThreadBus { pub fn new(name: &str) -> Self { let (send, recv) = multiqueue::broadcast_queue(4); Self { name: name.into(), - id: super::str_to_channel_id(name), + id: str_to_channel_id(name), send: Mutex::new(send), recv: Mutex::new(recv), } diff --git a/libosdp/tests/file_transfer.rs b/libosdp/tests/file_transfer.rs new file mode 100644 index 0000000..b5d3be2 --- /dev/null +++ b/libosdp/tests/file_transfer.rs @@ -0,0 +1,173 @@ +// +// Copyright (c) 2024 Siddharth Chandrasekaran +// +// SPDX-License-Identifier: Apache-2.0 + +mod common; + +type Result = core::result::Result; + +use core::time::Duration; +use libosdp::{OsdpCommand, OsdpCommandFileTx, OsdpError, OsdpFileOps}; +use rand::Rng; +use std::{ + cmp, + collections::HashMap, + fs::File, + io::{BufWriter, Write}, + path::PathBuf, + str::FromStr, + thread, +}; + +use crate::common::{device::CpDevice, device::PdDevice, memory_channel::MemoryChannel}; + +#[cfg(not(target_os = "windows"))] +use std::os::unix::prelude::FileExt; +#[cfg(target_os = "windows")] +use std::os::windows::fs::FileExt; + +/// OSDP file transfer context +#[derive(Debug)] +pub struct OsdpFileManager { + files: HashMap, + file: Option, +} + +impl OsdpFileManager { + pub fn new() -> Self { + Self { + files: HashMap::new(), + file: None, + } + } + + pub fn register_file(&mut self, id: i32, path: &str) { + let _ = self.files.insert(id, PathBuf::from_str(path).unwrap()); + } +} + +impl OsdpFileOps for OsdpFileManager { + fn open(&mut self, id: i32, read_only: bool) -> Result { + let path = self + .files + .get(&id) + .ok_or(OsdpError::FileTransfer("Invalid file ID"))?; + log::debug!("File {:?}", path); + let file = if read_only { + File::open(path.as_os_str())? + } else { + File::create(path.as_os_str())? + }; + let size = file.metadata()?.len() as usize; + self.file = Some(file); + Ok(size) + } + + fn offset_read(&self, buf: &mut [u8], off: u64) -> Result { + let file = self + .file + .as_ref() + .ok_or(OsdpError::FileTransfer("File not open"))?; + + #[cfg(not(target_os = "windows"))] + let r = file.read_at(buf, off)?; + + #[cfg(target_os = "windows")] + let r = file.seek_read(buf, off)?; + + Ok(r) + } + + fn offset_write(&self, buf: &[u8], off: u64) -> Result { + let file = self + .file + .as_ref() + .ok_or(OsdpError::FileTransfer("File not open"))?; + + #[cfg(not(target_os = "windows"))] + let r = file.write_at(buf, off)?; + + #[cfg(target_os = "windows")] + let r = file.seek_write(buf, off)?; + + Ok(r) + } + + fn close(&mut self) -> Result<()> { + let _ = self.file.take().unwrap(); + Ok(()) + } +} + +fn create_random_file

(path: P, size: usize) +where + P: AsRef, +{ + if path.as_ref().exists() { + return; + } + + let mut buffer = [0; 1024]; + let mut remaining_size = size; + + let f = File::create(path).unwrap(); + let mut writer = BufWriter::new(f); + + let mut rng = rand::thread_rng(); + + while remaining_size > 0 { + let to_write = cmp::min(remaining_size, buffer.len()); + let buffer = &mut buffer[..to_write]; + rng.fill(buffer); + writer.write_all(buffer).unwrap(); + remaining_size -= to_write; + } +} + +#[test] +fn test_file_transfer() -> Result<()> { + common::setup(); + let (cp_bus, pd_bus) = MemoryChannel::new(); + let pd = PdDevice::new(Box::new(pd_bus))?; + let cp = CpDevice::new(Box::new(cp_bus))?; + + create_random_file("/tmp/ftx_test.in", 50 * 1024); + + thread::sleep(Duration::from_secs(2)); + + let mut fm = OsdpFileManager::new(); + fm.register_file(1, "/tmp/ftx_test.in"); + + cp.get_device().register_file_ops(0, Box::new(fm))?; + + let mut fm = OsdpFileManager::new(); + fm.register_file(1, "/tmp/ftx_test.out"); + + pd.get_device().register_file_ops(Box::new(fm))?; + + let command = OsdpCommand::FileTx(OsdpCommandFileTx::new(1, 0)); + cp.get_device().send_command(0, command.clone())?; + + assert_eq!( + pd.receiver.recv().unwrap(), + command, + "PD file tx command callback verification failed!" + ); + + loop { + let (size, offset) = pd.get_device().file_transfer_status()?; + log::info!("File TX in progress: size:{size} offset:{offset}"); + if size == offset { + break; + } + thread::sleep(Duration::from_secs(1)); + } + + assert_eq!( + sha256::digest(std::fs::read("/tmp/ftx_test.in").unwrap()), + sha256::digest(std::fs::read("/tmp/ftx_test.out").unwrap()), + "Transferred file hash mismatch!" + ); + Ok(()) +}