1use std::collections::VecDeque;
47use std::fmt;
48use std::path::{Path, PathBuf};
49use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
50use std::time::Duration;
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub enum Parity {
55 None,
57 Even,
59 Odd,
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
65pub enum StopBits {
66 One,
68 Two,
70}
71
72#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
87pub struct Settings {
88 pub baud: u32,
90 pub parity: Parity,
92 pub stop_bits: StopBits,
94}
95
96impl Settings {
97 pub const fn new(baud: u32) -> Settings {
107 Settings {
108 baud,
109 parity: Parity::None,
110 stop_bits: StopBits::One,
111 }
112 }
113
114 pub const fn with_parity(mut self, parity: Parity) -> Settings {
124 self.parity = parity;
125 self
126 }
127
128 pub const fn with_stop_bits(mut self, stop_bits: StopBits) -> Settings {
138 self.stop_bits = stop_bits;
139 self
140 }
141
142 pub const fn bits_per_character(&self) -> u32 {
145 let parity = match self.parity {
146 Parity::None => 0,
147 Parity::Even | Parity::Odd => 1,
148 };
149 let stop = match self.stop_bits {
150 StopBits::One => 1,
151 StopBits::Two => 2,
152 };
153 1 + 8 + parity + stop
154 }
155
156 pub const fn character_nanos(&self) -> u64 {
158 let baud = if self.baud == 0 { 1 } else { self.baud as u64 };
159 (self.bits_per_character() as u64 * 1_000_000_000).div_ceil(baud)
160 }
161
162 pub const fn transfer_micros(&self, bytes: usize) -> u64 {
173 (self.character_nanos() * bytes as u64).div_ceil(1_000)
174 }
175}
176
177impl fmt::Display for Settings {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 let parity = match self.parity {
180 Parity::None => 'N',
181 Parity::Even => 'E',
182 Parity::Odd => 'O',
183 };
184 let stop = match self.stop_bits {
185 StopBits::One => 1,
186 StopBits::Two => 2,
187 };
188 write!(f, "{} 8{parity}{stop}", self.baud)
189 }
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
194pub enum PortKind {
195 Device,
197 Looped,
199 Paired,
201 Simulated,
203 Scripted,
205}
206
207pub trait Peer: Send {
245 fn receive(&mut self, bytes: &[u8]) -> Vec<u8>;
255}
256
257impl<P: Peer> Peer for Arc<Mutex<P>> {
258 fn receive(&mut self, bytes: &[u8]) -> Vec<u8> {
259 self.lock()
260 .unwrap_or_else(PoisonError::into_inner)
261 .receive(bytes)
262 }
263}
264
265#[derive(Clone, Debug, PartialEq, Eq)]
267pub enum PortStep {
268 Write(Vec<u8>),
270 Read(Vec<u8>),
272}
273
274impl PortStep {
275 pub fn write(bytes: impl Into<Vec<u8>>) -> PortStep {
285 PortStep::Write(bytes.into())
286 }
287
288 pub fn read(bytes: impl Into<Vec<u8>>) -> PortStep {
298 PortStep::Read(bytes.into())
299 }
300}
301
302#[derive(Clone, Debug, PartialEq, Eq)]
304pub enum OpenError {
305 Unsupported,
308 Device {
310 path: PathBuf,
312 reason: String,
314 },
315}
316
317impl fmt::Display for OpenError {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match self {
320 OpenError::Unsupported => f.write_str(
321 "a serial device is opened through the kernel's terminal interface, which only Linux has here",
322 ),
323 OpenError::Device { path, reason } => write!(f, "{}: {reason}", path.display()),
324 }
325 }
326}
327
328impl std::error::Error for OpenError {}
329
330#[derive(Clone, Debug, PartialEq, Eq)]
332pub enum PortError {
333 Unexpected {
335 expected: Option<Vec<u8>>,
337 written: Vec<u8>,
339 },
340 Device {
342 reason: String,
344 },
345}
346
347impl fmt::Display for PortError {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 match self {
350 PortError::Unexpected {
351 expected: Some(expected),
352 written,
353 } => write!(
354 f,
355 "the script expected {} to be written, not {}",
356 hex(expected),
357 hex(written)
358 ),
359 PortError::Unexpected {
360 expected: None,
361 written,
362 } => write!(
363 f,
364 "the script expected no more writes, but {} was written",
365 hex(written)
366 ),
367 PortError::Device { reason } => f.write_str(reason),
368 }
369 }
370}
371
372impl std::error::Error for PortError {}
373
374fn hex(bytes: &[u8]) -> String {
375 let mut text = String::with_capacity(bytes.len() * 3);
376 for (index, byte) in bytes.iter().enumerate() {
377 if index > 0 {
378 text.push(' ');
379 }
380 text.push_str(&format!("{byte:02x}"));
381 }
382 text
383}
384
385#[derive(Clone)]
406pub struct SerialPort {
407 shared: Arc<Mutex<Shared>>,
408}
409
410struct Shared {
411 backend: Backend,
412 settings: Settings,
413 written: usize,
414 received: usize,
415 waited_ns: u64,
416}
417
418type Line = Arc<Mutex<VecDeque<u8>>>;
419
420enum Backend {
421 #[cfg(all(feature = "linux", target_os = "linux"))]
422 Device {
423 path: PathBuf,
424 file: Arc<std::fs::File>,
425 },
426 Looped(VecDeque<u8>),
427 Paired {
428 inbox: Line,
429 outbox: Line,
430 },
431 Simulated {
432 peer: Box<dyn Peer>,
433 inbox: VecDeque<u8>,
434 },
435 Scripted {
436 steps: VecDeque<PortStep>,
437 inbox: VecDeque<u8>,
438 },
439}
440
441impl Backend {
442 fn kind(&self) -> PortKind {
443 match self {
444 #[cfg(all(feature = "linux", target_os = "linux"))]
445 Backend::Device { .. } => PortKind::Device,
446 Backend::Looped(_) => PortKind::Looped,
447 Backend::Paired { .. } => PortKind::Paired,
448 Backend::Simulated { .. } => PortKind::Simulated,
449 Backend::Scripted { .. } => PortKind::Scripted,
450 }
451 }
452}
453
454fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
455 mutex.lock().unwrap_or_else(PoisonError::into_inner)
456}
457
458fn take(queue: &mut VecDeque<u8>, buffer: &mut [u8]) -> usize {
459 let count = buffer.len().min(queue.len());
460 for (slot, byte) in buffer.iter_mut().zip(queue.drain(..count)) {
461 *slot = byte;
462 }
463 count
464}
465
466fn nanos(duration: Duration) -> u64 {
467 u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
468}
469
470fn release(steps: &mut VecDeque<PortStep>, inbox: &mut VecDeque<u8>) {
472 while let Some(PortStep::Read(bytes)) = steps.front() {
473 inbox.extend(bytes);
474 steps.pop_front();
475 }
476}
477
478impl SerialPort {
479 fn with(backend: Backend, settings: Settings) -> SerialPort {
480 SerialPort {
481 shared: Arc::new(Mutex::new(Shared {
482 backend,
483 settings,
484 written: 0,
485 received: 0,
486 waited_ns: 0,
487 })),
488 }
489 }
490
491 pub fn open(path: impl AsRef<Path>, settings: Settings) -> Result<SerialPort, OpenError> {
522 #[cfg(all(feature = "linux", target_os = "linux"))]
523 {
524 let path = path.as_ref().to_path_buf();
525 let file = device::open(&path, settings).map_err(|error| OpenError::Device {
526 path: path.clone(),
527 reason: error.to_string(),
528 })?;
529 Ok(SerialPort::with(
530 Backend::Device {
531 path,
532 file: Arc::new(file),
533 },
534 settings,
535 ))
536 }
537 #[cfg(not(all(feature = "linux", target_os = "linux")))]
538 {
539 let _ = (path, settings);
540 Err(OpenError::Unsupported)
541 }
542 }
543
544 pub fn looped(settings: Settings) -> SerialPort {
555 SerialPort::with(Backend::Looped(VecDeque::new()), settings)
556 }
557
558 pub fn pair(settings: Settings) -> (SerialPort, SerialPort) {
568 let one: Line = Arc::default();
569 let other: Line = Arc::default();
570 (
571 SerialPort::with(
572 Backend::Paired {
573 inbox: Arc::clone(&one),
574 outbox: Arc::clone(&other),
575 },
576 settings,
577 ),
578 SerialPort::with(
579 Backend::Paired {
580 inbox: other,
581 outbox: one,
582 },
583 settings,
584 ),
585 )
586 }
587
588 pub fn simulated(settings: Settings, peer: impl Peer + 'static) -> SerialPort {
599 SerialPort::with(
600 Backend::Simulated {
601 peer: Box::new(peer),
602 inbox: VecDeque::new(),
603 },
604 settings,
605 )
606 }
607
608 pub fn scripted(settings: Settings, steps: impl IntoIterator<Item = PortStep>) -> SerialPort {
640 let mut steps: VecDeque<PortStep> = steps.into_iter().collect();
641 let mut inbox = VecDeque::new();
642 release(&mut steps, &mut inbox);
643 SerialPort::with(Backend::Scripted { steps, inbox }, settings)
644 }
645
646 pub fn settings(&self) -> Settings {
648 lock(&self.shared).settings
649 }
650
651 pub fn kind(&self) -> PortKind {
653 lock(&self.shared).backend.kind()
654 }
655
656 pub fn write(&self, bytes: &[u8]) -> Result<(), PortError> {
667 let mut shared = lock(&self.shared);
668 match &mut shared.backend {
669 #[cfg(all(feature = "linux", target_os = "linux"))]
670 Backend::Device { path, file } => {
671 let (path, file) = (path.clone(), Arc::clone(file));
672 drop(shared);
673 device::write(&file, bytes).map_err(|error| PortError::Device {
674 reason: format!("{}: {error}", path.display()),
675 })?;
676 shared = lock(&self.shared);
677 }
678 Backend::Looped(inbox) => inbox.extend(bytes),
679 Backend::Paired { outbox, .. } => lock(outbox).extend(bytes),
680 Backend::Simulated { peer, inbox } => inbox.extend(peer.receive(bytes)),
681 Backend::Scripted { steps, inbox } => match steps.front() {
682 Some(PortStep::Write(expected)) if expected == bytes => {
683 steps.pop_front();
684 release(steps, inbox);
685 }
686 next => {
687 let expected = match next {
688 Some(PortStep::Write(expected)) => Some(expected.clone()),
689 _ => None,
690 };
691 return Err(PortError::Unexpected {
692 expected,
693 written: bytes.to_vec(),
694 });
695 }
696 },
697 }
698 shared.written += bytes.len();
699 Ok(())
700 }
701
702 pub fn read(&self, buffer: &mut [u8], timeout: Duration) -> Result<usize, PortError> {
720 let mut shared = lock(&self.shared);
721 let count = match &mut shared.backend {
722 #[cfg(all(feature = "linux", target_os = "linux"))]
723 Backend::Device { path, file } => {
724 let (path, file) = (path.clone(), Arc::clone(file));
725 drop(shared);
726 let count =
727 device::read(&file, buffer, timeout).map_err(|error| PortError::Device {
728 reason: format!("{}: {error}", path.display()),
729 })?;
730 shared = lock(&self.shared);
731 count
732 }
733 Backend::Looped(inbox)
734 | Backend::Simulated { inbox, .. }
735 | Backend::Scripted { inbox, .. } => take(inbox, buffer),
736 Backend::Paired { inbox, .. } => take(&mut lock(inbox), buffer),
737 };
738 if count == 0 && !buffer.is_empty() {
739 shared.waited_ns = shared.waited_ns.saturating_add(nanos(timeout));
740 }
741 shared.received += count;
742 Ok(count)
743 }
744
745 pub fn discard_input(&self) -> Result<(), PortError> {
752 let mut shared = lock(&self.shared);
753 match &mut shared.backend {
754 #[cfg(all(feature = "linux", target_os = "linux"))]
755 Backend::Device { path, file } => {
756 device::discard(file).map_err(|error| PortError::Device {
757 reason: format!("{}: {error}", path.display()),
758 })?;
759 }
760 Backend::Looped(inbox)
761 | Backend::Simulated { inbox, .. }
762 | Backend::Scripted { inbox, .. } => inbox.clear(),
763 Backend::Paired { inbox, .. } => lock(inbox).clear(),
764 }
765 Ok(())
766 }
767
768 pub fn wait(&self, duration: Duration) {
775 let sleeps = {
776 let mut shared = lock(&self.shared);
777 shared.waited_ns = shared.waited_ns.saturating_add(nanos(duration));
778 shared.backend.kind() == PortKind::Device
779 };
780 if sleeps {
781 std::thread::sleep(duration);
782 }
783 }
784
785 pub fn written(&self) -> usize {
787 lock(&self.shared).written
788 }
789
790 pub fn received(&self) -> usize {
792 lock(&self.shared).received
793 }
794
795 pub fn waited_micros(&self) -> u64 {
798 lock(&self.shared).waited_ns / 1_000
799 }
800
801 pub fn remaining(&self) -> Option<usize> {
803 match &lock(&self.shared).backend {
804 Backend::Scripted { steps, .. } => Some(steps.len()),
805 _ => None,
806 }
807 }
808}
809
810impl fmt::Debug for SerialPort {
811 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
812 let shared = lock(&self.shared);
813 f.debug_struct("SerialPort")
814 .field("kind", &shared.backend.kind())
815 .field("settings", &shared.settings)
816 .field("written", &shared.written)
817 .field("received", &shared.received)
818 .finish_non_exhaustive()
819 }
820}
821
822#[cfg(all(feature = "linux", target_os = "linux"))]
824mod device {
825 use std::fs::{File, OpenOptions};
826 use std::io::{self, Read, Write};
827 use std::os::fd::AsFd;
828 use std::os::unix::fs::OpenOptionsExt;
829 use std::path::Path;
830 use std::time::{Duration, Instant};
831
832 use nix::errno::Errno;
833 use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
834 use nix::sys::termios::{
835 self, BaudRate, ControlFlags, FlushArg, InputFlags, SetArg, SpecialCharacterIndices,
836 };
837
838 use super::{Parity, Settings, StopBits};
839
840 pub(super) fn open(path: &Path, settings: Settings) -> io::Result<File> {
841 let file = OpenOptions::new()
842 .read(true)
843 .write(true)
844 .custom_flags(nix::libc::O_NOCTTY)
845 .open(path)?;
846 let speed = match settings.baud {
847 1_200 => BaudRate::B1200,
848 2_400 => BaudRate::B2400,
849 4_800 => BaudRate::B4800,
850 9_600 => BaudRate::B9600,
851 19_200 => BaudRate::B19200,
852 38_400 => BaudRate::B38400,
853 57_600 => BaudRate::B57600,
854 115_200 => BaudRate::B115200,
855 230_400 => BaudRate::B230400,
856 460_800 => BaudRate::B460800,
857 921_600 => BaudRate::B921600,
858 _ => {
859 return Err(io::Error::new(
860 io::ErrorKind::InvalidInput,
861 "not a standard serial rate",
862 ))
863 }
864 };
865 let mut tty = termios::tcgetattr(&file)?;
866 termios::cfmakeraw(&mut tty);
867 termios::cfsetispeed(&mut tty, speed)?;
868 termios::cfsetospeed(&mut tty, speed)?;
869 tty.control_flags |= ControlFlags::CLOCAL | ControlFlags::CREAD;
870 tty.control_flags &= !(ControlFlags::PARENB
871 | ControlFlags::PARODD
872 | ControlFlags::CSTOPB
873 | ControlFlags::CRTSCTS
874 | ControlFlags::CSIZE);
875 tty.control_flags |= ControlFlags::CS8;
876 match settings.parity {
877 Parity::None => {}
878 Parity::Even => tty.control_flags |= ControlFlags::PARENB,
879 Parity::Odd => tty.control_flags |= ControlFlags::PARENB | ControlFlags::PARODD,
880 }
881 if settings.parity != Parity::None {
882 tty.input_flags |= InputFlags::INPCK | InputFlags::IGNPAR;
883 }
884 if settings.stop_bits == StopBits::Two {
885 tty.control_flags |= ControlFlags::CSTOPB;
886 }
887 tty.control_chars[SpecialCharacterIndices::VMIN as usize] = 0;
888 tty.control_chars[SpecialCharacterIndices::VTIME as usize] = 0;
889 termios::tcsetattr(&file, SetArg::TCSANOW, &tty)?;
890 termios::tcflush(&file, FlushArg::TCIOFLUSH)?;
891 Ok(file)
892 }
893
894 pub(super) fn write(file: &File, bytes: &[u8]) -> io::Result<()> {
895 let mut writer = file;
896 writer.write_all(bytes)?;
897 termios::tcdrain(file)?;
898 Ok(())
899 }
900
901 pub(super) fn read(file: &File, buffer: &mut [u8], timeout: Duration) -> io::Result<usize> {
902 if buffer.is_empty() {
903 return Ok(0);
904 }
905 let deadline = Instant::now() + timeout;
906 loop {
907 let left = deadline.saturating_duration_since(Instant::now());
908 let millis = i32::try_from(left.as_micros().div_ceil(1_000)).unwrap_or(i32::MAX);
909 let wait = PollTimeout::try_from(millis).unwrap_or(PollTimeout::MAX);
910 let mut fds = [PollFd::new(file.as_fd(), PollFlags::POLLIN)];
911 match poll(&mut fds, wait) {
912 Ok(0) => return Ok(0),
913 Ok(_) => {
914 let mut reader = file;
915 return reader.read(buffer);
916 }
917 Err(Errno::EINTR) => continue,
918 Err(error) => return Err(error.into()),
919 }
920 }
921 }
922
923 pub(super) fn discard(file: &File) -> io::Result<()> {
924 termios::tcflush(file, FlushArg::TCIFLUSH)?;
925 Ok(())
926 }
927}
928
929#[cfg(test)]
930mod tests {
931 use super::*;
932
933 #[test]
934 fn a_character_is_its_start_data_parity_and_stop_bits() {
935 let eight_n_one = Settings::new(115_200);
936 assert_eq!(eight_n_one.bits_per_character(), 10);
937 assert_eq!(eight_n_one.character_nanos(), 86_806, "10 bits at 115200");
938 assert_eq!(eight_n_one.to_string(), "115200 8N1");
939
940 let modbus = Settings::new(9_600).with_parity(Parity::Even);
943 assert_eq!(modbus.bits_per_character(), 11);
944 let no_parity = Settings::new(9_600).with_stop_bits(StopBits::Two);
945 assert_eq!(no_parity.bits_per_character(), 11);
946 assert_eq!(no_parity.to_string(), "9600 8N2");
947 assert_eq!(
948 Settings::new(19_200).with_parity(Parity::Odd).to_string(),
949 "19200 8O1"
950 );
951
952 assert_eq!((modbus.character_nanos() * 7).div_ceil(2), 4_010_419);
954 assert_eq!(modbus.transfer_micros(8), 9_167, "an eight-byte request");
955 }
956
957 #[test]
958 fn a_looped_line_reads_back_what_it_wrote() {
959 let port = SerialPort::looped(Settings::new(9_600));
960 port.write(b"abc").unwrap();
961 let mut buffer = [0u8; 2];
962 assert_eq!(port.read(&mut buffer, Duration::from_millis(5)).unwrap(), 2);
963 assert_eq!(&buffer, b"ab");
964 assert_eq!(port.read(&mut buffer, Duration::from_millis(5)).unwrap(), 1);
965 assert_eq!(buffer[0], b'c');
966 assert_eq!(port.read(&mut buffer, Duration::from_millis(5)).unwrap(), 0);
967 assert_eq!(port.waited_micros(), 5_000, "only the empty read waited");
968 assert_eq!((port.written(), port.received()), (3, 3));
969 }
970
971 #[test]
972 fn each_end_of_a_pair_reads_what_the_other_wrote() {
973 let (left, right) = SerialPort::pair(Settings::new(115_200));
974 left.write(b"ping").unwrap();
975 right.write(b"pong").unwrap();
976 let mut buffer = [0u8; 8];
977 let got = right.read(&mut buffer, Duration::ZERO).unwrap();
978 assert_eq!(&buffer[..got], b"ping");
979 let got = left.read(&mut buffer, Duration::ZERO).unwrap();
980 assert_eq!(&buffer[..got], b"pong");
981 assert_eq!(left.kind(), PortKind::Paired);
982
983 let reader = right.clone();
984 left.write(b"!").unwrap();
985 assert_eq!(reader.read(&mut buffer, Duration::ZERO).unwrap(), 1);
986 assert_eq!(right.received(), 5, "a clone reaches the same end");
987 }
988
989 struct Counter(usize);
990
991 impl Peer for Counter {
992 fn receive(&mut self, bytes: &[u8]) -> Vec<u8> {
993 self.0 += 1;
994 vec![bytes.len() as u8, self.0 as u8]
995 }
996 }
997
998 #[test]
999 fn a_simulated_device_answers_each_write_and_stays_reachable() {
1000 let device = Arc::new(Mutex::new(Counter(0)));
1001 let port = SerialPort::simulated(Settings::new(9_600), Arc::clone(&device));
1002 port.write(b"one").unwrap();
1003 port.write(b"three").unwrap();
1004 let mut buffer = [0u8; 8];
1005 let got = port.read(&mut buffer, Duration::from_millis(1)).unwrap();
1006 assert_eq!(&buffer[..got], &[3, 1, 5, 2]);
1007 assert_eq!(device.lock().unwrap().0, 2);
1008 assert_eq!(port.kind(), PortKind::Simulated);
1009 }
1010
1011 #[test]
1012 fn stale_input_is_discarded() {
1013 let port = SerialPort::looped(Settings::new(9_600));
1014 port.write(b"stale").unwrap();
1015 port.discard_input().unwrap();
1016 let mut buffer = [0u8; 8];
1017 assert_eq!(port.read(&mut buffer, Duration::from_millis(2)).unwrap(), 0);
1018 assert_eq!(port.received(), 0);
1019 }
1020
1021 #[test]
1022 fn a_script_checks_each_write_and_releases_what_follows() {
1023 let port = SerialPort::scripted(
1024 Settings::new(9_600),
1025 [
1026 PortStep::read(*b"hello"),
1027 PortStep::write(*b"?"),
1028 PortStep::read(*b"4"),
1029 PortStep::read(*b"2"),
1030 PortStep::write(*b"bye"),
1031 ],
1032 );
1033 let mut buffer = [0u8; 8];
1034 let got = port.read(&mut buffer, Duration::ZERO).unwrap();
1035 assert_eq!(
1036 &buffer[..got],
1037 b"hello",
1038 "reads at the start are there at once"
1039 );
1040 assert_eq!(
1041 port.write(b"!"),
1042 Err(PortError::Unexpected {
1043 expected: Some(b"?".to_vec()),
1044 written: b"!".to_vec(),
1045 })
1046 );
1047 port.write(b"?").unwrap();
1048 let got = port.read(&mut buffer, Duration::ZERO).unwrap();
1049 assert_eq!(&buffer[..got], b"42");
1050 assert_eq!(port.remaining(), Some(1));
1051 port.write(b"bye").unwrap();
1052 let error = port.write(b"again").unwrap_err();
1053 assert_eq!(
1054 error.to_string(),
1055 "the script expected no more writes, but 61 67 61 69 6e was written"
1056 );
1057 assert_eq!(port.written(), 4, "a refused write is not counted");
1058 }
1059
1060 #[test]
1061 fn a_wait_is_counted_and_not_slept_off_a_real_device() {
1062 let port = SerialPort::looped(Settings::new(9_600));
1063 let started = std::time::Instant::now();
1064 port.wait(Duration::from_secs(3));
1065 assert!(started.elapsed() < Duration::from_secs(1));
1066 assert_eq!(port.waited_micros(), 3_000_000);
1067 }
1068
1069 #[cfg(all(feature = "linux", target_os = "linux"))]
1070 #[test]
1071 fn a_pseudo_terminal_opens_raw_and_carries_bytes_both_ways() {
1072 use nix::fcntl::OFlag;
1073 use nix::pty::{grantpt, posix_openpt, ptsname_r, unlockpt};
1074 use std::io::{Read, Write};
1075 use std::time::Instant;
1076
1077 let mut far_end = posix_openpt(OFlag::O_RDWR | OFlag::O_NOCTTY).unwrap();
1078 grantpt(&far_end).unwrap();
1079 unlockpt(&far_end).unwrap();
1080 let path = ptsname_r(&far_end).unwrap();
1081 let port = SerialPort::open(&path, Settings::new(9_600).with_parity(Parity::Even)).unwrap();
1082 assert_eq!(port.kind(), PortKind::Device);
1083
1084 let awkward = [0x00, 0x11, 0x13, 0x0D];
1087 far_end.write_all(&awkward).unwrap();
1088 let mut buffer = [0u8; 8];
1089 let mut got = 0;
1090 while got < awkward.len() {
1091 let count = port
1092 .read(&mut buffer[got..], Duration::from_millis(500))
1093 .unwrap();
1094 assert!(count > 0, "the pseudo-terminal went quiet");
1095 got += count;
1096 }
1097 assert_eq!(&buffer[..got], &awkward);
1098
1099 port.write(b"ok").unwrap();
1100 let mut back = [0u8; 2];
1101 far_end.read_exact(&mut back).unwrap();
1102 assert_eq!(&back, b"ok");
1103
1104 let started = Instant::now();
1105 assert_eq!(
1106 port.read(&mut buffer, Duration::from_millis(40)).unwrap(),
1107 0
1108 );
1109 assert!(
1110 started.elapsed() >= Duration::from_millis(40),
1111 "a real read waits"
1112 );
1113 assert_eq!(port.waited_micros(), 40_000);
1114
1115 far_end.write_all(b"stale").unwrap();
1116 std::thread::sleep(Duration::from_millis(20));
1117 port.discard_input().unwrap();
1118 assert_eq!(
1119 port.read(&mut buffer, Duration::from_millis(20)).unwrap(),
1120 0
1121 );
1122
1123 assert!(matches!(
1124 SerialPort::open(&path, Settings::new(12_345)),
1125 Err(OpenError::Device { .. })
1126 ));
1127 }
1128
1129 #[cfg(not(all(feature = "linux", target_os = "linux")))]
1130 #[test]
1131 fn only_linux_opens_a_device() {
1132 assert_eq!(
1133 SerialPort::open("/dev/serial0", Settings::new(115_200)).unwrap_err(),
1134 OpenError::Unsupported
1135 );
1136 }
1137}