summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Henri Chataing <henrichataing@google.com> 2023-03-09 11:43:23 -0800
committer Henri Chataing <henrichataing@google.com> 2023-03-14 00:51:55 +0000
commit39cfd33a2c1c18795b2a6ac6d0b752c0c7093982 (patch)
tree224c44ca4d6d69e38c63e96ebd28d8a5535c5fc8
parent17b967b0ed4973a018572ce6defb0d649a4f105e (diff)
RootCanal: Migrate python binding from pybind to ctype
pybind is not officially supported in Android. Change the python binding to the supported ctype. Test: atest rootcanal_ll_test Change-Id: Id82ddc6cf27c016e42026720644c987bb6dfad68
-rw-r--r--tools/rootcanal/Android.bp16
-rw-r--r--tools/rootcanal/model/controller/dual_mode_controller_python3.cc214
-rw-r--r--tools/rootcanal/model/controller/ffi.cc145
-rw-r--r--tools/rootcanal/model/controller/ffi.h35
-rw-r--r--tools/rootcanal/py/controller.py112
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_01_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_02_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_03_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_04_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_05_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_08_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py5
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_15_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_16_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_18_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_20_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/ADV/BV_22_C.py1
-rw-r--r--tools/rootcanal/test/LL/DDI/SCN/BV_13_C.py5
-rw-r--r--tools/rootcanal/test/LL/DDI/SCN/BV_14_C.py9
-rw-r--r--tools/rootcanal/test/LL/DDI/SCN/BV_18_C.py5
-rw-r--r--tools/rootcanal/test/main.py59
26 files changed, 314 insertions, 307 deletions
diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp
index 49823c88e9..48a9bcf51b 100644
--- a/tools/rootcanal/Android.bp
+++ b/tools/rootcanal/Android.bp
@@ -127,12 +127,11 @@ cc_library_static {
],
}
-// This library implements Python bindings to the DualModeController
-// class to enable scripted testing in Python.
+// This library implements a foreigh function interface over DualModeController
+// compatible with Python or Rust.
cc_library_host_shared {
- name: "lib_rootcanal_python3",
+ name: "lib_rootcanal_ffi",
defaults: [
- "bluetooth_py3_native_extension_defaults",
"rootcanal_defaults",
],
srcs: [
@@ -140,7 +139,7 @@ cc_library_host_shared {
"model/controller/acl_connection_handler.cc",
"model/controller/controller_properties.cc",
"model/controller/dual_mode_controller.cc",
- "model/controller/dual_mode_controller_python3.cc",
+ "model/controller/ffi.cc",
"model/controller/isochronous_connection_handler.cc",
"model/controller/le_advertiser.cc",
"model/controller/link_layer_controller.cc",
@@ -163,14 +162,11 @@ cc_library_host_shared {
whole_static_libs: [
"libbase",
"liblmp",
- ],
- header_libs: [
- "pybind11_headers",
+ "liblog",
],
cflags: [
"-fexceptions",
],
- rtti: true,
}
// Generate the python parser+serializer backend for
@@ -278,7 +274,7 @@ python_test_host {
"test/LL/DDI/SCN/*.py",
],
data: [
- ":lib_rootcanal_python3",
+ ":lib_rootcanal_ffi",
],
libs: [
"typing_extensions",
diff --git a/tools/rootcanal/model/controller/dual_mode_controller_python3.cc b/tools/rootcanal/model/controller/dual_mode_controller_python3.cc
deleted file mode 100644
index 54ce44f130..0000000000
--- a/tools/rootcanal/model/controller/dual_mode_controller_python3.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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.
- */
-
-#include <android-base/logging.h>
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-
-#include "dual_mode_controller.h"
-#include "model/setup/async_manager.h"
-
-using namespace std::literals;
-namespace py = pybind11;
-
-namespace rootcanal {
-
-namespace hci {
-enum Type {
- CMD,
- EVT,
- ACL,
- SCO,
- ISO,
-};
-} // namespace hci
-
-// Overload the class DualModeController to implement
-// SendLinkLayerPacket as forwarding packets to a registered handler.
-class BaseController : public DualModeController {
- public:
- BaseController() {}
- ~BaseController() = default;
-
- void Start() {
- if (timer_task_id_ == kInvalidTaskId) {
- timer_task_id_ = async_manager_.ExecAsyncPeriodically(
- 0, 0ms, 5ms, [this]() { this->Tick(); });
- }
- }
-
- void Stop() {
- if (timer_task_id_ != kInvalidTaskId) {
- async_manager_.CancelAsyncTask(timer_task_id_);
- timer_task_id_ = kInvalidTaskId;
- }
- }
-
- private:
- AsyncManager async_manager_{};
- AsyncTaskId timer_task_id_{kInvalidTaskId};
-
- BaseController(BaseController const&) = delete;
- DualModeController& operator=(BaseController const&) = delete;
-};
-
-PYBIND11_MODULE(lib_rootcanal_python3, m) {
- m.doc() = "RootCanal controller plugin";
-
- py::enum_<hci::Type>(m, "HciType")
- .value("Cmd", hci::Type::CMD)
- .value("Evt", hci::Type::EVT)
- .value("Acl", hci::Type::ACL)
- .value("Sco", hci::Type::SCO)
- .value("Iso", hci::Type::ISO);
-
- m.def(
- "generate_rpa",
- [](py::bytes arg) {
- std::string irk_str = arg;
- irk_str.resize(LinkLayerController::kIrkSize);
-
- std::array<uint8_t, LinkLayerController::kIrkSize> irk{};
- std::copy(irk_str.begin(), irk_str.end(), irk.begin());
-
- bluetooth::hci::Address rpa =
- rootcanal::LinkLayerController::generate_rpa(irk);
- // Python address representation keeps the same
- // byte order as the string representation, instead of using
- // little endian order.
- std::reverse(rpa.address.begin(), rpa.address.end());
- return rpa.address;
- },
- "Bluetooth RPA generation");
-
- py::class_<rootcanal::BaseController,
- std::shared_ptr<rootcanal::BaseController>>
- basic_controller(m, "BaseController");
-
- // Implement the constructor with two callback parameters to
- // handle emitted HCI packets and LL packets.
- basic_controller.def(py::init([](std::string address_str,
- py::object hci_handler,
- py::object ll_handler) {
- std::shared_ptr<BaseController> controller =
- std::make_shared<BaseController>();
-
- std::optional<bluetooth::hci::Address> address =
- bluetooth::hci::Address::FromString(address_str);
- if (address.has_value()) {
- controller->SetAddress(address.value());
- }
- controller->RegisterEventChannel(
- [=](std::shared_ptr<std::vector<uint8_t>> data) {
- pybind11::gil_scoped_acquire acquire;
- hci_handler(
- hci::Type::EVT,
- py::bytes(reinterpret_cast<char*>(data->data()), data->size()));
- });
- controller->RegisterAclChannel(
- [=](std::shared_ptr<std::vector<uint8_t>> data) {
- pybind11::gil_scoped_acquire acquire;
- hci_handler(
- hci::Type::ACL,
- py::bytes(reinterpret_cast<char*>(data->data()), data->size()));
- });
- controller->RegisterScoChannel(
- [=](std::shared_ptr<std::vector<uint8_t>> data) {
- pybind11::gil_scoped_acquire acquire;
- hci_handler(
- hci::Type::SCO,
- py::bytes(reinterpret_cast<char*>(data->data()), data->size()));
- });
- controller->RegisterIsoChannel(
- [=](std::shared_ptr<std::vector<uint8_t>> data) {
- pybind11::gil_scoped_acquire acquire;
- hci_handler(
- hci::Type::ISO,
- py::bytes(reinterpret_cast<char*>(data->data()), data->size()));
- });
- controller->RegisterLinkLayerChannel([=](std::vector<uint8_t> const& data,
- Phy::Type /*type*/,
- int8_t /*tx_power*/) {
- pybind11::gil_scoped_acquire acquire;
- ll_handler(
- py::bytes(reinterpret_cast<const char*>(data.data()), data.size()));
- });
- return controller;
- }));
-
- // Timer interface.
- basic_controller.def("start", &BaseController::Start);
- basic_controller.def("stop", &BaseController::Stop);
-
- // Implement method BaseController.receive_hci which
- // injects HCI packets into the controller as if sent from the host.
- basic_controller.def(
- "send_hci", [](std::shared_ptr<rootcanal::BaseController> controller,
- hci::Type typ, py::bytes data) {
- std::string data_str = data;
- std::shared_ptr<std::vector<uint8_t>> bytes =
- std::make_shared<std::vector<uint8_t>>(data_str.begin(),
- data_str.end());
-
- switch (typ) {
- case hci::Type::CMD:
- controller->HandleCommand(bytes);
- break;
- case hci::Type::ACL:
- controller->HandleAcl(bytes);
- break;
- case hci::Type::SCO:
- controller->HandleSco(bytes);
- break;
- case hci::Type::ISO:
- controller->HandleIso(bytes);
- break;
- default:
- std::cerr << "Dropping HCI packet with unknown type " << typ
- << std::endl;
- break;
- }
- });
-
- // Implement method BaseController.send_ll which
- // injects LL packets into the controller as if sent over the air.
- basic_controller.def(
- "send_ll", [](std::shared_ptr<rootcanal::BaseController> controller,
- py::bytes data, int rssi) {
- std::string data_str = data;
- std::shared_ptr<std::vector<uint8_t>> bytes =
- std::make_shared<std::vector<uint8_t>>(data_str.begin(),
- data_str.end());
-
- model::packets::LinkLayerPacketView packet =
- model::packets::LinkLayerPacketView::Create(
- bluetooth::packet::PacketView<bluetooth::packet::kLittleEndian>(
- bytes));
- if (!packet.IsValid()) {
- std::cerr << "Dropping malformed LL packet" << std::endl;
- return;
- }
- // TODO: pass correct phy information to ReceiveLinkLayerPacket.
- controller->ReceiveLinkLayerPacket(std::move(packet),
- Phy::Type::LOW_ENERGY, rssi);
- });
-}
-
-__attribute__((constructor)) static void ConfigureLogging() {
- android::base::InitLogging({}, android::base::StdioLogger);
-}
-
-} // namespace rootcanal
diff --git a/tools/rootcanal/model/controller/ffi.cc b/tools/rootcanal/model/controller/ffi.cc
new file mode 100644
index 0000000000..81162ed9dd
--- /dev/null
+++ b/tools/rootcanal/model/controller/ffi.cc
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "ffi.h"
+
+#include <android-base/logging.h>
+
+#include <iostream>
+
+#include "dual_mode_controller.h"
+
+using namespace rootcanal;
+using bluetooth::hci::Address;
+
+namespace hci {
+
+enum Idc {
+ CMD = 1,
+ ACL,
+ SCO,
+ EVT,
+ ISO,
+};
+
+__attribute__((constructor)) static void ConfigureLogging() {
+ android::base::InitLogging({}, android::base::StdioLogger);
+}
+
+} // namespace hci
+
+extern "C" {
+
+__attribute__((visibility("default"))) void* ffi_controller_new(
+ uint8_t const address[6],
+ void (*send_hci)(int idc, uint8_t const* data, size_t data_len),
+ void (*send_ll)(uint8_t const* data, size_t data_len, int phy,
+ int tx_power)) {
+ DualModeController* controller = new DualModeController();
+ controller->SetAddress(Address({address[0], address[1], address[2],
+ address[3], address[4], address[5]}));
+ controller->RegisterEventChannel(
+ [=](std::shared_ptr<std::vector<uint8_t>> data) {
+ send_hci(hci::Idc::EVT, data->data(), data->size());
+ });
+ controller->RegisterAclChannel(
+ [=](std::shared_ptr<std::vector<uint8_t>> data) {
+ send_hci(hci::Idc::ACL, data->data(), data->size());
+ });
+ controller->RegisterScoChannel(
+ [=](std::shared_ptr<std::vector<uint8_t>> data) {
+ send_hci(hci::Idc::SCO, data->data(), data->size());
+ });
+ controller->RegisterIsoChannel(
+ [=](std::shared_ptr<std::vector<uint8_t>> data) {
+ send_hci(hci::Idc::ISO, data->data(), data->size());
+ });
+ controller->RegisterLinkLayerChannel(
+ [=](std::vector<uint8_t> const& data, Phy::Type phy, int8_t tx_power) {
+ send_ll(data.data(), data.size(), static_cast<int>(phy), tx_power);
+ });
+
+ return controller;
+}
+
+__attribute__((visibility("default"))) void ffi_controller_delete(
+ void* controller_) {
+ DualModeController* controller =
+ reinterpret_cast<DualModeController*>(controller_);
+ delete controller;
+}
+
+__attribute__((visibility("default"))) void ffi_controller_receive_hci(
+ void* controller_, int idc, uint8_t const* data, size_t data_len) {
+ DualModeController* controller =
+ reinterpret_cast<DualModeController*>(controller_);
+ std::shared_ptr<std::vector<uint8_t>> bytes =
+ std::make_shared<std::vector<uint8_t>>(data, data + data_len);
+
+ switch (idc) {
+ case hci::Idc::CMD:
+ controller->HandleCommand(bytes);
+ break;
+ case hci::Idc::ACL:
+ controller->HandleAcl(bytes);
+ break;
+ case hci::Idc::SCO:
+ controller->HandleSco(bytes);
+ break;
+ case hci::Idc::ISO:
+ controller->HandleIso(bytes);
+ break;
+ default:
+ std::cerr << "Dropping HCI packet with unknown type " << (int)idc
+ << std::endl;
+ break;
+ }
+}
+
+__attribute__((visibility("default"))) void ffi_controller_receive_ll(
+ void* controller_, uint8_t const* data, size_t data_len, int phy,
+ int rssi) {
+ DualModeController* controller =
+ reinterpret_cast<DualModeController*>(controller_);
+ std::shared_ptr<std::vector<uint8_t>> bytes =
+ std::make_shared<std::vector<uint8_t>>(data, data + data_len);
+ model::packets::LinkLayerPacketView packet =
+ model::packets::LinkLayerPacketView::Create(
+ bluetooth::packet::PacketView<bluetooth::packet::kLittleEndian>(
+ bytes));
+ if (!packet.IsValid()) {
+ std::cerr << "Dropping malformed LL packet" << std::endl;
+ return;
+ }
+ controller->ReceiveLinkLayerPacket(packet, Phy::Type(phy), rssi);
+}
+
+__attribute__((visibility("default"))) void ffi_controller_tick(
+ void* controller_) {
+ DualModeController* controller =
+ reinterpret_cast<DualModeController*>(controller_);
+ controller->Tick();
+}
+
+__attribute__((visibility("default"))) void ffi_generate_rpa(
+ uint8_t const irk_[16], uint8_t rpa[6]) {
+ std::array<uint8_t, LinkLayerController::kIrkSize> irk;
+ memcpy(irk.data(), irk_, LinkLayerController::kIrkSize);
+ Address address = LinkLayerController::generate_rpa(irk);
+ memcpy(rpa, address.data(), Address::kLength);
+}
+
+}; // extern "C"
diff --git a/tools/rootcanal/model/controller/ffi.h b/tools/rootcanal/model/controller/ffi.h
new file mode 100644
index 0000000000..8b6033be02
--- /dev/null
+++ b/tools/rootcanal/model/controller/ffi.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <cstddef>
+#include <cstdint>
+
+extern "C" {
+
+void* ffi_controller_new(uint8_t const address[6],
+ void (*send_hci)(int idc, uint8_t const* data,
+ size_t data_len),
+ void (*send_ll)(uint8_t const* data, size_t data_len,
+ int phy, int tx_power));
+void ffi_controller_delete(void* controller);
+void ffi_controller_receive_hci(void* controller, int idc, uint8_t const* data,
+ size_t data_len);
+void ffi_controller_receive_ll(void* controller, uint8_t const* data,
+ size_t data_len, int phy, int rssi);
+void ffi_controller_tick(void* controller);
+void ffi_generate_rpa(uint8_t const irk[16], uint8_t rpa[6]);
+
+}; // extern "C"
diff --git a/tools/rootcanal/py/controller.py b/tools/rootcanal/py/controller.py
index b1575f1819..99e93a4221 100644
--- a/tools/rootcanal/py/controller.py
+++ b/tools/rootcanal/py/controller.py
@@ -1,13 +1,36 @@
import asyncio
import collections
+import enum
import hci_packets as hci
-import lib_rootcanal_python3 as rootcanal
import link_layer_packets as ll
import py.bluetooth
+import sys
+import typing
import unittest
from typing import Optional
from hci_packets import ErrorCode
+from ctypes import *
+
+rootcanal = cdll.LoadLibrary("lib_rootcanal_ffi.so")
+rootcanal.ffi_controller_new.restype = c_void_p
+
+SEND_HCI_FUNC = CFUNCTYPE(None, c_int, POINTER(c_ubyte), c_size_t)
+SEND_LL_FUNC = CFUNCTYPE(None, POINTER(c_ubyte), c_size_t, c_int, c_int)
+
+
+class Idc(enum.IntEnum):
+ Cmd = 1
+ Acl = 2
+ Sco = 3
+ Evt = 4
+ Iso = 5
+
+
+class Phy(enum.IntEnum):
+ LowEnergy = 0
+ BrEdr = 1
+
class LeFeatures:
@@ -17,14 +40,42 @@ class LeFeatures:
self.le_extended_advertising = (le_features & hci.LLFeaturesBits.LE_EXTENDED_ADVERTISING) != 0
-class Controller(rootcanal.BaseController):
- """Binder class to DualModeController.
+def generate_rpa(irk: bytes) -> hci.Address:
+ rpa = bytearray(6)
+ rpa_type = c_char * 6
+ rootcanal.ffi_generate_rpa(c_char_p(irk), rpa_type.from_buffer(rpa))
+ return hci.Address(bytes(rpa))
+
+
+class Controller:
+ """Binder class over RootCanal's ffi interfaces.
The methods send_cmd, send_hci, send_ll are used to inject HCI or LL
packets into the controller, and receive_hci, receive_ll to
catch outgoing HCI packets of LL pdus."""
def __init__(self, address: hci.Address):
- super().__init__(repr(address), self.receive_hci_, self.receive_ll_)
+ # Write the callbacks for handling HCI and LL send events.
+ @SEND_HCI_FUNC
+ def send_hci(idc: c_int, data: POINTER(c_ubyte), data_len: c_size_t):
+ packet = []
+ for n in range(data_len):
+ packet.append(data[n])
+ self.receive_hci_(int(idc), bytes(packet))
+
+ @SEND_LL_FUNC
+ def send_ll(data: POINTER(c_ubyte), data_len: c_size_t, phy: c_int, tx_power: c_int):
+ packet = []
+ for n in range(data_len):
+ packet.append(data[n])
+ self.receive_ll_(bytes(packet), int(phy), int(tx_power))
+
+ self.send_hci_callback = SEND_HCI_FUNC(send_hci)
+ self.send_ll_callback = SEND_LL_FUNC(send_ll)
+
+ # Create a c++ controller instance.
+ self.instance = rootcanal.ffi_controller_new(c_char_p(address.address), self.send_hci_callback,
+ self.send_ll_callback)
+
self.address = address
self.evt_queue = collections.deque()
self.acl_queue = collections.deque()
@@ -33,37 +84,51 @@ class Controller(rootcanal.BaseController):
self.acl_queue_event = asyncio.Event()
self.ll_queue_event = asyncio.Event()
- def receive_hci_(self, typ: rootcanal.HciType, packet: bytes):
- if typ == rootcanal.HciType.Evt:
+ def __del__(self):
+ rootcanal.ffi_controller_delete(c_void_p(self.instance))
+
+ def receive_hci_(self, idc: int, packet: bytes):
+ if idc == Idc.Evt:
print(f"<-- received HCI event data={len(packet)}[..]")
self.evt_queue.append(packet)
- self.loop.call_soon_threadsafe(self.evt_queue_event.set)
- elif typ == rootcanal.HciType.Acl:
+ self.evt_queue_event.set()
+ elif idc == Idc.Acl:
print(f"<-- received HCI ACL packet data={len(packet)}[..]")
self.acl_queue.append(packet)
- self.loop.call_soon_threadsafe(self.acl_queue_event.set)
+ self.acl_queue_event.set()
else:
print(f"ignoring HCI packet typ={typ}")
- def receive_ll_(self, packet: bytes):
+ def receive_ll_(self, packet: bytes, phy: int, tx_power: int):
print(f"<-- received LL pdu data={len(packet)}[..]")
self.ll_queue.append(packet)
- self.loop.call_soon_threadsafe(self.ll_queue_event.set)
+ self.ll_queue_event.set()
def send_cmd(self, cmd: hci.Command):
print(f"--> sending HCI command {cmd.__class__.__name__}")
- self.send_hci(rootcanal.HciType.Cmd, cmd.serialize())
+ data = cmd.serialize()
+ rootcanal.ffi_controller_receive_hci(c_void_p(self.instance), c_int(Idc.Cmd), c_char_p(data), c_int(len(data)))
- def send_ll(self, pdu: ll.LinkLayerPacket, rssi: int = -90):
+ def send_ll(self, pdu: ll.LinkLayerPacket, phy: Phy = Phy.LowEnergy, rssi: int = -90):
print(f"--> sending LL pdu {pdu.__class__.__name__}")
- super().send_ll(pdu.serialize(), rssi)
+ data = pdu.serialize()
+ rootcanal.ffi_controller_receive_ll(c_void_p(self.instance), c_char_p(data), c_int(len(data)), c_int(phy),
+ c_int(rssi))
async def start(self):
- super().start()
- self.loop = asyncio.get_event_loop()
+
+ async def timer():
+ while True:
+ await asyncio.sleep(0.005)
+ rootcanal.ffi_controller_tick(c_void_p(self.instance))
+
+ # Spawn the controller timer task.
+ self.timer_task = asyncio.create_task(timer())
def stop(self):
- super().stop()
+ # Cancel the controller timer task.
+ del self.timer_task
+
if self.evt_queue:
print("evt queue not empty at stop():")
for packet in self.evt_queue:
@@ -123,10 +188,10 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase):
await controller.expect_evt(hci.SetEventMaskComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
controller.send_cmd(hci.LeSetEventMask(le_event_mask=0xffffffffffffffff))
await controller.expect_evt(hci.LeSetEventMaskComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
- controller.send_cmd(hci.LeReadLocalSupportedFeatures())
# Load the local supported features to be able to disable tests
# that rely on unsupported features.
+ controller.send_cmd(hci.LeReadLocalSupportedFeatures())
evt = await self.expect_cmd_complete(hci.LeReadLocalSupportedFeaturesComplete)
controller.le_features = LeFeatures(evt.le_features)
@@ -158,11 +223,18 @@ class ControllerTest(unittest.IsolatedAsyncioTestCase):
assert evt.num_hci_command_packets == 1
return evt
- async def expect_ll(self, expected_pdu: ll.LinkLayerPacket, timeout: int = 3):
+ async def expect_ll(self, expected_pdu: typing.Union[ll.LinkLayerPacket, type], timeout: int = 3):
packet = await asyncio.wait_for(self.controller.receive_ll(), timeout=timeout)
pdu = ll.LinkLayerPacket.parse_all(packet)
- if pdu != expected_pdu:
+ if isinstance(expected_pdu, type) and not isinstance(pdu, expected_pdu):
+ print("received pdu of unexpected type")
+ print(f"expected pdu: {expected_pdu.__name__}")
+ print("received pdu:")
+ pdu.show()
+ self.assertTrue(False)
+
+ if isinstance(expected_pdu, ll.LinkLayerPacket) and pdu != expected_pdu:
print("received unexpected pdu")
print("expected pdu:")
expected_pdu.show()
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_01_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_01_C.py
index cd8bae1b4c..be89bfc3b8 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_01_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_01_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_02_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_02_C.py
index e27eea17d3..3f49d18c1c 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_02_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_02_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_03_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_03_C.py
index c266c62bea..1eb851153d 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_03_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_03_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_04_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_04_C.py
index f3b8a29916..8888d51fdd 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_04_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_04_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_05_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_05_C.py
index 396ead9ed6..3f846aec4b 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_05_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_05_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py
index 57875af2c3..6c078af77c 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_06_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py
index 9cdad2405a..8a10b6a1ba 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_07_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_08_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_08_C.py
index 34e3dee951..13eb857aee 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_08_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_08_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py
index e20f30b329..bad9c81ba7 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_09_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py
index 153dd7a36f..f2d14f325c 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_11_C.py
@@ -1,5 +1,4 @@
import asyncio
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
@@ -102,6 +101,10 @@ class Test(ControllerTest):
conn_supervision_timeout=self.LL_initiator_connSupervisionTimeout),
rssi=-16)
+ # Note: another advertising pdu is received waiting from the connect
+ # complete.
+ await self.expect_ll(ll.LeLegacyAdvertisingPdu)
+
# Note: Link layer sends LeConnectComplete here.
await self.expect_ll(
ll.LeConnectComplete(source_address=controller.address,
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_15_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_15_C.py
index 70a7a24c45..14d5be02fe 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_15_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_15_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_16_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_16_C.py
index b444112c83..1512ddc7b6 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_16_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_16_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py
index efdc1a2ca4..f22c0421ad 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_17_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_18_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_18_C.py
index 9fcc4142ac..729d934ffa 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_18_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_18_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py
index 60528e9f64..f48caf2fc0 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_19_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_20_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_20_C.py
index 6cbf26ed5b..b1c1769b5a 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_20_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_20_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/ADV/BV_22_C.py b/tools/rootcanal/test/LL/DDI/ADV/BV_22_C.py
index 8a6be12ec5..5c36abe633 100644
--- a/tools/rootcanal/test/LL/DDI/ADV/BV_22_C.py
+++ b/tools/rootcanal/test/LL/DDI/ADV/BV_22_C.py
@@ -1,4 +1,3 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
diff --git a/tools/rootcanal/test/LL/DDI/SCN/BV_13_C.py b/tools/rootcanal/test/LL/DDI/SCN/BV_13_C.py
index 87ae4fab08..1052503eb8 100644
--- a/tools/rootcanal/test/LL/DDI/SCN/BV_13_C.py
+++ b/tools/rootcanal/test/LL/DDI/SCN/BV_13_C.py
@@ -1,10 +1,9 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
from hci_packets import ErrorCode
from py.bluetooth import Address
-from py.controller import ControllerTest
+from py.controller import ControllerTest, generate_rpa
class Test(ControllerTest):
@@ -22,7 +21,7 @@ class Test(ControllerTest):
peer_irk = bytes([1] * 16)
peer_identity_address = Address('aa:bb:cc:dd:ee:ff')
peer_identity_address_type = hci.PeerAddressType.PUBLIC_DEVICE_OR_IDENTITY_ADDRESS
- peer_resolvable_address = Address(rootcanal.generate_rpa(peer_irk))
+ peer_resolvable_address = generate_rpa(peer_irk)
if not controller.le_features.ll_privacy:
self.skipTest("LL privacy not supported")
diff --git a/tools/rootcanal/test/LL/DDI/SCN/BV_14_C.py b/tools/rootcanal/test/LL/DDI/SCN/BV_14_C.py
index 37903e9051..7caa4e1240 100644
--- a/tools/rootcanal/test/LL/DDI/SCN/BV_14_C.py
+++ b/tools/rootcanal/test/LL/DDI/SCN/BV_14_C.py
@@ -1,10 +1,9 @@
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
from hci_packets import ErrorCode
from py.bluetooth import Address
-from py.controller import ControllerTest
+from py.controller import ControllerTest, generate_rpa
class Test(ControllerTest):
@@ -23,9 +22,9 @@ class Test(ControllerTest):
controller = self.controller
peer_irk = bytes([1] * 16)
local_irk = bytes([2] * 16)
- peer_resolvable_address = Address(rootcanal.generate_rpa(peer_irk))
- local_resolvable_address_1 = Address(rootcanal.generate_rpa(local_irk))
- local_resolvable_address_2 = Address(rootcanal.generate_rpa(local_irk))
+ peer_resolvable_address = generate_rpa(peer_irk)
+ local_resolvable_address_1 = generate_rpa(local_irk)
+ local_resolvable_address_2 = generate_rpa(local_irk)
if not controller.le_features.ll_privacy:
self.skipTest("LL privacy not supported")
diff --git a/tools/rootcanal/test/LL/DDI/SCN/BV_18_C.py b/tools/rootcanal/test/LL/DDI/SCN/BV_18_C.py
index bff342bac1..a56ba366df 100644
--- a/tools/rootcanal/test/LL/DDI/SCN/BV_18_C.py
+++ b/tools/rootcanal/test/LL/DDI/SCN/BV_18_C.py
@@ -1,11 +1,10 @@
import asyncio
-import lib_rootcanal_python3 as rootcanal
import hci_packets as hci
import link_layer_packets as ll
import unittest
from hci_packets import ErrorCode
from py.bluetooth import Address
-from py.controller import ControllerTest
+from py.controller import ControllerTest, generate_rpa
class Test(ControllerTest):
@@ -26,7 +25,7 @@ class Test(ControllerTest):
local_irk = bytes([2] * 16)
peer_identity_address = Address('aa:bb:cc:dd:ff:c0')
peer_identity_address_type = hci.PeerAddressType.PUBLIC_DEVICE_OR_IDENTITY_ADDRESS
- peer_resolvable_address = Address(rootcanal.generate_rpa(peer_irk))
+ peer_resolvable_address = generate_rpa(peer_irk)
if not controller.le_features.ll_privacy:
self.skipTest("LL privacy not supported")
diff --git a/tools/rootcanal/test/main.py b/tools/rootcanal/test/main.py
index 9f9db3963e..e1022b4a92 100644
--- a/tools/rootcanal/test/main.py
+++ b/tools/rootcanal/test/main.py
@@ -1,45 +1,34 @@
from importlib import resources
from pathlib import Path
import importlib
+import sys
import tempfile
-
-# Python is not able to load the module lib_rootcanal_python3.so
-# when the test target is configured with embedded_launcher: true.
-# This code loads the file to a temporary directory and adds the
-# path to the sys lookup.
-with tempfile.TemporaryDirectory() as cache:
- with (Path('lib_rootcanal_python3.so').open('rb') as fin,
- Path(cache, 'lib_rootcanal_python3.so').open('wb') as fout):
- fout.write(fin.read())
- sys.path.append(cache)
- import lib_rootcanal_python3
-
import unittest
tests = [
- 'LL.DDI.ADV.BV_01_C',
- 'LL.DDI.ADV.BV_02_C',
- 'LL.DDI.ADV.BV_03_C',
- 'LL.DDI.ADV.BV_04_C',
- 'LL.DDI.ADV.BV_05_C',
- 'LL.DDI.ADV.BV_06_C',
- 'LL.DDI.ADV.BV_07_C',
- 'LL.DDI.ADV.BV_08_C',
- 'LL.DDI.ADV.BV_09_C',
- 'LL.DDI.ADV.BV_11_C',
- 'LL.DDI.ADV.BV_15_C',
- 'LL.DDI.ADV.BV_16_C',
- 'LL.DDI.ADV.BV_17_C',
- 'LL.DDI.ADV.BV_18_C',
- 'LL.DDI.ADV.BV_19_C',
- # TODO: Implement HCI command Le Set Default Phy
- # 'LL.DDI.ADV.BV_20_C',
- 'LL.DDI.ADV.BV_21_C',
- 'LL.DDI.ADV.BV_22_C',
- 'LL.DDI.ADV.BV_47_C',
- 'LL.DDI.SCN.BV_13_C',
- 'LL.DDI.SCN.BV_14_C',
- 'LL.DDI.SCN.BV_18_C',
+ 'LL.DDI.ADV.BV_01_C',
+ 'LL.DDI.ADV.BV_02_C',
+ 'LL.DDI.ADV.BV_03_C',
+ 'LL.DDI.ADV.BV_04_C',
+ 'LL.DDI.ADV.BV_05_C',
+ 'LL.DDI.ADV.BV_06_C',
+ 'LL.DDI.ADV.BV_07_C',
+ 'LL.DDI.ADV.BV_08_C',
+ 'LL.DDI.ADV.BV_09_C',
+ 'LL.DDI.ADV.BV_11_C',
+ 'LL.DDI.ADV.BV_15_C',
+ 'LL.DDI.ADV.BV_16_C',
+ 'LL.DDI.ADV.BV_17_C',
+ 'LL.DDI.ADV.BV_18_C',
+ 'LL.DDI.ADV.BV_19_C',
+ # TODO: Implement HCI command Le Set Default Phy
+ # 'LL.DDI.ADV.BV_20_C',
+ 'LL.DDI.ADV.BV_21_C',
+ 'LL.DDI.ADV.BV_22_C',
+ 'LL.DDI.ADV.BV_47_C',
+ 'LL.DDI.SCN.BV_13_C',
+ 'LL.DDI.SCN.BV_14_C',
+ 'LL.DDI.SCN.BV_18_C',
]
if __name__ == "__main__":