Krolyxon
Commit a307193

Branches

Refs →

budctlcratesbudctl-coresrctransportmod.rs

103 lines · 3.2 KB · Rust

1//! Byte transports. Device protocols only see the [`Transport`] trait, so they can be tested
2//! against a scripted mock instead of real buds.
3
4use std::io;
5use std::time::Instant;
6
7use crate::debug;
8use crate::error::{Error, Result};
9
10pub mod rfcomm;
11pub mod sdp;
12mod socket;
13
14#[cfg(test)]
15pub(crate) mod mock;
16
17/// A bidirectional byte stream to the buds.
18pub trait Transport: Send {
19 /// Send all of `bytes`.
20 fn send(&mut self, bytes: &[u8]) -> Result<()>;
21
22 /// The next bytes from the buds, or `None` once `deadline` passes with nothing to read.
23 fn recv(&mut self, deadline: Instant) -> Result<Option<Vec<u8>>>;
24}
25
26/// Wraps a transport and prints every chunk sent and received (`--debug`).
27pub struct Hexdump {
28 inner: Box<dyn Transport>,
29 start: Instant,
30}
31
32impl Hexdump {
33 pub fn new(inner: Box<dyn Transport>) -> Self {
34 Self {
35 inner,
36 start: Instant::now(),
37 }
38 }
39
40 fn print(&self, direction: &str, bytes: &[u8]) {
41 let elapsed = self.start.elapsed().as_secs_f64();
42 eprintln!("{elapsed:>7.3} {direction} {}", debug::hex(bytes));
43 }
44}
45
46impl Transport for Hexdump {
47 fn send(&mut self, bytes: &[u8]) -> Result<()> {
48 self.print("TX", bytes);
49 self.inner.send(bytes)
50 }
51
52 fn recv(&mut self, deadline: Instant) -> Result<Option<Vec<u8>>> {
53 let received = self.inner.recv(deadline)?;
54 if let Some(bytes) = &received {
55 self.print("RX", bytes);
56 }
57 Ok(received)
58 }
59}
60
61/// What an OS error on a Bluetooth socket means for budctl.
62/// `may_be_busy` is set only for the RFCOMM connect, where a refusal means another client
63/// holds the channel. Spec: transport.md#errno
64pub(crate) fn classify(error: io::Error, context: &'static str, may_be_busy: bool) -> Error {
65 let detail = || format!("{context}: {error}");
66 match error.raw_os_error() {
67 Some(libc::EBUSY | libc::EADDRINUSE | libc::ECONNREFUSED) if may_be_busy => Error::ChannelBusy(detail()),
68 Some(
69 libc::EHOSTDOWN
70 | libc::EHOSTUNREACH
71 | libc::ENOTCONN
72 | libc::ECONNRESET
73 | libc::ECONNABORTED
74 | libc::EPIPE
75 | libc::ENODEV
76 | libc::ENETDOWN,
77 ) => Error::NotConnected(detail()),
78 Some(libc::ETIMEDOUT | libc::EINPROGRESS | libc::EAGAIN) => Error::Timeout(detail()),
79 _ => Error::Os { context, source: error },
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::error::Kind;
87
88 fn kind_of(errno: i32, may_be_busy: bool) -> Kind {
89 classify(io::Error::from_raw_os_error(errno), "test", may_be_busy).kind()
90 }
91
92 #[test]
93 fn errno_maps_to_the_documented_kinds() {
94 assert_eq!(kind_of(libc::ECONNREFUSED, true), Kind::Busy);
95 assert_eq!(kind_of(libc::EBUSY, true), Kind::Busy);
96 assert_eq!(kind_of(libc::ECONNREFUSED, false), Kind::NotFound);
97 assert_eq!(kind_of(libc::EHOSTDOWN, true), Kind::NotFound);
98 assert_eq!(kind_of(libc::ECONNRESET, false), Kind::NotFound);
99 assert_eq!(kind_of(libc::EINPROGRESS, true), Kind::Failed);
100 assert_eq!(kind_of(libc::ETIMEDOUT, false), Kind::Failed);
101 assert_eq!(kind_of(libc::EACCES, false), Kind::NotFound);
102 }
103}