blob: 1bf368f621f2e5bed1414a366a020922ef8629e7 [file] [log] [blame]
use std::mem::MaybeUninit;
use std::os::windows::prelude::*;
use std::time::Duration;
use std::{io, ptr};
use winapi::shared::minwindef::*;
use winapi::um::commapi::*;
use winapi::um::fileapi::*;
use winapi::um::handleapi::*;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winbase::*;
use winapi::um::winnt::{
DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, HANDLE, MAXDWORD,
};
use crate::windows::dcb;
use crate::{
ClearBuffer, DataBits, Error, ErrorKind, FlowControl, Parity, Result, SerialPort,
SerialPortBuilder, StopBits,
};
/// A serial port implementation for Windows COM ports
///
/// The port will be closed when the value is dropped. However, this struct
/// should not be instantiated directly by using `COMPort::open()`, instead use
/// the cross-platform `serialport::open()` or
/// `serialport::open_with_settings()`.
#[derive(Debug)]
pub struct COMPort {
handle: HANDLE,
timeout: Duration,
port_name: Option<String>,
}
unsafe impl Send for COMPort {}
impl COMPort {
/// Opens a COM port as a serial device.
///
/// `port` should be the name of a COM port, e.g., `COM1`.
///
/// If the COM port handle needs to be opened with special flags, use
/// `from_raw_handle` method to create the `COMPort`. Note that you should
/// set the different settings before using the serial port using `set_all`.
///
/// ## Errors
///
/// * `NoDevice` if the device could not be opened. This could indicate that
/// the device is already in use.
/// * `InvalidInput` if `port` is not a valid device name.
/// * `Io` for any other I/O error while opening or initializing the device.
pub fn open(builder: &SerialPortBuilder) -> Result<COMPort> {
let mut name = Vec::<u16>::with_capacity(4 + builder.path.len() + 1);
name.extend(r"\\.\".encode_utf16());
name.extend(builder.path.encode_utf16());
name.push(0);
let handle = unsafe {
CreateFileW(
name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
0,
ptr::null_mut(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0 as HANDLE,
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(super::error::last_os_error());
}
// create the COMPort here so the handle is getting closed
// if one of the calls to `get_dcb()` or `set_dcb()` fails
let mut com = COMPort::open_from_raw_handle(handle as RawHandle);
let mut dcb = dcb::get_dcb(handle)?;
dcb::init(&mut dcb);
dcb::set_baud_rate(&mut dcb, builder.baud_rate);
dcb::set_data_bits(&mut dcb, builder.data_bits);
dcb::set_parity(&mut dcb, builder.parity);
dcb::set_stop_bits(&mut dcb, builder.stop_bits);
dcb::set_flow_control(&mut dcb, builder.flow_control);
dcb::set_dcb(handle, dcb)?;
if let Some(dtr) = builder.dtr_on_open {
com.write_data_terminal_ready(dtr)?;
}
com.set_timeout(builder.timeout)?;
com.port_name = Some(builder.path.clone());
Ok(com)
}
/// Attempts to clone the `SerialPort`. This allow you to write and read simultaneously from the
/// same serial connection. Please note that if you want a real asynchronous serial port you
/// should look at [mio-serial](https://crates.io/crates/mio-serial) or
/// [tokio-serial](https://crates.io/crates/tokio-serial).
///
/// Also, you must be very careful when changing the settings of a cloned `SerialPort` : since
/// the settings are cached on a per object basis, trying to modify them from two different
/// objects can cause some nasty behavior.
///
/// This is the same as `SerialPort::try_clone()` but returns the concrete type instead.
///
/// # Errors
///
/// This function returns an error if the serial port couldn't be cloned.
pub fn try_clone_native(&self) -> Result<COMPort> {
let process_handle: HANDLE = unsafe { GetCurrentProcess() };
let mut cloned_handle: HANDLE = INVALID_HANDLE_VALUE;
unsafe {
DuplicateHandle(
process_handle,
self.handle,
process_handle,
&mut cloned_handle,
0,
TRUE,
DUPLICATE_SAME_ACCESS,
);
if cloned_handle != INVALID_HANDLE_VALUE {
Ok(COMPort {
handle: cloned_handle,
port_name: self.port_name.clone(),
timeout: self.timeout,
})
} else {
Err(super::error::last_os_error())
}
}
}
fn escape_comm_function(&mut self, function: DWORD) -> Result<()> {
match unsafe { EscapeCommFunction(self.handle, function) } {
0 => Err(super::error::last_os_error()),
_ => Ok(()),
}
}
fn read_pin(&mut self, pin: DWORD) -> Result<bool> {
let mut status: DWORD = 0;
match unsafe { GetCommModemStatus(self.handle, &mut status) } {
0 => Err(super::error::last_os_error()),
_ => Ok(status & pin != 0),
}
}
fn open_from_raw_handle(handle: RawHandle) -> Self {
// It is not trivial to get the file path corresponding to a handle.
// We'll punt and set it `None` here.
COMPort {
handle: handle as HANDLE,
timeout: Duration::from_millis(100),
port_name: None,
}
}
fn timeout_constant(duration: Duration) -> DWORD {
let milliseconds = duration.as_millis();
// In the way we are setting up COMMTIMEOUTS, a timeout_constant of MAXDWORD gets rejected.
// Let's clamp the timeout constant for values of MAXDWORD and above. See remarks at
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts.
//
// This effectively throws away accuracy for really long timeouts but at least preserves a
// long-ish timeout. But just casting to DWORD would result in presumably unexpected short
// and non-monotonic timeouts from cutting off the higher bits.
u128::min(milliseconds, MAXDWORD as u128 - 1) as DWORD
}
}
impl Drop for COMPort {
fn drop(&mut self) {
unsafe {
CloseHandle(self.handle);
}
}
}
impl AsRawHandle for COMPort {
fn as_raw_handle(&self) -> RawHandle {
self.handle as RawHandle
}
}
impl FromRawHandle for COMPort {
unsafe fn from_raw_handle(handle: RawHandle) -> Self {
COMPort::open_from_raw_handle(handle)
}
}
impl IntoRawHandle for COMPort {
fn into_raw_handle(self) -> RawHandle {
let Self { handle, .. } = self;
handle as RawHandle
}
}
impl io::Read for COMPort {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut len: DWORD = 0;
match unsafe {
ReadFile(
self.handle,
buf.as_mut_ptr() as LPVOID,
buf.len() as DWORD,
&mut len,
ptr::null_mut(),
)
} {
0 => Err(io::Error::last_os_error()),
_ => {
if len != 0 {
Ok(len as usize)
} else {
Err(io::Error::new(
io::ErrorKind::TimedOut,
"Operation timed out",
))
}
}
}
}
}
impl io::Write for COMPort {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut len: DWORD = 0;
match unsafe {
WriteFile(
self.handle,
buf.as_ptr() as LPVOID,
buf.len() as DWORD,
&mut len,
ptr::null_mut(),
)
} {
0 => Err(io::Error::last_os_error()),
_ => Ok(len as usize),
}
}
fn flush(&mut self) -> io::Result<()> {
match unsafe { FlushFileBuffers(self.handle) } {
0 => Err(io::Error::last_os_error()),
_ => Ok(()),
}
}
}
impl SerialPort for COMPort {
fn name(&self) -> Option<String> {
self.port_name.clone()
}
fn timeout(&self) -> Duration {
self.timeout
}
fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
let timeout_constant = Self::timeout_constant(timeout);
let mut timeouts = COMMTIMEOUTS {
ReadIntervalTimeout: MAXDWORD,
ReadTotalTimeoutMultiplier: MAXDWORD,
ReadTotalTimeoutConstant: timeout_constant,
WriteTotalTimeoutMultiplier: 0,
WriteTotalTimeoutConstant: timeout_constant,
};
if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 {
return Err(super::error::last_os_error());
}
self.timeout = timeout;
Ok(())
}
fn write_request_to_send(&mut self, level: bool) -> Result<()> {
if level {
self.escape_comm_function(SETRTS)
} else {
self.escape_comm_function(CLRRTS)
}
}
fn write_data_terminal_ready(&mut self, level: bool) -> Result<()> {
if level {
self.escape_comm_function(SETDTR)
} else {
self.escape_comm_function(CLRDTR)
}
}
fn read_clear_to_send(&mut self) -> Result<bool> {
self.read_pin(MS_CTS_ON)
}
fn read_data_set_ready(&mut self) -> Result<bool> {
self.read_pin(MS_DSR_ON)
}
fn read_ring_indicator(&mut self) -> Result<bool> {
self.read_pin(MS_RING_ON)
}
fn read_carrier_detect(&mut self) -> Result<bool> {
self.read_pin(MS_RLSD_ON)
}
fn baud_rate(&self) -> Result<u32> {
let dcb = dcb::get_dcb(self.handle)?;
Ok(dcb.BaudRate as u32)
}
fn data_bits(&self) -> Result<DataBits> {
let dcb = dcb::get_dcb(self.handle)?;
match dcb.ByteSize {
5 => Ok(DataBits::Five),
6 => Ok(DataBits::Six),
7 => Ok(DataBits::Seven),
8 => Ok(DataBits::Eight),
_ => Err(Error::new(
ErrorKind::Unknown,
"Invalid data bits setting encountered",
)),
}
}
fn parity(&self) -> Result<Parity> {
let dcb = dcb::get_dcb(self.handle)?;
match dcb.Parity {
ODDPARITY => Ok(Parity::Odd),
EVENPARITY => Ok(Parity::Even),
NOPARITY => Ok(Parity::None),
_ => Err(Error::new(
ErrorKind::Unknown,
"Invalid parity bits setting encountered",
)),
}
}
fn stop_bits(&self) -> Result<StopBits> {
let dcb = dcb::get_dcb(self.handle)?;
match dcb.StopBits {
TWOSTOPBITS => Ok(StopBits::Two),
ONESTOPBIT => Ok(StopBits::One),
_ => Err(Error::new(
ErrorKind::Unknown,
"Invalid stop bits setting encountered",
)),
}
}
fn flow_control(&self) -> Result<FlowControl> {
let dcb = dcb::get_dcb(self.handle)?;
if dcb.fOutxCtsFlow() != 0 || dcb.fRtsControl() != 0 {
Ok(FlowControl::Hardware)
} else if dcb.fOutX() != 0 || dcb.fInX() != 0 {
Ok(FlowControl::Software)
} else {
Ok(FlowControl::None)
}
}
fn set_baud_rate(&mut self, baud_rate: u32) -> Result<()> {
let mut dcb = dcb::get_dcb(self.handle)?;
dcb::set_baud_rate(&mut dcb, baud_rate);
dcb::set_dcb(self.handle, dcb)
}
fn set_data_bits(&mut self, data_bits: DataBits) -> Result<()> {
let mut dcb = dcb::get_dcb(self.handle)?;
dcb::set_data_bits(&mut dcb, data_bits);
dcb::set_dcb(self.handle, dcb)
}
fn set_parity(&mut self, parity: Parity) -> Result<()> {
let mut dcb = dcb::get_dcb(self.handle)?;
dcb::set_parity(&mut dcb, parity);
dcb::set_dcb(self.handle, dcb)
}
fn set_stop_bits(&mut self, stop_bits: StopBits) -> Result<()> {
let mut dcb = dcb::get_dcb(self.handle)?;
dcb::set_stop_bits(&mut dcb, stop_bits);
dcb::set_dcb(self.handle, dcb)
}
fn set_flow_control(&mut self, flow_control: FlowControl) -> Result<()> {
let mut dcb = dcb::get_dcb(self.handle)?;
dcb::set_flow_control(&mut dcb, flow_control);
dcb::set_dcb(self.handle, dcb)
}
fn bytes_to_read(&self) -> Result<u32> {
let mut errors: DWORD = 0;
let mut comstat = MaybeUninit::uninit();
if unsafe { ClearCommError(self.handle, &mut errors, comstat.as_mut_ptr()) != 0 } {
unsafe { Ok(comstat.assume_init().cbInQue) }
} else {
Err(super::error::last_os_error())
}
}
fn bytes_to_write(&self) -> Result<u32> {
let mut errors: DWORD = 0;
let mut comstat = MaybeUninit::uninit();
if unsafe { ClearCommError(self.handle, &mut errors, comstat.as_mut_ptr()) != 0 } {
unsafe { Ok(comstat.assume_init().cbOutQue) }
} else {
Err(super::error::last_os_error())
}
}
fn clear(&self, buffer_to_clear: ClearBuffer) -> Result<()> {
let buffer_flags = match buffer_to_clear {
ClearBuffer::Input => PURGE_RXABORT | PURGE_RXCLEAR,
ClearBuffer::Output => PURGE_TXABORT | PURGE_TXCLEAR,
ClearBuffer::All => PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR,
};
if unsafe { PurgeComm(self.handle, buffer_flags) != 0 } {
Ok(())
} else {
Err(super::error::last_os_error())
}
}
fn try_clone(&self) -> Result<Box<dyn SerialPort>> {
match self.try_clone_native() {
Ok(p) => Ok(Box::new(p)),
Err(e) => Err(e),
}
}
fn set_break(&self) -> Result<()> {
if unsafe { SetCommBreak(self.handle) != 0 } {
Ok(())
} else {
Err(super::error::last_os_error())
}
}
fn clear_break(&self) -> Result<()> {
if unsafe { ClearCommBreak(self.handle) != 0 } {
Ok(())
} else {
Err(super::error::last_os_error())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::timeout::MONOTONIC_DURATIONS;
#[test]
fn timeout_constant_is_monotonic() {
let mut last = COMPort::timeout_constant(Duration::ZERO);
for (i, d) in MONOTONIC_DURATIONS.iter().enumerate() {
let next = COMPort::timeout_constant(*d);
assert!(
next >= last,
"{next} >= {last} failed for {d:?} at index {i}"
);
last = next;
}
}
#[test]
fn timeout_constant_zero_is_zero() {
assert_eq!(0, COMPort::timeout_constant(Duration::ZERO));
}
}