forked from auracaster/bumble_mirror
Ability to send HCI commands from Rust
* Autogenerate packet code in Rust from PDL (packet file copied from rootcanal) * Implement parsing of packets that have a type header * Expose Python APIs for sending HCI commands * Expose Python APIs for instantiating a local controller
This commit is contained in:
@@ -14,84 +14,62 @@
|
||||
|
||||
//! HCI
|
||||
|
||||
pub use crate::internal::hci::packets;
|
||||
|
||||
use crate::wrapper::hci::packets::{
|
||||
Acl, AddressType, Command, Error, ErrorCode, Event, Packet, Sco,
|
||||
};
|
||||
use itertools::Itertools as _;
|
||||
use pyo3::{
|
||||
exceptions::PyException, intern, types::PyModule, FromPyObject, PyAny, PyErr, PyObject,
|
||||
PyResult, Python, ToPyObject,
|
||||
exceptions::PyException,
|
||||
intern, pyclass, pymethods,
|
||||
types::{PyBytes, PyModule},
|
||||
FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
|
||||
};
|
||||
|
||||
/// HCI error code.
|
||||
pub struct HciErrorCode(u8);
|
||||
|
||||
impl<'source> FromPyObject<'source> for HciErrorCode {
|
||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||
Ok(HciErrorCode(ob.extract()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for HciErrorCode {
|
||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||
self.0.to_object(py)
|
||||
}
|
||||
}
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Provides helpers for interacting with HCI
|
||||
pub struct HciConstant;
|
||||
|
||||
impl HciConstant {
|
||||
/// Human-readable error name
|
||||
pub fn error_name(status: HciErrorCode) -> PyResult<String> {
|
||||
pub fn error_name(status: ErrorCode) -> PyResult<String> {
|
||||
Python::with_gil(|py| {
|
||||
PyModule::import(py, intern!(py, "bumble.hci"))?
|
||||
.getattr(intern!(py, "HCI_Constant"))?
|
||||
.call_method1(intern!(py, "error_name"), (status.0,))?
|
||||
.call_method1(intern!(py, "error_name"), (status.to_object(py),))?
|
||||
.extract()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bluetooth address
|
||||
#[derive(Clone)]
|
||||
pub struct Address(pub(crate) PyObject);
|
||||
|
||||
impl Address {
|
||||
/// Creates a new [Address] object
|
||||
pub fn new(address: &str, address_type: &AddressType) -> PyResult<Self> {
|
||||
Python::with_gil(|py| {
|
||||
PyModule::import(py, intern!(py, "bumble.device"))?
|
||||
.getattr(intern!(py, "Address"))?
|
||||
.call1((address, address_type.to_object(py)))
|
||||
.map(|any| Self(any.into()))
|
||||
})
|
||||
}
|
||||
|
||||
/// The type of address
|
||||
pub fn address_type(&self) -> PyResult<AddressType> {
|
||||
Python::with_gil(|py| {
|
||||
let addr_type = self
|
||||
.0
|
||||
self.0
|
||||
.getattr(py, intern!(py, "address_type"))?
|
||||
.extract::<u32>(py)?;
|
||||
|
||||
let module = PyModule::import(py, intern!(py, "bumble.hci"))?;
|
||||
let klass = module.getattr(intern!(py, "Address"))?;
|
||||
|
||||
if addr_type
|
||||
== klass
|
||||
.getattr(intern!(py, "PUBLIC_DEVICE_ADDRESS"))?
|
||||
.extract::<u32>()?
|
||||
{
|
||||
Ok(AddressType::PublicDevice)
|
||||
} else if addr_type
|
||||
== klass
|
||||
.getattr(intern!(py, "RANDOM_DEVICE_ADDRESS"))?
|
||||
.extract::<u32>()?
|
||||
{
|
||||
Ok(AddressType::RandomDevice)
|
||||
} else if addr_type
|
||||
== klass
|
||||
.getattr(intern!(py, "PUBLIC_IDENTITY_ADDRESS"))?
|
||||
.extract::<u32>()?
|
||||
{
|
||||
Ok(AddressType::PublicIdentity)
|
||||
} else if addr_type
|
||||
== klass
|
||||
.getattr(intern!(py, "RANDOM_IDENTITY_ADDRESS"))?
|
||||
.extract::<u32>()?
|
||||
{
|
||||
Ok(AddressType::RandomIdentity)
|
||||
} else {
|
||||
Err(PyErr::new::<PyException, _>("Invalid address type"))
|
||||
}
|
||||
.extract::<u8>(py)?
|
||||
.try_into()
|
||||
.map_err(|addr_type| {
|
||||
PyErr::new::<PyException, _>(format!(
|
||||
"Failed to convert {addr_type} to AddressType"
|
||||
))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -134,12 +112,221 @@ impl Address {
|
||||
}
|
||||
}
|
||||
|
||||
/// BT address types
|
||||
#[allow(missing_docs)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum AddressType {
|
||||
PublicDevice,
|
||||
RandomDevice,
|
||||
PublicIdentity,
|
||||
RandomIdentity,
|
||||
impl ToPyObject for Address {
|
||||
fn to_object(&self, _py: Python<'_>) -> PyObject {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements minimum necessary interface to be treated as bumble's [HCI_Command].
|
||||
/// While pyo3's macros do not support generics, this could probably be refactored to allow multiple
|
||||
/// implementations of the HCI_Command methods in the future, if needed.
|
||||
#[pyclass]
|
||||
pub(crate) struct HciCommandWrapper(pub(crate) Command);
|
||||
|
||||
#[pymethods]
|
||||
impl HciCommandWrapper {
|
||||
fn __bytes__(&self, py: Python) -> PyResult<PyObject> {
|
||||
let bytes = PyBytes::new(py, &self.0.clone().to_vec_with_packet_type());
|
||||
Ok(bytes.into_py(py))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn op_code(&self) -> u16 {
|
||||
self.0.get_op_code().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// HCI Packet type, prepended to the packet.
|
||||
/// Rootcanal's PDL declaration excludes this from ser/deser and instead is implemented in code.
|
||||
/// To maintain the ability to easily use future versions of their packet PDL, packet type is
|
||||
/// implemented here.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PacketType {
|
||||
Command = 0x01,
|
||||
Acl = 0x02,
|
||||
Sco = 0x03,
|
||||
Event = 0x04,
|
||||
}
|
||||
|
||||
impl From<PacketType> for u8 {
|
||||
fn from(packet_type: PacketType) -> Self {
|
||||
match packet_type {
|
||||
PacketType::Command => 0x01,
|
||||
PacketType::Acl => 0x02,
|
||||
PacketType::Sco => 0x03,
|
||||
PacketType::Event => 0x04,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PacketType {
|
||||
type Error = PacketTypeParseError;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0x01 => Ok(PacketType::Command),
|
||||
0x02 => Ok(PacketType::Acl),
|
||||
0x03 => Ok(PacketType::Sco),
|
||||
0x04 => Ok(PacketType::Event),
|
||||
_ => Err(PacketTypeParseError::NonexistentPacketType(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows for smoother interoperability between a [Packet] and a bytes representation of it that
|
||||
/// includes its type as a header
|
||||
pub(crate) trait WithPacketType<T: Packet> {
|
||||
/// Converts the [Packet] into bytes, prefixed with its type
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8>;
|
||||
|
||||
/// Parses a [Packet] out of bytes that are prefixed with the packet's type
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<T, PacketTypeParseError>;
|
||||
}
|
||||
|
||||
/// Errors that may arise when parsing a packet that is prefixed with its type
|
||||
pub(crate) enum PacketTypeParseError {
|
||||
EmptySlice,
|
||||
NoPacketBytes,
|
||||
PacketTypeMismatch {
|
||||
expected: PacketType,
|
||||
actual: PacketType,
|
||||
},
|
||||
NonexistentPacketType(u8),
|
||||
PacketParse(packets::Error),
|
||||
}
|
||||
|
||||
impl Display for PacketTypeParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PacketTypeParseError::EmptySlice => write!(f, "The slice being parsed was empty"),
|
||||
PacketTypeParseError::NoPacketBytes => write!(
|
||||
f,
|
||||
"There were no bytes left after parsing the packet type header"
|
||||
),
|
||||
PacketTypeParseError::PacketTypeMismatch { expected, actual } => {
|
||||
write!(f, "Expected type: {expected:?}, but got: {actual:?}")
|
||||
}
|
||||
PacketTypeParseError::NonexistentPacketType(packet_byte) => {
|
||||
write!(f, "Packet type ({packet_byte:X}) does not exist")
|
||||
}
|
||||
PacketTypeParseError::PacketParse(e) => f.write_str(&e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<packets::Error> for PacketTypeParseError {
|
||||
fn from(value: Error) -> Self {
|
||||
Self::PacketParse(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Command {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.push(PacketType::Command.into());
|
||||
bytes.append(&mut self.to_vec());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
let first_byte = bytes.first().ok_or(PacketTypeParseError::EmptySlice)?;
|
||||
match PacketType::try_from(*first_byte)? {
|
||||
PacketType::Command => {
|
||||
let packet_bytes = bytes.get(1..).ok_or(PacketTypeParseError::NoPacketBytes)?;
|
||||
Ok(Command::parse(packet_bytes)?)
|
||||
}
|
||||
packet_type => Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: PacketType::Command,
|
||||
actual: packet_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Acl {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.push(PacketType::Acl.into());
|
||||
bytes.append(&mut self.to_vec());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
let first_byte = bytes.first().ok_or(PacketTypeParseError::EmptySlice)?;
|
||||
match PacketType::try_from(*first_byte)? {
|
||||
PacketType::Acl => {
|
||||
let packet_bytes = bytes.get(1..).ok_or(PacketTypeParseError::NoPacketBytes)?;
|
||||
Ok(Acl::parse(packet_bytes)?)
|
||||
}
|
||||
packet_type => Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: PacketType::Acl,
|
||||
actual: packet_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Sco {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.push(PacketType::Sco.into());
|
||||
bytes.append(&mut self.to_vec());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
let first_byte = bytes.first().ok_or(PacketTypeParseError::EmptySlice)?;
|
||||
match PacketType::try_from(*first_byte)? {
|
||||
PacketType::Sco => {
|
||||
let packet_bytes = bytes.get(1..).ok_or(PacketTypeParseError::NoPacketBytes)?;
|
||||
Ok(Sco::parse(packet_bytes)?)
|
||||
}
|
||||
packet_type => Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: PacketType::Sco,
|
||||
actual: packet_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Event {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.push(PacketType::Event.into());
|
||||
bytes.append(&mut self.to_vec());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
let first_byte = bytes.first().ok_or(PacketTypeParseError::EmptySlice)?;
|
||||
match PacketType::try_from(*first_byte)? {
|
||||
PacketType::Event => {
|
||||
let packet_bytes = bytes.get(1..).ok_or(PacketTypeParseError::NoPacketBytes)?;
|
||||
Ok(Event::parse(packet_bytes)?)
|
||||
}
|
||||
packet_type => Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: PacketType::Event,
|
||||
actual: packet_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for AddressType {
|
||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||
u8::from(self).to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'source> FromPyObject<'source> for ErrorCode {
|
||||
fn extract(ob: &'source PyAny) -> PyResult<Self> {
|
||||
ob.extract()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPyObject for ErrorCode {
|
||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||
u8::from(self).to_object(py)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user