Add python async wrapper, move hci non-wrapper to internal, add hci::internal tests

This commit is contained in:
Gabriel White-Vega
2023-09-28 15:55:23 -04:00
parent 7e331c2944
commit 511ab4b630
11 changed files with 380 additions and 214 deletions

View File

@@ -17,7 +17,7 @@ use crate::wrapper::{
common::{TransportSink, TransportSource},
hci::Address,
link::Link,
PyDictExt,
wrap_python_async, PyDictExt,
};
use pyo3::{
intern,
@@ -44,6 +44,9 @@ impl Controller {
public_address: Option<Address>,
) -> PyResult<Self> {
Python::with_gil(|py| {
let controller_ctr = PyModule::import(py, intern!(py, "bumble.controller"))?
.getattr(intern!(py, "Controller"))?;
let kwargs = PyDict::new(py);
kwargs.set_item("name", name)?;
kwargs.set_opt_item("host_source", host_source)?;
@@ -51,9 +54,10 @@ impl Controller {
kwargs.set_opt_item("link", link)?;
kwargs.set_opt_item("public_address", public_address)?;
PyModule::import(py, intern!(py, "bumble.controller"))?
.getattr(intern!(py, "Controller"))?
.call_method("create", (), Some(kwargs))
// Controller constructor (`__init__`) is not (and can't be) marked async, but calls
// `get_running_loop`, and thus needs wrapped in an async function.
wrap_python_async(py, controller_ctr)?
.call((), Some(kwargs))
.and_then(into_future)
})?
.await

View File

@@ -14,6 +14,7 @@
//! Devices and connections to them
use crate::internal::hci::WithPacketType;
use crate::{
adv::AdvertisementDataBuilder,
wrapper::{
@@ -21,7 +22,7 @@ use crate::{
gatt_client::{ProfileServiceProxy, ServiceProxy},
hci::{
packets::{Command, ErrorCode, Event},
Address, HciCommandWrapper, WithPacketType,
Address, HciCommandWrapper,
},
host::Host,
l2cap::LeConnectionOrientedChannel,
@@ -317,7 +318,7 @@ impl Connection {
kwargs.set_opt_item("mps", mps)?;
self.0
.call_method(py, intern!(py, "open_l2cap_channel"), (), Some(kwargs))
.and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
.and_then(|coroutine| into_future(coroutine.as_ref(py)))
})?
.await
.map(LeConnectionOrientedChannel::from)
@@ -331,7 +332,7 @@ impl Connection {
kwargs.set_opt_item("reason", reason)?;
self.0
.call_method(py, intern!(py, "disconnect"), (), Some(kwargs))
.and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
.and_then(|coroutine| into_future(coroutine.as_ref(py)))
})?
.await
.map(|_| ())

View File

@@ -16,8 +16,9 @@
pub use crate::internal::hci::packets;
use crate::wrapper::hci::packets::{
Acl, AddressType, Command, Error, ErrorCode, Event, Packet, Sco,
use crate::{
internal::hci::WithPacketType,
wrapper::hci::packets::{AddressType, Command, ErrorCode},
};
use itertools::Itertools as _;
use pyo3::{
@@ -26,7 +27,6 @@ use pyo3::{
types::{PyBytes, PyModule},
FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
};
use std::fmt::{Display, Formatter};
/// Provides helpers for interacting with HCI
pub struct HciConstant;
@@ -137,182 +137,6 @@ impl HciCommandWrapper {
}
}
/// 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)

View File

@@ -14,8 +14,12 @@
//! Host-side types
use crate::wrapper::transport::{Sink, Source};
use crate::wrapper::{
transport::{Sink, Source},
wrap_python_async,
};
use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python, ToPyObject};
use pyo3_asyncio::tokio::into_future;
/// Host HCI commands
pub struct Host {
@@ -29,13 +33,23 @@ impl Host {
}
/// Create a new Host
pub fn new(source: Source, sink: Sink) -> PyResult<Self> {
pub async fn new(source: Source, sink: Sink) -> PyResult<Self> {
Python::with_gil(|py| {
PyModule::import(py, intern!(py, "bumble.host"))?
.getattr(intern!(py, "Host"))?
.call((source.0, sink.0), None)
.map(|any| Self { obj: any.into() })
})
let host_ctr =
PyModule::import(py, intern!(py, "bumble.host"))?.getattr(intern!(py, "Host"))?;
let kwargs = PyDict::new(py);
kwargs.set_item("controller_source", source.0)?;
kwargs.set_item("controller_sink", sink.0)?;
// Needed for Python 3.8-3.9, in which the Semaphore object, when constructed, calls
// `get_event_loop`.
wrap_python_async(py, host_ctr)?
.call((), Some(kwargs))
.and_then(into_future)
})?
.await
.map(|any| Self { obj: any })
}
/// Send a reset command and perform other reset tasks.

View File

@@ -22,11 +22,13 @@
// Re-exported to make it easy for users to depend on the same `PyObject`, etc
pub use pyo3;
pub use pyo3_asyncio;
use pyo3::{
intern,
prelude::*,
types::{PyDict, PyTuple},
};
pub use pyo3_asyncio;
pub mod assigned_numbers;
pub mod common;
@@ -122,3 +124,11 @@ impl ClosureCallback {
(self.callback)(py, args, kwargs).map(|_| py.None())
}
}
/// Wraps the Python function in a Python async function. `pyo3_asyncio` needs functions to be
/// marked async to properly inject a running loop.
pub(crate) fn wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny> {
PyModule::import(py, intern!(py, "bumble.utils"))?
.getattr(intern!(py, "wrap_async"))?
.call1((function,))
}