use crate::underlying::sys::DEFAULT_TIMEOUT_MS;
use bytes::{Bytes, BytesMut};
use std::{
ffi::CStr,
fs::{File, OpenOptions},
io::{Error as IoError, ErrorKind as IoErrorKind, IoSlice, IoSliceMut, Result as IoResult},
os::windows::{fs::OpenOptionsExt, io::AsRawHandle},
path::{Path, PathBuf},
time::Duration,
};
use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
use windows::{
Win32::{
Devices::Communication::{
CLRDTR, CLRRTS, COMMTIMEOUTS, DCB, EscapeCommFunction, GetCommModemStatus,
GetCommTimeouts, MODEM_STATUS_FLAGS, MS_CTS_ON, MS_DSR_ON, MS_RING_ON, MS_RLSD_ON,
NOPARITY, ONESTOPBIT, PURGE_COMM_FLAGS, PURGE_RXCLEAR, PURGE_TXCLEAR, PurgeComm,
SETDTR, SETRTS, SetCommState, SetCommTimeouts,
},
Foundation::{CloseHandle, ERROR_IO_PENDING, ERROR_NO_MORE_ITEMS, HANDLE},
Storage::FileSystem::{FILE_FLAG_OVERLAPPED, FlushFileBuffers, ReadFile, WriteFile},
System::{
IO::{GetOverlappedResult, OVERLAPPED},
Registry::{
HKEY, HKEY_LOCAL_MACHINE, KEY_READ, REG_SAM_FLAGS, REG_SZ, RegCloseKey,
RegEnumValueA, RegOpenKeyExA, RegQueryInfoKeyA,
},
Threading::CreateEventA,
},
},
core::{PCSTR, PSTR},
};
#[derive(Debug)]
pub struct RawSyncSerialPort {
pub fd: File,
}
impl RawSyncSerialPort {
#[allow(
// Guaranteed to not truncate.
clippy::cast_possible_truncation,
)]
pub fn new(path: impl AsRef<Path>) -> IoResult<Self> {
let mut serial_port_path = PathBuf::from(r"\\.");
serial_port_path.push(path);
let fd = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.custom_flags(FILE_FLAG_OVERLAPPED.0)
.open(&serial_port_path)?;
let timeouts = COMMTIMEOUTS {
ReadIntervalTimeout: u32::MAX,
ReadTotalTimeoutMultiplier: u32::MAX,
ReadTotalTimeoutConstant: DEFAULT_TIMEOUT_MS,
WriteTotalTimeoutMultiplier: 0,
WriteTotalTimeoutConstant: DEFAULT_TIMEOUT_MS,
};
unsafe {
SetCommTimeouts(HANDLE(fd.as_raw_handle()), &timeouts)
.map_err(|_| IoError::last_os_error())?;
}
let dcb = DCB {
DCBlength: std::mem::size_of::<DCB>() as u32,
BaudRate: 57600,
ByteSize: 8,
Parity: NOPARITY,
StopBits: ONESTOPBIT,
..Default::default()
};
unsafe {
SetCommState(HANDLE(fd.as_raw_handle()), &dcb).map_err(|_| IoError::last_os_error())?;
}
Ok(Self { fd })
}
pub fn try_clone(&self) -> IoResult<Self> {
Ok(Self {
fd: self.fd.try_clone()?,
})
}
pub fn get_read_timeout(&self) -> IoResult<Duration> {
let mut timeouts = unsafe { std::mem::zeroed() };
unsafe {
GetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &mut timeouts)
.map_err(|_| IoError::last_os_error())?;
}
Ok(Duration::from_millis(
timeouts.ReadTotalTimeoutConstant.into(),
))
}
pub fn get_write_timeout(&self) -> IoResult<Duration> {
let mut timeouts = unsafe { std::mem::zeroed() };
unsafe {
GetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &mut timeouts)
.map_err(|_| IoError::last_os_error())?;
}
Ok(Duration::from_millis(
timeouts.WriteTotalTimeoutConstant.into(),
))
}
pub fn set_read_timeout(&self, new_timeout: Duration) -> IoResult<()> {
let timeout_ms = new_timeout.as_millis();
if timeout_ms < 1 || timeout_ms > (u32::MAX - 1).into() {
return Err(IoError::other(
"read timeout must be between 1, and 4294967294",
));
}
let mut timeouts = unsafe { std::mem::zeroed() };
unsafe {
GetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &mut timeouts)
.map_err(|_| IoError::last_os_error())?;
}
timeouts.ReadIntervalTimeout = u32::MAX;
timeouts.ReadTotalTimeoutMultiplier = u32::MAX;
timeouts.ReadTotalTimeoutConstant = timeout_ms.try_into().unwrap_or(u32::MAX);
unsafe {
SetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &timeouts)
.map_err(|_| IoError::last_os_error())?;
}
Ok(())
}
pub fn set_write_timeout(&self, new_timeout: Duration) -> IoResult<()> {
let timeout_ms = new_timeout.as_millis();
if timeout_ms < 1 || timeout_ms > (u32::MAX - 1).into() {
return Err(IoError::other(
"write timeout must be between 1, and 4294967294",
));
}
let mut timeouts = unsafe { std::mem::zeroed() };
unsafe {
GetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &mut timeouts)
.map_err(|_| IoError::last_os_error())?;
}
timeouts.WriteTotalTimeoutMultiplier = u32::MAX;
timeouts.WriteTotalTimeoutConstant = timeout_ms.try_into().unwrap_or(u32::MAX);
unsafe {
SetCommTimeouts(HANDLE(self.fd.as_raw_handle()), &timeouts)
.map_err(|_| IoError::last_os_error())?;
}
Ok(())
}
#[allow(
// Wrap is guaranteed to not happen in this error code.
clippy::cast_possible_wrap,
)]
pub fn read(&self, buff: &mut [u8]) -> IoResult<usize> {
let event = Event::create(false, false)?;
let mut read_bytes = 0_u32;
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
overlapped.hEvent = event.handle;
match unsafe {
ReadFile(
HANDLE(self.fd.as_raw_handle()),
Some(buff),
Some(&mut read_bytes),
Some(&mut overlapped),
)
}
.map_err(|_| IoError::last_os_error())
{
Ok(()) => {
if read_bytes == 0 {
Err(IoErrorKind::TimedOut.into())
} else {
Ok(read_bytes as usize)
}
}
Err(cause) => {
if cause.kind() == IoErrorKind::BrokenPipe {
Ok(0)
} else if cause.raw_os_error() == Some(ERROR_IO_PENDING.0 as i32) {
Self::wait_async_transfer(&self.fd, &mut overlapped).or_else(|error| {
if error.kind() == IoErrorKind::BrokenPipe {
Ok(0)
} else {
Err(error)
}
})
} else {
Err(cause)
}
}
}
}
#[must_use]
pub const fn can_read_vectored() -> bool {
false
}
#[allow(
// Need to match API with linux which does use self.
clippy::unused_self,
)]
pub fn read_vectored(&self, _bufs: &mut [IoSliceMut<'_>]) -> IoResult<usize> {
Err(IoError::from_raw_os_error(
windows::Win32::Networking::WinSock::WSAENOTSOCK.0,
))
}
#[allow(
// Wrap is guaranteed to not happen for an error code in this case.
clippy::cast_possible_wrap,
)]
pub fn write(&self, buff: &[u8]) -> IoResult<usize> {
let event = Event::create(false, false)?;
let mut written = 0;
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
overlapped.hEvent = event.handle;
match unsafe {
WriteFile(
HANDLE(self.fd.as_raw_handle()),
Some(buff),
Some(&mut written),
Some(&mut overlapped),
)
}
.map_err(|_| IoError::last_os_error())
{
Ok(()) => {
if written == 0 {
Err(IoErrorKind::TimedOut.into())
} else {
Ok(written as usize)
}
}
Err(cause) => {
if cause.raw_os_error() == Some(ERROR_IO_PENDING.0 as i32) {
Self::wait_async_transfer(&self.fd, &mut overlapped)
} else {
Err(cause)
}
}
}
}
#[must_use]
pub const fn can_write_vectored() -> bool {
false
}
#[allow(
// Need to match API with linux which does use self.
clippy::unused_self,
)]
pub fn write_vectored(&self, _bufs: &[IoSlice<'_>]) -> IoResult<usize> {
Err(IoError::from_raw_os_error(
windows::Win32::Networking::WinSock::WSAENOTSOCK.0,
))
}
pub fn flush_output(&self) -> IoResult<()> {
unsafe {
FlushFileBuffers(HANDLE(self.fd.as_raw_handle())).map_err(|_| IoError::last_os_error())
}
}
pub fn discard_buffers(&self, discard_input: bool, discard_output: bool) -> IoResult<()> {
let mut flags = 0;
if discard_input {
flags |= PURGE_RXCLEAR.0;
}
if discard_output {
flags |= PURGE_TXCLEAR.0;
}
unsafe {
PurgeComm(HANDLE(self.fd.as_raw_handle()), PURGE_COMM_FLAGS(flags))
.map_err(|_| IoError::last_os_error())
}
}
pub fn set_rts(&self, state: bool) -> IoResult<()> {
unsafe {
EscapeCommFunction(
HANDLE(self.fd.as_raw_handle()),
if state { SETRTS } else { CLRRTS },
)
.map_err(|_| IoError::last_os_error())
}
}
pub fn read_cts(&self) -> IoResult<bool> {
Self::read_pin(&self.fd, MS_CTS_ON.0)
}
pub fn set_dtr(&self, state: bool) -> IoResult<()> {
unsafe {
EscapeCommFunction(
HANDLE(self.fd.as_raw_handle()),
if state { SETDTR } else { CLRDTR },
)
.map_err(|_| IoError::last_os_error())
}
}
pub fn read_dsr(&self) -> IoResult<bool> {
Self::read_pin(&self.fd, MS_DSR_ON.0)
}
pub fn read_ri(&self) -> IoResult<bool> {
Self::read_pin(&self.fd, MS_RING_ON.0)
}
pub fn read_cd(&self) -> IoResult<bool> {
Self::read_pin(&self.fd, MS_RLSD_ON.0)
}
pub fn enumerate() -> IoResult<Vec<PathBuf>> {
let subkey =
unsafe { CStr::from_bytes_with_nul_unchecked(b"Hardware\\DEVICEMAP\\SERIALCOMM\x00") };
let device_map = match RegKey::open(HKEY_LOCAL_MACHINE, subkey, KEY_READ) {
Ok(map) => map,
Err(cause) => {
if cause.kind() == IoErrorKind::NotFound {
return Ok(Vec::with_capacity(0));
}
return Err(cause);
}
};
let (value_count, max_value_name_len, max_value_data_len) = device_map.get_value_info()?;
let mut entries = Vec::with_capacity(16);
for idx in 0..value_count {
let Ok(Some((_, mut name))) =
device_map.get_string_value(idx, max_value_name_len, max_value_data_len)
else {
continue;
};
if let Some(nul_byte_idx) = name.iter().rposition(|&b| b != 0) {
name.truncate(nul_byte_idx + 1);
if let Ok(name) = String::from_utf8(name.to_vec()) {
entries.push(name.into());
}
}
}
Ok(entries)
}
fn read_pin(fd: &File, pin: u32) -> IoResult<bool> {
let mut bits: MODEM_STATUS_FLAGS = MODEM_STATUS_FLAGS(0);
unsafe {
GetCommModemStatus(HANDLE(fd.as_raw_handle()), &mut bits)
.map_err(|_| IoError::last_os_error())?;
}
Ok(bits.0 & pin != 0)
}
fn wait_async_transfer(file: &File, overlapped: &mut OVERLAPPED) -> IoResult<usize> {
unsafe {
let mut transferred = 0;
match GetOverlappedResult(
HANDLE(file.as_raw_handle()),
overlapped,
&mut transferred,
true,
)
.map_err(|_| IoError::last_os_error())
{
Ok(()) if transferred == 0 => Err(IoErrorKind::TimedOut.into()),
Ok(()) => Ok(transferred as usize),
Err(e) => Err(e),
}
}
}
}
const RAW_SYNC_SERIAL_PORT_FIELDS: &[NamedField<'static>] = &[NamedField::new("fd")];
impl Structable for RawSyncSerialPort {
fn definition(&self) -> StructDef<'_> {
StructDef::new_static(
"RawSyncSerialPortFields",
Fields::Named(RAW_SYNC_SERIAL_PORT_FIELDS),
)
}
}
impl Valuable for RawSyncSerialPort {
fn as_value(&self) -> Value<'_> {
Value::Structable(self)
}
fn visit(&self, visitor: &mut dyn Visit) {
visitor.visit_named_fields(&NamedValues::new(
RAW_SYNC_SERIAL_PORT_FIELDS,
&[Valuable::as_value(&format!("{:?}", self.fd))],
));
}
}
struct Event {
handle: HANDLE,
}
impl Event {
fn create(manual_reset: bool, initially_signalled: bool) -> IoResult<Self> {
let handle = unsafe {
CreateEventA(
None, manual_reset,
initially_signalled,
PCSTR(std::ptr::null()), )
.map_err(|_| IoError::last_os_error())?
};
Ok(Self { handle })
}
}
impl Drop for Event {
fn drop(&mut self) {
unsafe {
std::mem::drop(CloseHandle(self.handle));
}
}
}
#[derive(Debug)]
struct RegKey {
key: HKEY,
}
impl RegKey {
fn open(parent: HKEY, subpath: &CStr, rights: REG_SAM_FLAGS) -> IoResult<Self> {
let mut key: HKEY = HKEY(std::ptr::null_mut::<std::ffi::c_void>());
unsafe {
RegOpenKeyExA(
parent,
PCSTR(subpath.as_ptr().cast()),
None,
rights,
&mut key,
)
.ok()
.map_err(|_| IoError::last_os_error())?;
}
Ok(Self { key })
}
fn get_value_info(&self) -> IoResult<(u32, u32, u32)> {
let mut value_count = 0_u32;
let mut max_value_name_len = 0_u32;
let mut max_value_data_len = 0_u32;
unsafe {
RegQueryInfoKeyA(
self.key,
None,
None,
None,
None,
None,
None,
Some(&mut value_count),
Some(&mut max_value_name_len),
Some(&mut max_value_data_len),
None,
None,
)
.ok()
}
.map_err(|_| IoError::last_os_error())?;
Ok((value_count, max_value_name_len, max_value_data_len))
}
#[allow(
// Truncation is guaranteed to not happen in this context.
clippy::cast_possible_truncation,
)]
fn get_string_value(
&self,
index: u32,
max_name_len: u32,
max_data_len: u32,
) -> IoResult<Option<(Bytes, Bytes)>> {
let mut name = BytesMut::zeroed(max_name_len as usize + 1);
let mut data = BytesMut::zeroed(max_data_len as usize);
let mut name_len = name.len() as u32;
let mut data_len = data.len() as u32;
let mut kind = 0;
let result = unsafe {
RegEnumValueA(
self.key,
index,
Some(PSTR::from_raw(name.as_mut_ptr().cast())),
&mut name_len,
None,
Some(&mut kind),
Some(data.as_mut_ptr().cast()),
Some(&mut data_len),
)
};
if let Err(cause) = result.ok() {
if cause.code() == ERROR_NO_MORE_ITEMS.into() {
Ok(None)
} else {
Err(IoError::from_raw_os_error(cause.code().0))
}
} else if kind != REG_SZ.0 {
Ok(None)
} else {
name.truncate(name_len as usize + 1);
data.truncate(data_len as usize);
Ok(Some((name.freeze(), data.freeze())))
}
}
}
impl Drop for RegKey {
fn drop(&mut self) {
unsafe {
_ = RegCloseKey(self.key);
}
}
}