Krolyxon
Commit e479dcc

Branches

Refs →

budctlcratesbudctl-coresrctransportsocket.rs

197 lines · 6.5 KB · Rust

1//! Raw Linux Bluetooth sockets through libc. This is the only module in budctl allowed to use
2//! `unsafe`; everything above it gets safe functions that return `io::Result`.
3//! Constants and struct layouts: docs/protocol/transport.md#sockets.
4#![allow(unsafe_code)]
5
6use std::io;
7use std::mem::size_of;
8use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
9use std::time::{Duration, Instant};
10
11use crate::bt::BdAddr;
12
13/// Spec: transport.md#sockets
14const BTPROTO_L2CAP: libc::c_int = 0;
15/// Spec: transport.md#sockets
16const BTPROTO_RFCOMM: libc::c_int = 3;
17
18/// `struct sockaddr_rc`. Spec: transport.md#sockets
19#[repr(C)]
20struct SockaddrRc {
21 family: libc::sa_family_t,
22 bdaddr: [u8; 6],
23 channel: u8,
24}
25
26/// `struct sockaddr_l2`. Spec: transport.md#sockets
27#[repr(C)]
28struct SockaddrL2 {
29 family: libc::sa_family_t,
30 psm: u16,
31 bdaddr: [u8; 6],
32 cid: u16,
33 bdaddr_type: u8,
34}
35
36pub(crate) enum Target {
37 /// A stream socket to an RFCOMM channel.
38 Rfcomm { channel: u8 },
39 /// A packet socket to an L2CAP PSM (SDP is PSM 1).
40 L2cap { psm: u16 },
41}
42
43/// Open a Bluetooth socket and connect it. `timeout` bounds the connect and every later send.
44pub(crate) fn connect(address: BdAddr, target: Target, timeout: Duration) -> io::Result<OwnedFd> {
45 let (kind, protocol) = match target {
46 Target::Rfcomm { .. } => (libc::SOCK_STREAM, BTPROTO_RFCOMM),
47 Target::L2cap { .. } => (libc::SOCK_SEQPACKET, BTPROTO_L2CAP),
48 };
49 // SAFETY: a plain syscall with constant arguments; the result is checked below.
50 let raw = unsafe { libc::socket(libc::AF_BLUETOOTH, kind | libc::SOCK_CLOEXEC, protocol) };
51 if raw < 0 {
52 return Err(io::Error::last_os_error());
53 }
54 // SAFETY: `raw` is a fresh, valid descriptor that nothing else owns.
55 let fd = unsafe { OwnedFd::from_raw_fd(raw) };
56
57 // A blocking connect waits at most SO_SNDTIMEO, then fails with EINPROGRESS.
58 // Spec: transport.md#connect-timeout
59 set_timeout(&fd, libc::SO_SNDTIMEO, timeout)?;
60
61 let family = libc::sa_family_t::try_from(libc::AF_BLUETOOTH).map_err(io::Error::other)?;
62 let bdaddr = address.kernel_order();
63 match target {
64 Target::Rfcomm { channel } => connect_to(
65 &fd,
66 &SockaddrRc {
67 family,
68 bdaddr,
69 channel,
70 },
71 )?,
72 Target::L2cap { psm } => connect_to(
73 &fd,
74 &SockaddrL2 {
75 family,
76 psm: psm.to_le(),
77 bdaddr,
78 cid: 0,
79 bdaddr_type: 0,
80 },
81 )?,
82 }
83 Ok(fd)
84}
85
86fn connect_to<T>(fd: &OwnedFd, address: &T) -> io::Result<()> {
87 let len = libc::socklen_t::try_from(size_of::<T>()).map_err(io::Error::other)?;
88 // SAFETY: `address` points to a live, initialised #[repr(C)] sockaddr of exactly `len` bytes.
89 let rc = unsafe {
90 libc::connect(
91 fd.as_raw_fd(),
92 std::ptr::from_ref(address).cast::<libc::sockaddr>(),
93 len,
94 )
95 };
96 if rc < 0 {
97 Err(io::Error::last_os_error())
98 } else {
99 Ok(())
100 }
101}
102
103fn set_timeout(fd: &OwnedFd, option: libc::c_int, timeout: Duration) -> io::Result<()> {
104 let tv = libc::timeval {
105 tv_sec: libc::time_t::try_from(timeout.as_secs()).map_err(io::Error::other)?,
106 tv_usec: libc::suseconds_t::from(timeout.subsec_micros()),
107 };
108 let len = libc::socklen_t::try_from(size_of::<libc::timeval>()).map_err(io::Error::other)?;
109 // SAFETY: `tv` lives for the duration of the call and `len` is its size.
110 let rc = unsafe {
111 libc::setsockopt(
112 fd.as_raw_fd(),
113 libc::SOL_SOCKET,
114 option,
115 std::ptr::from_ref(&tv).cast::<libc::c_void>(),
116 len,
117 )
118 };
119 if rc < 0 {
120 Err(io::Error::last_os_error())
121 } else {
122 Ok(())
123 }
124}
125
126/// Wait until the socket has something to read (data, end of stream or an error).
127/// `Ok(false)` means `deadline` passed first.
128pub(crate) fn wait_readable(fd: &OwnedFd, deadline: Instant) -> io::Result<bool> {
129 loop {
130 let left = deadline.saturating_duration_since(Instant::now());
131 let ms = libc::c_int::try_from(left.as_millis()).unwrap_or(libc::c_int::MAX);
132 let mut poll = libc::pollfd {
133 fd: fd.as_raw_fd(),
134 events: libc::POLLIN,
135 revents: 0,
136 };
137 // SAFETY: `poll` is one valid pollfd that lives for the duration of the call.
138 let rc = unsafe { libc::poll(&mut poll, 1, ms) };
139 if rc >= 0 {
140 return Ok(rc > 0);
141 }
142 let error = io::Error::last_os_error();
143 if error.kind() != io::ErrorKind::Interrupted {
144 return Err(error);
145 }
146 }
147}
148
149/// One `recv()`. Returns 0 when the other side closed the connection.
150pub(crate) fn read(fd: &OwnedFd, buffer: &mut [u8]) -> io::Result<usize> {
151 loop {
152 // SAFETY: `buffer` is valid for writes of `buffer.len()` bytes.
153 let n = unsafe { libc::recv(fd.as_raw_fd(), buffer.as_mut_ptr().cast(), buffer.len(), 0) };
154 if let Ok(n) = usize::try_from(n) {
155 return Ok(n);
156 }
157 let error = io::Error::last_os_error();
158 if error.kind() != io::ErrorKind::Interrupted {
159 return Err(error);
160 }
161 }
162}
163
164/// Send every byte. `MSG_NOSIGNAL` turns a dead link into `EPIPE` instead of `SIGPIPE`.
165pub(crate) fn write_all(fd: &OwnedFd, bytes: &[u8]) -> io::Result<()> {
166 let mut rest = bytes;
167 while !rest.is_empty() {
168 // SAFETY: `rest` is valid for reads of `rest.len()` bytes.
169 let n = unsafe { libc::send(fd.as_raw_fd(), rest.as_ptr().cast(), rest.len(), libc::MSG_NOSIGNAL) };
170 match usize::try_from(n) {
171 Ok(0) => return Err(io::ErrorKind::WriteZero.into()),
172 Ok(sent) => rest = rest.get(sent..).unwrap_or_default(),
173 Err(_) => {
174 let error = io::Error::last_os_error();
175 if error.kind() != io::ErrorKind::Interrupted {
176 return Err(error);
177 }
178 }
179 }
180 }
181 Ok(())
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn sockaddr_layouts_match_the_kernel() {
190 // Spec: transport.md#sockets
191 assert_eq!(size_of::<SockaddrRc>(), 10);
192 assert_eq!(size_of::<SockaddrL2>(), 14);
193 assert_eq!(std::mem::offset_of!(SockaddrRc, channel), 8);
194 assert_eq!(std::mem::offset_of!(SockaddrL2, cid), 10);
195 assert_eq!(std::mem::offset_of!(SockaddrL2, bdaddr_type), 12);
196 }
197}