Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f64fcd85 authored by Henri Chataing's avatar Henri Chataing Committed by Automerger Merge Worker
Browse files

Merge "RootCanal: Migrate python binding from pybind to ctype" am: 7d36f870

parents 68c5693a 7d36f870
Loading
Loading
Loading
Loading
+6 −10
Original line number Diff line number Diff line
@@ -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",
+0 −214
Original line number Diff line number Diff line
/*
 * 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
+145 −0
Original line number Diff line number Diff line
/*
 * 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"
+35 −0
Original line number Diff line number Diff line
/*
 * 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"
+92 −20
Original line number Diff line number Diff line
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()
Loading