Support Linux Bluetooth socket addresses (#9243)

Assisted-by: Codex:GPT-6
This commit is contained in:
1ndahous3 authored and GitHub committed 2026-10-20 19:52:57 +09:00
1 parent 0c5e5f68ee
commit 0c5fa209a8
3 files changed
+317

No files matched your search

+3
View File
@@ -18,6 +18,9 @@ pub use ::dns_lookup as dns;
#[cfg(not(target_arch = "wasm32"))]
pub use ::socket2 as raw;
#[cfg(target_os = "linux")]
pub mod bluetooth;
#[cfg(not(any(
target_os = "ios",
target_os = "android",
+185
View File
@@ -0,0 +1,185 @@
//! Linux Bluetooth socket address layouts from the BlueZ headers.
use super::raw::{SockAddr, SockAddrStorage};
pub const L2CAP: i32 = 0;
pub const HCI: i32 = 1;
pub const SCO: i32 = 2;
pub const RFCOMM: i32 = 3;
#[repr(C)]
struct SockaddrL2 {
family: libc::sa_family_t,
psm: u16,
bdaddr: [u8; 6],
cid: u16,
addr_type: u8,
}
#[repr(C)]
struct SockaddrRc {
family: libc::sa_family_t,
bdaddr: [u8; 6],
channel: u8,
}
#[repr(C)]
struct SockaddrHci {
family: libc::sa_family_t,
dev: u16,
channel: u16,
}
#[repr(C)]
struct SockaddrSco {
family: libc::sa_family_t,
bdaddr: [u8; 6],
}
#[derive(Debug, PartialEq, Eq)]
pub enum Address {
L2cap {
bdaddr: [u8; 6],
psm: u16,
cid: u16,
addr_type: u8,
},
Rfcomm {
bdaddr: [u8; 6],
channel: u8,
},
Hci {
dev: u16,
channel: u16,
},
Sco {
bdaddr: [u8; 6],
},
}
impl Address {
pub fn to_sockaddr(&self) -> SockAddr {
let mut storage = SockAddrStorage::zeroed();
// SAFETY: these repr(C) layouts match the Linux sockaddr_* definitions.
// SockAddrStorage provides their size/alignment and is zero-initialized.
// Write fields in place so padding remains initialized.
let len = unsafe {
match *self {
Self::L2cap {
bdaddr,
psm,
cid,
addr_type,
} => {
let addr = storage.view_as::<SockaddrL2>();
addr.family = libc::AF_BLUETOOTH as _;
addr.psm = psm;
addr.bdaddr = bdaddr;
addr.cid = cid;
addr.addr_type = addr_type;
size_of::<SockaddrL2>()
}
Self::Rfcomm { bdaddr, channel } => {
let addr = storage.view_as::<SockaddrRc>();
addr.family = libc::AF_BLUETOOTH as _;
addr.bdaddr = bdaddr;
addr.channel = channel;
size_of::<SockaddrRc>()
}
Self::Hci { dev, channel } => {
let addr = storage.view_as::<SockaddrHci>();
addr.family = libc::AF_BLUETOOTH as _;
addr.dev = dev;
addr.channel = channel;
size_of::<SockaddrHci>()
}
Self::Sco { bdaddr } => {
let addr = storage.view_as::<SockaddrSco>();
addr.family = libc::AF_BLUETOOTH as _;
addr.bdaddr = bdaddr;
size_of::<SockaddrSco>()
}
}
};
// SAFETY: storage contains a complete address of the stated length.
unsafe { SockAddr::new(storage, len as _) }
}
pub fn from_sockaddr(addr: &SockAddr, protocol: i32) -> Option<Self> {
if addr.family() != libc::AF_BLUETOOTH as _ {
return None;
}
// SAFETY: SockAddr owns aligned storage; each cast checks the returned
// length. All bit patterns are valid for these integer-only layouts.
unsafe {
match protocol {
L2CAP if addr.len() as usize >= size_of::<SockaddrL2>() => {
let addr = &*addr.as_ptr().cast::<SockaddrL2>();
Some(Self::L2cap {
bdaddr: addr.bdaddr,
psm: addr.psm,
cid: addr.cid,
addr_type: addr.addr_type,
})
}
RFCOMM if addr.len() as usize >= size_of::<SockaddrRc>() => {
let addr = &*addr.as_ptr().cast::<SockaddrRc>();
Some(Self::Rfcomm {
bdaddr: addr.bdaddr,
channel: addr.channel,
})
}
HCI if addr.len() as usize >= size_of::<SockaddrHci>() => {
let addr = &*addr.as_ptr().cast::<SockaddrHci>();
Some(Self::Hci {
dev: addr.dev,
channel: addr.channel,
})
}
SCO if addr.len() as usize >= size_of::<SockaddrSco>() => {
let addr = &*addr.as_ptr().cast::<SockaddrSco>();
Some(Self::Sco {
bdaddr: addr.bdaddr,
})
}
_ => None,
}
}
}
}
pub fn parse_bdaddr(name: &[u8]) -> Option<[u8; 6]> {
let mut parts = name.split(|&b| b == b':');
let mut bdaddr = [0; 6];
for byte in bdaddr.iter_mut().rev() {
// CPython's %X parser permits leading whitespace, a sign and 0x.
let part = parts.next()?.trim_ascii_start();
let (part, negative) = match part.first() {
Some(b'+') => (&part[1..], false),
Some(b'-') => (&part[1..], true),
_ => (part, false),
};
if part.is_empty() {
return None;
}
let part = part
.strip_prefix(b"0x")
.or_else(|| part.strip_prefix(b"0X"))
.unwrap_or(part);
let mut value = 0u8;
for &digit in part {
let digit = (digit as char).to_digit(16)? as u8;
value = value.checked_mul(16)?.checked_add(digit)?;
}
if negative && value != 0 {
return None;
}
*byte = value;
}
parts.next().is_none().then_some(bdaddr)
}
pub fn format_bdaddr(bdaddr: [u8; 6]) -> String {
let [f, e, d, c, b, a] = bdaddr;
alloc::format!("{a:02X}:{b:02X}:{c:02X}:{d:02X}:{e:02X}:{f:02X}")
}
+129
View File
@@ -1606,6 +1606,14 @@ mod _socket {
})
}
#[cfg(target_os = "linux")]
c::AF_BLUETOOTH => Ok(extract_bluetooth_address(
addr,
self.proto.load(Ordering::Relaxed),
caller,
vm,
)?
.to_sockaddr()),
#[cfg(target_os = "linux")]
c::AF_CAN => {
let tuple: PyTupleRef = addr.downcast().map_err(|obj| {
vm.new_type_error(format!(
@@ -2925,6 +2933,96 @@ mod _socket {
}
}
#[cfg(target_os = "linux")]
fn extract_bluetooth_address(
addr: PyObjectRef,
protocol: i32,
caller: &str,
vm: &VirtualMachine,
) -> PyResult<host_socket::bluetooth::Address> {
use crate::vm::builtins::PyBytes;
use host_socket::bluetooth::{self, Address};
let wrong_format = || vm.new_os_error(format!("{caller}(): wrong format"));
let parse_name = |name: &[u8]| {
bluetooth::parse_bdaddr(name).ok_or_else(|| vm.new_os_error("bad bluetooth address"))
};
if protocol == bluetooth::SCO {
let (name, nul_error) = if let Some(bytes) = addr.downcast_ref::<PyBytes>() {
(bytes.as_bytes().to_vec(), "embedded null byte")
} else {
let name: PyStrRef = addr.downcast().map_err(|_| wrong_format())?;
(
name.try_into_utf8(vm)?.as_str().as_bytes().to_vec(),
"embedded null character",
)
};
if name.contains(&0) {
return Err(vm.new_value_error(nul_error));
}
return Ok(Address::Sco {
bdaddr: parse_name(&name)?,
});
}
if protocol == bluetooth::HCI
&& let Some(index) = addr.try_index_opt(vm)
{
return Ok(Address::Hci {
dev: index?.as_u32_mask() as u16,
channel: 0,
});
}
let (min, max) = match protocol {
bluetooth::L2CAP => (2, 4),
bluetooth::RFCOMM => (2, 2),
bluetooth::HCI => (1, 2),
_ => return Err(vm.new_os_error(format!("{caller}(): unknown Bluetooth protocol"))),
};
let tuple: PyTupleRef = addr.downcast().map_err(|_| wrong_format())?;
let fields = tuple.as_slice();
if !(min..=max).contains(&fields.len()) {
return Err(wrong_format());
}
// PyArg_ParseTuple's H/B formats mask unsigned values rather than
// rejecting overflow, including values returned by __index__.
let number = |i: usize| -> PyResult<u32> {
fields.get(i).map_or(Ok(0), |value| {
value
.try_index(vm)
.map(|n| n.as_u32_mask())
.map_err(|_| wrong_format())
})
};
if protocol == bluetooth::HCI {
return Ok(Address::Hci {
dev: number(0)? as u16,
channel: number(1)? as u16,
});
}
let name: PyStrRef = fields[0].clone().downcast().map_err(|_| wrong_format())?;
let name = name.try_into_utf8(vm).map_err(|_| wrong_format())?;
if name.as_str().contains('\0') {
return Err(wrong_format());
}
let port = number(1)?;
let cid = number(2)? as u16;
let addr_type = number(3)? as u8;
let bdaddr = parse_name(name.as_str().as_bytes())?;
Ok(if protocol == bluetooth::L2CAP {
Address::L2cap {
bdaddr,
psm: port as u16,
cid,
addr_type,
}
} else {
Address::Rfcomm {
bdaddr,
channel: port as u8,
}
})
}
fn get_addr_tuple(
addr: &host_socket::raw::SockAddr,
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] proto: i32,
@@ -2956,6 +3054,37 @@ mod _socket {
#[cfg(target_os = "linux")]
{
let family = addr.family();
if let Some(addr) = host_socket::bluetooth::Address::from_sockaddr(addr, proto) {
use host_socket::bluetooth::{Address, format_bdaddr};
return match addr {
Address::L2cap {
bdaddr,
psm,
cid,
addr_type,
} => {
let name = format_bdaddr(bdaddr);
if addr_type != 0 {
(name, psm, cid, addr_type).to_pyobject(vm)
} else if cid != 0 {
(name, psm, cid).to_pyobject(vm)
} else {
(name, psm).to_pyobject(vm)
}
}
Address::Rfcomm { bdaddr, channel } => {
(format_bdaddr(bdaddr), channel).to_pyobject(vm)
}
Address::Hci { dev, channel } => {
if channel == 0 {
dev.to_pyobject(vm)
} else {
(dev, channel).to_pyobject(vm)
}
}
Address::Sco { bdaddr } => format_bdaddr(bdaddr).to_pyobject(vm),
};
}
if family == c::AF_CAN as c::sa_family_t {
let can_addr = unsafe { &*(addr.as_ptr() as *const c::sockaddr_can) };
let ifindex = can_addr.can_ifindex;