mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Support Linux Bluetooth socket addresses (#9243)
Assisted-by: Codex:GPT-6
This commit is contained in:
1 parent
0c5e5f68ee
commit
0c5fa209a8
3 files changed
+317
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user