mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
Add python async wrapper, move hci non-wrapper to internal, add hci::internal tests
This commit is contained in:
@@ -194,21 +194,6 @@ class Controller:
|
||||
|
||||
self.terminated = asyncio.get_running_loop().create_future()
|
||||
|
||||
@classmethod
|
||||
async def create(
|
||||
cls,
|
||||
name,
|
||||
host_source=None,
|
||||
host_sink: Optional[TransportSink] = None,
|
||||
link=None,
|
||||
public_address: Optional[Union[bytes, str, Address]] = None,
|
||||
):
|
||||
'''
|
||||
Rust's pyo3_asyncio needs the constructor to be async in order to properly
|
||||
inject a running loop for creating the `terminated` future.
|
||||
'''
|
||||
return Controller(name, host_source, host_sink, link, public_address)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self.hci_sink
|
||||
|
||||
@@ -33,7 +33,7 @@ from typing import (
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from functools import wraps
|
||||
from functools import wraps, partial
|
||||
from pyee import EventEmitter
|
||||
|
||||
from .colors import color
|
||||
@@ -410,3 +410,20 @@ class FlowControlAsyncPipe:
|
||||
self.resume_source()
|
||||
|
||||
self.check_pump()
|
||||
|
||||
|
||||
async def async_call(function, *args, **kwargs):
|
||||
"""
|
||||
Immediately calls the function with provided args and kwargs, wrapping it in an async function.
|
||||
Rust's `pyo3_asyncio` library needs functions to be marked async to properly inject a running loop.
|
||||
|
||||
result = await async_call(some_function, ...)
|
||||
"""
|
||||
return function(*args, **kwargs)
|
||||
|
||||
|
||||
def wrap_async(function):
|
||||
"""
|
||||
Wraps the provided function in an async function.
|
||||
"""
|
||||
return partial(async_call, function)
|
||||
|
||||
@@ -56,7 +56,7 @@ async fn hci_command_wrapper_has_correct_methods() -> PyResult<()> {
|
||||
let address = Address::new("F0:F1:F2:F3:F4:F5", &AddressType::RandomDeviceAddress)?;
|
||||
let link = Link::new_local_link()?;
|
||||
let controller = Controller::new("C1", None, None, Some(link), Some(address.clone())).await?;
|
||||
let host = Host::new(controller.clone().into(), controller.into())?;
|
||||
let host = Host::new(controller.clone().into(), controller.into()).await?;
|
||||
let device = Device::new(None, Some(address), None, Some(host), None)?;
|
||||
|
||||
device.power_on().await?;
|
||||
|
||||
@@ -179,7 +179,7 @@ pub(crate) fn parse(firmware_path: &path::Path) -> PyResult<()> {
|
||||
pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
|
||||
let transport = Transport::open(transport).await?;
|
||||
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||
host.reset(DriverFactory::None).await?;
|
||||
|
||||
if !force && !Driver::check(&host).await? {
|
||||
@@ -203,7 +203,7 @@ pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
|
||||
pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
|
||||
let transport = Transport::open(transport).await?;
|
||||
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||
host.reset(DriverFactory::None).await?;
|
||||
|
||||
match Driver::for_host(&host, force).await? {
|
||||
@@ -219,7 +219,7 @@ pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
|
||||
pub(crate) async fn drop(transport: &str) -> PyResult<()> {
|
||||
let transport = Transport::open(transport).await?;
|
||||
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?)?;
|
||||
let mut host = Host::new(transport.source()?, transport.sink()?).await?;
|
||||
host.reset(DriverFactory::None).await?;
|
||||
|
||||
Driver::drop_firmware(&mut host).await?;
|
||||
|
||||
@@ -12,8 +12,225 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::internal::hci::packets::{Acl, Command, Error, Event, Packet, Sco};
|
||||
use pdl_derive::pdl;
|
||||
|
||||
#[allow(missing_docs, warnings, clippy::all)]
|
||||
#[pdl("src/internal/hci/packets.pdl")]
|
||||
pub mod packets {}
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// 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, PartialEq)]
|
||||
pub(crate) enum PacketType {
|
||||
Command = 0x01,
|
||||
Acl = 0x02,
|
||||
Sco = 0x03,
|
||||
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::InvalidPacketType { value }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub(crate) enum PacketTypeParseError {
|
||||
#[error("The slice being parsed was empty")]
|
||||
EmptySlice,
|
||||
#[error("Packet type ({value:#X}) is invalid")]
|
||||
InvalidPacketType { value: u8 },
|
||||
#[error("Expected packet type: {expected:?}, but got: {actual:?}")]
|
||||
PacketTypeMismatch {
|
||||
expected: PacketType,
|
||||
actual: PacketType,
|
||||
},
|
||||
#[error("Failed to parse packet after header: {error}")]
|
||||
PacketParse { error: packets::Error },
|
||||
}
|
||||
|
||||
// TODO: remove if/when PDL derives this for their Error enum (https://github.com/google/pdl/issues/71)
|
||||
impl PartialEq<Self> for packets::Error {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Error::InvalidPacketError, Error::InvalidPacketError) => true,
|
||||
(
|
||||
Error::ConstraintOutOfBounds {
|
||||
field: field1,
|
||||
value: value1,
|
||||
},
|
||||
Error::ConstraintOutOfBounds {
|
||||
field: field2,
|
||||
value: value2,
|
||||
},
|
||||
) => field1 == field2 && value1 == value2,
|
||||
(
|
||||
Error::InvalidFixedValue {
|
||||
expected: expected1,
|
||||
actual: actual1,
|
||||
},
|
||||
Error::InvalidFixedValue {
|
||||
expected: expected2,
|
||||
actual: actual2,
|
||||
},
|
||||
) => expected1 == expected2 && actual1 == actual2,
|
||||
(
|
||||
Error::InvalidLengthError {
|
||||
obj: obj1,
|
||||
wanted: wanted1,
|
||||
got: got1,
|
||||
},
|
||||
Error::InvalidLengthError {
|
||||
obj: obj2,
|
||||
wanted: wanted2,
|
||||
got: got2,
|
||||
},
|
||||
) => obj1 == obj2 && wanted1 == wanted2 && got1 == got2,
|
||||
(
|
||||
Error::InvalidArraySize {
|
||||
array: array1,
|
||||
element: element1,
|
||||
},
|
||||
Error::InvalidArraySize {
|
||||
array: array2,
|
||||
element: element2,
|
||||
},
|
||||
) => array1 == array2 && element1 == element2,
|
||||
(Error::ImpossibleStructError, Error::ImpossibleStructError) => true,
|
||||
(
|
||||
Error::InvalidEnumValueError {
|
||||
obj: obj1,
|
||||
field: field1,
|
||||
value: value1,
|
||||
type_: type_1,
|
||||
},
|
||||
Error::InvalidEnumValueError {
|
||||
obj: obj2,
|
||||
field: field2,
|
||||
value: value2,
|
||||
type_: type_2,
|
||||
},
|
||||
) => obj1 == obj2 && field1 == field2 && value1 == value2 && type_1 == type_2,
|
||||
(
|
||||
Error::InvalidChildError {
|
||||
expected: expected1,
|
||||
actual: actual1,
|
||||
},
|
||||
Error::InvalidChildError {
|
||||
expected: expected2,
|
||||
actual: actual2,
|
||||
},
|
||||
) => expected1 == expected2 && actual1 == actual2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<packets::Error> for PacketTypeParseError {
|
||||
fn from(error: Error) -> Self {
|
||||
Self::PacketParse { error }
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Command {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
prepend_packet_type(PacketType::Command, self.to_vec())
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
parse_with_expected_packet_type(Command::parse, PacketType::Command, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Acl {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
prepend_packet_type(PacketType::Acl, self.to_vec())
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
parse_with_expected_packet_type(Acl::parse, PacketType::Acl, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Sco {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
prepend_packet_type(PacketType::Sco, self.to_vec())
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
parse_with_expected_packet_type(Sco::parse, PacketType::Sco, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl WithPacketType<Self> for Event {
|
||||
fn to_vec_with_packet_type(self) -> Vec<u8> {
|
||||
prepend_packet_type(PacketType::Event, self.to_vec())
|
||||
}
|
||||
|
||||
fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
|
||||
parse_with_expected_packet_type(Event::parse, PacketType::Event, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn prepend_packet_type(packet_type: PacketType, mut packet_bytes: Vec<u8>) -> Vec<u8> {
|
||||
packet_bytes.insert(0, packet_type.into());
|
||||
packet_bytes
|
||||
}
|
||||
|
||||
fn parse_with_expected_packet_type<T: Packet, F, E>(
|
||||
parser: F,
|
||||
expected_packet_type: PacketType,
|
||||
bytes: &[u8],
|
||||
) -> Result<T, PacketTypeParseError>
|
||||
where
|
||||
F: Fn(&[u8]) -> Result<T, E>,
|
||||
PacketTypeParseError: From<E>,
|
||||
{
|
||||
let (first_byte, packet_bytes) = bytes
|
||||
.split_first()
|
||||
.ok_or(PacketTypeParseError::EmptySlice)?;
|
||||
let actual_packet_type = PacketType::try_from(*first_byte)?;
|
||||
if actual_packet_type == expected_packet_type {
|
||||
Ok(parser(packet_bytes)?)
|
||||
} else {
|
||||
Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: expected_packet_type,
|
||||
actual: actual_packet_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
94
rust/src/internal/hci/tests.rs
Normal file
94
rust/src/internal/hci/tests.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::internal::hci::{
|
||||
packets::{self, Event, EventBuilder, EventCode, Packet, Sco},
|
||||
parse_with_expected_packet_type, prepend_packet_type, PacketType, PacketTypeParseError,
|
||||
WithPacketType,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
||||
#[test]
|
||||
fn prepends_packet_type() {
|
||||
let packet_type = PacketType::Event;
|
||||
let packet_bytes = vec![0x00, 0x00, 0x00, 0x00];
|
||||
let actual = prepend_packet_type(packet_type, packet_bytes);
|
||||
assert_eq!(vec![0x04, 0x00, 0x00, 0x00, 0x00], actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_slice_should_error() {
|
||||
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[]);
|
||||
assert_eq!(Err(PacketTypeParseError::EmptySlice), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid_packet_type_should_error() {
|
||||
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[0xFF]);
|
||||
assert_eq!(
|
||||
Err(PacketTypeParseError::InvalidPacketType { value: 0xFF }),
|
||||
actual
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mismatched_packet_type_should_error() {
|
||||
let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Acl, &[0x01]);
|
||||
assert_eq!(
|
||||
Err(PacketTypeParseError::PacketTypeMismatch {
|
||||
expected: PacketType::Acl,
|
||||
actual: PacketType::Command
|
||||
}),
|
||||
actual
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid_packet_should_error() {
|
||||
let actual = parse_with_expected_packet_type(Sco::parse, PacketType::Sco, &[0x03]);
|
||||
assert!(actual.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packet_roundtrip_with_type() {
|
||||
let event_packet = EventBuilder {
|
||||
event_code: EventCode::InquiryComplete,
|
||||
payload: None,
|
||||
}
|
||||
.build();
|
||||
let event_packet_bytes = event_packet.clone().to_vec_with_packet_type();
|
||||
let actual =
|
||||
parse_with_expected_packet_type(Event::parse, PacketType::Event, &event_packet_bytes)
|
||||
.unwrap();
|
||||
assert_eq!(event_packet, actual);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct FakePacket;
|
||||
|
||||
impl FakePacket {
|
||||
fn parse(_bytes: &[u8]) -> Result<Self, packets::Error> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Packet for FakePacket {
|
||||
fn to_bytes(self) -> Bytes {
|
||||
Bytes::new()
|
||||
}
|
||||
|
||||
fn to_vec(self) -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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(|_| ())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user