use std::{collections::VecDeque, fs::File, io::{self, Error, Read, Write}, os::fd::{AsRawFd, BorrowedFd, FromRawFd}, path::Path, time::Instant};
use nix::{errno::Errno, poll::{PollFd, PollFlags, PollTimeout}};
use termios::Termios;
pub fn port_open(path: impl AsRef<Path>) -> io::Result<File> {
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
let oflag =
OFlag::O_RDWR |
OFlag::O_APPEND |
OFlag::O_DSYNC |
OFlag::O_RSYNC |
OFlag::O_SYNC |
OFlag::O_NOCTTY |
OFlag::O_NONBLOCK |
OFlag::O_NDELAY;
let fd = nix::fcntl::open(path.as_ref(), oflag, Mode::empty())?;
let mut termios = Termios::from_fd(fd)?;
termios::tcgetattr(fd, &mut termios)?;
termios::cfmakeraw(&mut termios);
termios::tcsetattr(fd, termios::TCSANOW, &termios)?;
let file = unsafe {
File::from_raw_fd(fd)
};
Ok(file)
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum PollKind {
ForRead,
ForWrite,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum PollResult {
TimedOut,
ReadReady,
WriteReady,
Undocumented,
}
pub fn port_poll(port: &File, poll: PollKind, deadline: Option<Instant>) -> io::Result<PollResult> {
let fd = unsafe {
BorrowedFd::borrow_raw(port.as_raw_fd())
};
let timeout = match deadline {
Some(deadline) => {
let now = Instant::now();
let time_left = deadline.saturating_duration_since(now);
PollTimeout::try_from(time_left).unwrap_or(PollTimeout::ZERO)
},
None => PollTimeout::ZERO,
};
let input_flags = match poll {
PollKind::ForRead => {
PollFlags::POLLIN |
PollFlags::POLLPRI |
PollFlags::POLLRDNORM |
PollFlags::POLLRDBAND
},
PollKind::ForWrite => {
PollFlags::POLLPRI |
PollFlags::POLLOUT |
PollFlags::POLLWRNORM |
PollFlags::POLLWRBAND
},
};
let mut pollfd = [PollFd::new(fd, input_flags)];
let poll_result = nix::poll::poll(&mut pollfd, timeout);
match poll_result {
Err(errno) => {
Err(Error::from(errno))
},
Ok(rc) if rc < 0 => {
Err(Error::from(Errno::last()))
},
Ok(0) => {
Ok(PollResult::TimedOut)
},
Ok(_) => {
let revents = match pollfd[0].revents() {
Some(flags) => flags,
None => {
return Ok(PollResult::TimedOut)
},
};
if revents.intersects(PollFlags::POLLHUP) {
return Err(Error::other("POLLHUP: Device has been disconnected"));
}
if revents.intersects(PollFlags::POLLNVAL) {
return Err(Error::other("POLLNVAL: Invalid fd member"));
}
if revents.intersects(PollFlags::POLLERR) {
return Err(Error::other("POLLERR: An error has occurred"));
}
let pf_write_ready =
PollFlags::POLLOUT | PollFlags::POLLWRNORM | PollFlags::POLLWRBAND; if revents.intersects(pf_write_ready) {
return Ok(PollResult::WriteReady);
}
let pf_read_ready =
PollFlags::POLLIN | PollFlags::POLLRDNORM | PollFlags::POLLRDBAND | PollFlags::POLLPRI; if revents.intersects(pf_read_ready) {
return Ok(PollResult::ReadReady);
}
Ok(PollResult::Undocumented)
}
}
}
pub fn port_read(port: &mut File, data: &mut VecDeque<u8>) -> io::Result<()> {
let mut buf = [0; 1024 * 1024];
loop {
match port.read(&mut buf) {
Ok(0) => {
return Ok(())
}
Ok(n) => {
data.extend(&buf[0..n]);
}
Err(err) => match err.kind() {
io::ErrorKind::Interrupted => {
return Ok(())
},
io::ErrorKind::TimedOut => {
return Ok(())
},
io::ErrorKind::WouldBlock => {
return Ok(())
}
_ => {
return Err(err)
},
},
}
}
}
pub fn port_write(port: &mut File, data: &mut VecDeque<u8>) -> io::Result<()> {
let buf = data.make_contiguous();
match port.write(buf) {
Ok(0) => {
Ok(())
},
Ok(n) => {
let _ = data.drain(0..n);
Ok(())
}
Err(err) => match err.kind() {
io::ErrorKind::Interrupted => {
Ok(())
},
io::ErrorKind::TimedOut => {
Ok(())
},
io::ErrorKind::WouldBlock => {
Ok(())
}
_ => {
Err(err)
},
},
}
}
pub fn port_send(port: &mut File, send: &[u8], recv: &mut VecDeque<u8>, deadline: Instant) -> io::Result<()> {
let mut send = VecDeque::from(send.to_vec());
loop {
match port_poll(port, PollKind::ForWrite, Some(deadline))? {
PollResult::TimedOut => {
},
PollResult::ReadReady => {
port_read(port, recv)?;
},
PollResult::WriteReady => {
port_write(port, &mut send)?;
},
PollResult::Undocumented => {
}
}
if send.is_empty() {
return Ok(());
}
if deadline <= Instant::now() {
return Err(io::ErrorKind::TimedOut.into());
}
}
}
pub fn port_recv(port: &mut File, buff: &mut VecDeque<u8>, until: Option<u8>, deadline: Option<Instant>) -> io::Result<()> {
loop {
match port_poll(port, PollKind::ForRead, deadline)? {
PollResult::TimedOut => {
return Ok(());
},
PollResult::ReadReady => {
port_read(port, buff)?;
},
PollResult::WriteReady => {
}
PollResult::Undocumented => {
}
}
if let Some(delimiter) = until {
if buff.make_contiguous().contains(&delimiter) {
return Ok(());
}
}
}
}