From 4a9e935ee9ebe1c4caaa657d4a73999e7ef87cdc Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Tue, 23 Feb 2021 09:19:43 +0100 Subject: [PATCH] Cert test scaffolding for iso layer Tag: #feature Test: cert/run LeIsoTest Bug: 177907117 Change-Id: Icc23db17d5f6fc8d0fd1f9497a64c99d9f0cfd5e --- gd/Android.bp | 7 + gd/cert/all_cert_testcases | 1 + gd/cert/gd_device.py | 2 + gd/cert/matchers.py | 11 ++ gd/cert/py_le_iso.py | 180 +++++++++++++++++++++++ gd/facade/grpc_root_server.cc | 3 + gd/iso/Android.bp | 1 + gd/iso/cert/cert_le_iso.py | 59 ++++++++ gd/iso/cert/le_iso_test.py | 286 ++++++++++++++++++++++++++++++++++++ gd/iso/facade.cc | 232 +++++++++++++++++++++++++++++ gd/iso/facade.h | 41 ++++++ gd/iso/facade.proto | 90 ++++++++++++ gd/iso/internal/iso_manager_impl.cc | 197 ++++++++++++++++++++++++- gd/iso/internal/iso_manager_impl.h | 68 ++++++++- gd/iso/iso_manager.cc | 54 ++++++- gd/iso/iso_manager.h | 28 +++- gd/iso/iso_module.cc | 1 - 17 files changed, 1248 insertions(+), 13 deletions(-) create mode 100644 gd/cert/py_le_iso.py create mode 100644 gd/iso/cert/cert_le_iso.py create mode 100644 gd/iso/cert/le_iso_test.py create mode 100644 gd/iso/facade.cc create mode 100644 gd/iso/facade.h create mode 100644 gd/iso/facade.proto diff --git a/gd/Android.bp b/gd/Android.bp index cd2648d6d..bd3a8a4ac 100644 --- a/gd/Android.bp +++ b/gd/Android.bp @@ -220,6 +220,7 @@ cc_binary { ":BluetoothFacade_hci_layer", ":BluetoothFacade_l2cap_layer", ":BluetoothFacade_neighbor", + ":BluetoothFacade_iso_layer", ":BluetoothFacade_security_layer", ":BluetoothFacade_shim_layer", ], @@ -652,6 +653,7 @@ filegroup { "hci/facade/le_advertising_manager_facade.proto", "hci/facade/le_initiator_address_facade.proto", "hci/facade/le_scanning_manager_facade.proto", + "iso/facade.proto", "neighbor/facade/facade.proto", "l2cap/classic/facade.proto", "l2cap/le/facade.proto", @@ -691,6 +693,8 @@ genrule { "hci/facade/le_initiator_address_facade.pb.h", "hci/facade/le_scanning_manager_facade.grpc.pb.h", "hci/facade/le_scanning_manager_facade.pb.h", + "iso/facade.grpc.pb.h", + "iso/facade.pb.h", "l2cap/classic/facade.grpc.pb.h", "l2cap/classic/facade.pb.h", "l2cap/le/facade.grpc.pb.h", @@ -735,6 +739,8 @@ genrule { "hci/facade/le_initiator_address_facade.pb.cc", "hci/facade/le_scanning_manager_facade.grpc.pb.cc", "hci/facade/le_scanning_manager_facade.pb.cc", + "iso/facade.grpc.pb.cc", + "iso/facade.pb.cc", "l2cap/classic/facade.grpc.pb.cc", "l2cap/classic/facade.pb.cc", "l2cap/le/facade.grpc.pb.cc", @@ -763,6 +769,7 @@ genrule { "touch $(genDir)/files/hal/__init__.py && " + "touch $(genDir)/files/hci/__init__.py && " + "touch $(genDir)/files/hci/facade/__init__.py && " + + "touch $(genDir)/files/iso/__init__.py && " + "touch $(genDir)/files/l2cap/classic/__init__.py && " + "touch $(genDir)/files/l2cap/le/__init__.py && " + "touch $(genDir)/files/neighbor/facade/__init__.py && " + diff --git a/gd/cert/all_cert_testcases b/gd/cert/all_cert_testcases index d81f85de3..fbbcaeebd 100644 --- a/gd/cert/all_cert_testcases +++ b/gd/cert/all_cert_testcases @@ -16,3 +16,4 @@ LeSecurityTest L2capPerformanceTest SecurityTest ShimTest +LeIsoTest diff --git a/gd/cert/gd_device.py b/gd/cert/gd_device.py index 15f383ff4..65d85d216 100644 --- a/gd/cert/gd_device.py +++ b/gd/cert/gd_device.py @@ -56,6 +56,7 @@ from hci.facade import le_initiator_address_facade_pb2_grpc from hci.facade import le_scanning_manager_facade_pb2_grpc from l2cap.classic import facade_pb2_grpc as l2cap_facade_pb2_grpc from l2cap.le import facade_pb2_grpc as l2cap_le_facade_pb2_grpc +from iso import facade_pb2_grpc as iso_facade_pb2_grpc from neighbor.facade import facade_pb2_grpc as neighbor_facade_pb2_grpc from security import facade_pb2_grpc as security_facade_pb2_grpc from shim.facade import facade_pb2_grpc as shim_facade_pb2_grpc @@ -236,6 +237,7 @@ class GdDeviceBase(ABC): self.hci = hci_facade_pb2_grpc.HciFacadeStub(self.grpc_channel) self.l2cap = l2cap_facade_pb2_grpc.L2capClassicModuleFacadeStub(self.grpc_channel) self.l2cap_le = l2cap_le_facade_pb2_grpc.L2capLeModuleFacadeStub(self.grpc_channel) + self.iso = iso_facade_pb2_grpc.IsoModuleFacadeStub(self.grpc_channel) self.hci_acl_manager = acl_manager_facade_pb2_grpc.AclManagerFacadeStub(self.grpc_channel) self.hci_le_acl_manager = le_acl_manager_facade_pb2_grpc.LeAclManagerFacadeStub(self.grpc_channel) self.hci_le_initiator_address = le_initiator_address_facade_pb2_grpc.LeInitiatorAddressFacadeStub( diff --git a/gd/cert/matchers.py b/gd/cert/matchers.py index 6e7465fc1..5d8fa1f8f 100644 --- a/gd/cert/matchers.py +++ b/gd/cert/matchers.py @@ -716,3 +716,14 @@ class SecurityMatchers(object): @staticmethod def HelperMsg(type, address=None): return lambda event: True if event.message_type == type and (address == None or address == event.peer) else False + + +class IsoMatchers(object): + + @staticmethod + def Data(payload): + return lambda packet: packet.payload == payload + + @staticmethod + def PacketPayloadWithMatchingCisHandle(cis_handle): + return lambda packet: None if cis_handle != packet.handle else packet diff --git a/gd/cert/py_le_iso.py b/gd/cert/py_le_iso.py new file mode 100644 index 000000000..bc22f491c --- /dev/null +++ b/gd/cert/py_le_iso.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 - 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. + +import logging + +from bluetooth_packets_python3 import hci_packets +from cert.capture import Capture +from cert.captures import SecurityCaptures +from cert.closable import Closable +from cert.closable import safeClose +from cert.event_stream import EventStream, IEventStream +from cert.event_stream import FilteringEventStream +from cert.matchers import IsoMatchers +from cert.truth import assertThat +from datetime import timedelta +from facade import common_pb2 as common +from google.protobuf import empty_pb2 as empty_proto +from iso import facade_pb2 as iso_facade_pb2 + + +class CisTestParameters(): + + def __init__(self, cis_id, nse, max_sdu_m_to_s, max_sdu_s_to_m, max_pdu_m_to_s, max_pdu_s_to_m, phy_m_to_s, + phy_s_to_m, bn_m_to_s, bn_s_to_m): + self.cis_id = cis_id + self.nse = nse + self.max_sdu_m_to_s = max_sdu_m_to_s + self.max_sdu_s_to_m = max_sdu_s_to_m + self.max_pdu_m_to_s = max_pdu_m_to_s + self.max_pdu_s_to_m = max_pdu_s_to_m + self.phy_m_to_s = phy_m_to_s + self.phy_s_to_m = phy_s_to_m + self.bn_m_to_s = bn_m_to_s + self.bn_s_to_m = bn_s_to_m + + +class PyLeIsoStream(IEventStream): + + def __init__(self, device, cis_handle, iso_data_stream): + self._device = device + self._cis_handle = cis_handle + self._le_iso_data_stream = iso_data_stream + self._our_le_iso_cis_view = FilteringEventStream( + self._le_iso_data_stream, IsoMatchers.PacketPayloadWithMatchingCisHandle(self._cis_handle)) + + def get_event_queue(self): + return self._our_le_iso_cis_view.get_event_queue() + + def send(self, payload): + self._device.iso.SendIsoPacket(iso_facade_pb2.IsoPacket(handle=self._cis_handle, payload=payload)) + + +class PyLeIso(Closable): + """ + Abstraction for iso tasks and GRPC calls + """ + + _iso_event_stream = None + + def __init__(self, device): + logging.info("DUT: Init") + self._device = device + self._device.wait_channel_ready() + self._iso_event_stream = EventStream(self._device.iso.FetchIsoEvents(empty_proto.Empty())) + self._iso_data_stream = EventStream(self._device.iso.FetchIsoData(empty_proto.Empty())) + + def close(self): + if self._iso_event_stream is not None: + safeClose(self._iso_event_stream) + else: + logging.info("DUT: ISO Event Stream is None!") + if self._iso_data_stream is not None: + safeClose(self._iso_data_stream) + else: + logging.info("DUT: ISO Data Stream is None!") + + logging.info("DUT: close") + + def le_set_cig_parameters(self, cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, peripherals_clock_accuracy, + packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, cis_id, + max_sdu_m_to_s, max_sdu_s_to_m, phy_m_to_s, phy_s_to_m, rtn_m_to_s, rtn_s_to_m): + + resp = self._device.iso.LeSetCigParameters( + iso_facade_pb2.LeSetCigParametersRequest( + cig_id=cig_id, + sdu_interval_m_to_s=sdu_interval_m_to_s, + sdu_interval_s_to_m=sdu_interval_s_to_m, + peripherals_clock_accuracy=peripherals_clock_accuracy, + packing=packing, + framing=framing, + max_transport_latency_m_to_s=max_transport_latency_m_to_s, + max_transport_latency_s_to_m=max_transport_latency_s_to_m, + cis_id=cis_id, + max_sdu_m_to_s=max_sdu_m_to_s, + max_sdu_s_to_m=max_sdu_s_to_m, + phy_m_to_s=phy_m_to_s, + phy_s_to_m=phy_s_to_m, + rtn_m_to_s=rtn_m_to_s, + rtn_s_to_m=rtn_s_to_m)) + + def le_set_cig_parameters_test(self, cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, + iso_interval, peripherals_clock_accuracy, packing, framing, + max_transport_latency_m_to_s, max_transport_latency_s_to_m, cis_configs): + configs = [] + for cc in cis_configs: + configs.append( + iso_facade_pb2.LeSetCigParametersTestRequest.LeCisParametersTestConfig( + cis_id=cc.cis_id, + nse=cc.nse, + max_sdu_m_to_s=cc.max_sdu_m_to_s, + max_sdu_s_to_m=cc.max_sdu_s_to_m, + max_pdu_m_to_s=cc.max_pdu_m_to_s, + max_pdu_s_to_m=cc.max_pdu_s_to_m, + phy_m_to_s=cc.phy_m_to_s, + phy_s_to_m=cc.phy_s_to_m, + bn_m_to_s=cc.bn_m_to_s, + bn_s_to_m=cc.bn_s_to_m, + )) + + resp = self._device.iso.LeSetCigParameters( + iso_facade_pb2.LeSetCigParametersTestRequest( + cig_id=cig_id, + sdu_interval_m_to_s=sdu_interval_m_to_s, + sdu_interval_s_to_m=sdu_interval_s_to_m, + ft_m_to_s=ft_m_to_s, + ft_s_to_m=ft_s_to_m, + iso_interval=iso_interval, + peripherals_clock_accuracy=peripherals_clock_accuracy, + packing=packing, + framing=framing, + max_transport_latency_m_to_s=max_transport_latency_m_to_s, + max_transport_latency_s_to_m=max_transport_latency_s_to_m, + cis_configs=configs)) + + def wait_le_set_cig_parameters_complete(self): + set_cig_params_complete_capture = PyLeIso.IsoCigComplete(iso_facade_pb2.IsoMsgType.ISO_PARAMETERS_SET_COMPLETE) + + assertThat(self._iso_event_stream).emits(set_cig_params_complete_capture, timeout=timedelta(seconds=5)) + return set_cig_params_complete_capture.get() + + @staticmethod + def IsoCigComplete(type=None): + return Capture(lambda event: True if event.message_type == type else False, PyLeIso._extract_cis_handles) + + @staticmethod + def _extract_cis_handles(event): + if event is None: + return None + return event.cis_handle + + def le_create_cis(self, cis_and_acl_handle_array): + handles_pairs = [] + for hp_tmp in cis_and_acl_handle_array: + handles_pairs.append( + iso_facade_pb2.LeCreateCisRequest.HandlePair(cis_handle=hp_tmp[0], acl_handle=hp_tmp[1])) + + self._device.iso.LeCreateCis(iso_facade_pb2.LeCreateCisRequest(handle_pair=handles_pairs)) + + def wait_le_cis_established(self): + cis_establshed_capture = PyLeIso.IsoCigEstablished(iso_facade_pb2.IsoMsgType.ISO_CIS_ESTABLISHED) + assertThat(self._iso_event_stream).emits(cis_establshed_capture, timeout=timedelta(seconds=5)) + cis_handle = cis_establshed_capture.get()[0] + return PyLeIsoStream(self._device, cis_handle, self._iso_data_stream) + + @staticmethod + def IsoCigEstablished(type): + return Capture(lambda event: True if event.message_type == type else False, PyLeIso._extract_cis_handles) diff --git a/gd/facade/grpc_root_server.cc b/gd/facade/grpc_root_server.cc index edf68d014..21e98e425 100644 --- a/gd/facade/grpc_root_server.cc +++ b/gd/facade/grpc_root_server.cc @@ -32,6 +32,7 @@ #include "hci/hci_layer.h" #include "hci/le_advertising_manager.h" #include "hci/le_scanning_manager.h" +#include "iso/facade.h" #include "l2cap/classic/facade.h" #include "l2cap/le/facade.h" #include "neighbor/connectability.h" @@ -90,6 +91,7 @@ class RootFacadeService : public ::bluetooth::facade::RootFacade::Service { modules.add<::bluetooth::hci::facade::LeInitiatorAddressFacadeModule>(); modules.add<::bluetooth::hci::facade::LeScanningManagerFacadeModule>(); modules.add<::bluetooth::neighbor::facade::NeighborFacadeModule>(); + modules.add<::bluetooth::iso::IsoModuleFacadeModule>(); break; case BluetoothModule::L2CAP: modules.add<::bluetooth::hci::facade::ControllerFacadeModule>(); @@ -100,6 +102,7 @@ class RootFacadeService : public ::bluetooth::facade::RootFacade::Service { modules.add<::bluetooth::l2cap::classic::L2capClassicModuleFacadeModule>(); modules.add<::bluetooth::l2cap::le::L2capLeModuleFacadeModule>(); modules.add<::bluetooth::hci::facade::HciFacadeModule>(); + modules.add<::bluetooth::iso::IsoModuleFacadeModule>(); break; case BluetoothModule::SECURITY: modules.add<::bluetooth::facade::ReadOnlyPropertyServerModule>(); diff --git a/gd/iso/Android.bp b/gd/iso/Android.bp index 060b1157d..6d07c39f3 100644 --- a/gd/iso/Android.bp +++ b/gd/iso/Android.bp @@ -25,5 +25,6 @@ filegroup { filegroup { name: "BluetoothFacade_iso_layer", srcs: [ + "facade.cc", ], } diff --git a/gd/iso/cert/cert_le_iso.py b/gd/iso/cert/cert_le_iso.py new file mode 100644 index 000000000..94043649b --- /dev/null +++ b/gd/iso/cert/cert_le_iso.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 - 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. + +import logging + +from cert.closable import Closable +from cert.closable import safeClose +from cert.py_le_iso import PyLeIso +import bluetooth_packets_python3 as bt_packets +from bluetooth_packets_python3 import l2cap_packets + + +class CertLeIso(Closable): + + def __init__(self, device): + self._device = device + self._le_iso = PyLeIso(device) + + def close(self): + logging.info("DUT: close") + self._le_iso.close() + + def le_set_cig_parameters(self, cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, peripherals_clock_accuracy, + packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, cis_id, + max_sdu_m_to_s, max_sdu_s_to_m, phy_m_to_s, phy_s_to_m, rtn_m_to_s, rtn_s_to_m): + return self._le_iso.le_set_cig_parameters( + cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, peripherals_clock_accuracy, packing, framing, + max_transport_latency_m_to_s, max_transport_latency_s_to_m, cis_id, max_sdu_m_to_s, max_sdu_s_to_m, + phy_m_to_s, phy_s_to_m, rtn_m_to_s, rtn_s_to_m) + + def le_set_cig_parameters_test(self, cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, + iso_interval, peripherals_clock_accuracy, packing, framing, + max_transport_latency_m_to_s, max_transport_latency_s_to_m, cis_configs): + return self._le_iso.le_set_cig_parameters_test(cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, + ft_s_to_m, iso_interval, peripherals_clock_accuracy, packing, + framing, max_transport_latency_m_to_s, + max_transport_latency_s_to_m, cis_configs) + + def wait_le_set_cig_parameters_complete(self): + return self._le_iso.wait_le_set_cig_parameters_complete() + + def le_cretate_cis(self, cis_and_acl_handle_array): + self._le_iso.le_create_cis(cis_and_acl_handle_array) + + def wait_le_cis_established(self): + return self._le_iso.wait_le_cis_established() diff --git a/gd/iso/cert/le_iso_test.py b/gd/iso/cert/le_iso_test.py new file mode 100644 index 000000000..c1d3d65e3 --- /dev/null +++ b/gd/iso/cert/le_iso_test.py @@ -0,0 +1,286 @@ +# +# Copyright 2021 - 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. + +import time +import logging + +from bluetooth_packets_python3 import hci_packets +from cert.event_stream import EventStream +from cert.gd_base_test import GdBaseTestClass +from cert.matchers import HciMatchers, IsoMatchers, L2capMatchers +from cert.metadata import metadata +from cert.py_hci import PyHci +from cert.py_l2cap import PyLeL2cap +from cert.py_le_iso import PyLeIso +from cert.py_le_iso import CisTestParameters +from cert.truth import assertThat +from datetime import timedelta +from facade import common_pb2 as common +from hci.facade import controller_facade_pb2 as controller_facade +from hci.facade import le_advertising_manager_facade_pb2 as le_advertising_facade +from hci.facade import le_initiator_address_facade_pb2 as le_initiator_address_facade +from google.protobuf import empty_pb2 as empty_proto +from neighbor.facade import facade_pb2 as neighbor_facade +from l2cap.le.cert.cert_le_l2cap import CertLeL2cap +from iso.cert.cert_le_iso import CertLeIso + +import time +from bluetooth_packets_python3.hci_packets import OpCode + + +class LeIsoTest(GdBaseTestClass): + """ + Collection of tests that each sample results from + different (unique) combinations of io capabilities, authentication requirements, and oob data. + """ + + def setup_class(self): + super().setup_class(dut_module='L2CAP', cert_module='HCI_INTERFACES') + + def setup_test(self): + super().setup_test() + + self.dut_l2cap = PyLeL2cap(self.dut) + self.cert_l2cap = CertLeL2cap(self.cert) + self.dut_address = common.BluetoothAddressWithType( + address=common.BluetoothAddress(address=bytes(b'D0:05:04:03:02:01')), type=common.RANDOM_DEVICE_ADDRESS) + self.cert_address = common.BluetoothAddressWithType( + address=common.BluetoothAddress(address=bytes(b'C0:11:FF:AA:33:22')), type=common.RANDOM_DEVICE_ADDRESS) + dut_privacy_policy = le_initiator_address_facade.PrivacyPolicy( + address_policy=le_initiator_address_facade.AddressPolicy.USE_STATIC_ADDRESS, + address_with_type=self.dut_address, + rotation_irk=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + minimum_rotation_time=0, + maximum_rotation_time=0) + self.dut_l2cap._device.hci_le_initiator_address.SetPrivacyPolicyForInitiatorAddress(dut_privacy_policy) + privacy_policy = le_initiator_address_facade.PrivacyPolicy( + address_policy=le_initiator_address_facade.AddressPolicy.USE_STATIC_ADDRESS, + address_with_type=self.cert_address, + rotation_irk=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + minimum_rotation_time=0, + maximum_rotation_time=0) + self.cert_l2cap._device.hci_le_initiator_address.SetPrivacyPolicyForInitiatorAddress(privacy_policy) + + self.dut_iso = PyLeIso(self.dut) + self.cert_iso = CertLeIso(self.cert) + + def teardown_test(self): + self.dut_iso.close() + self.cert_iso.close() + + self.cert_l2cap.close() + self.dut_l2cap.close() + super().teardown_test() + + #cert becomes central of connection, dut peripheral + def _setup_link_from_cert(self): + # DUT Advertises + gap_name = hci_packets.GapData() + gap_name.data_type = hci_packets.GapDataType.COMPLETE_LOCAL_NAME + gap_name.data = list(bytes(b'Im_The_DUT')) + gap_data = le_advertising_facade.GapDataMsg(data=bytes(gap_name.Serialize())) + config = le_advertising_facade.AdvertisingConfig( + advertisement=[gap_data], + interval_min=512, + interval_max=768, + advertising_type=le_advertising_facade.AdvertisingEventType.ADV_IND, + own_address_type=common.USE_RANDOM_DEVICE_ADDRESS, + channel_map=7, + filter_policy=le_advertising_facade.AdvertisingFilterPolicy.ALL_DEVICES) + request = le_advertising_facade.CreateAdvertiserRequest(config=config) + create_response = self.dut.hci_le_advertising_manager.CreateAdvertiser(request) + self.cert_l2cap.connect_le_acl(self.dut_address) + + def _setup_cis_from_cert(self, cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, iso_interval, + peripherals_clock_accuracy, packing, framing, max_transport_latency_m_to_s, + max_transport_latency_s_to_m, cis_configs): + self.cert_iso.le_set_cig_parameters_test(cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, + iso_interval, peripherals_clock_accuracy, packing, framing, + max_transport_latency_m_to_s, max_transport_latency_s_to_m, + cis_configs) + + cis_handles = self.cert_iso.wait_le_set_cig_parameters_complete() + + cis_handle = cis_handles[0] + + acl_connection_handle = self.cert_l2cap._le_acl.handle + self.cert_iso.le_cretate_cis([(cis_handle, acl_connection_handle)]) + dut_cis_stream = self.dut_iso.wait_le_cis_established() + cert_cis_stream = self.cert_iso.wait_le_cis_established() + return (dut_cis_stream, cert_cis_stream) + + @metadata( + pts_test_id="IAL/CIS/UNF/SLA/BV-01-C", + pts_test_name="connected isochronous stream, unframed data, peripheral role") + def test_iso_cis_unf_sla_bv_01_c(self): + """ + Verify that the IUT can send an SDU with length ≤ the Isochronous PDU length. + """ + cig_id = 0x01 + sdu_interval_m_to_s = 0 + sdu_interval_s_to_m = 0x186a + ft_m_to_s = 0 + ft_s_to_m = 1 + iso_interval = 0x0A + peripherals_clock_accuracy = 0 + packing = 0 + framing = 0 + max_transport_latency_m_to_s = 0 + max_transport_latency_s_to_m = 0 + cis_configs = [ + CisTestParameters( + cis_id=0x01, + nse=2, + max_sdu_m_to_s=100, + max_sdu_s_to_m=100, + max_pdu_m_to_s=100, + max_pdu_s_to_m=100, + phy_m_to_s=0x02, + phy_s_to_m=0x00, + bn_m_to_s=0, + bn_s_to_m=2, + ) + ] + + self._setup_link_from_cert() + (dut_cis_stream, cert_cis_stream) = self._setup_cis_from_cert( + cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, iso_interval, + peripherals_clock_accuracy, packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, + cis_configs) + dut_cis_stream.send(b'abcdefgh' * 10) + assertThat(cert_cis_stream).emits(IsoMatchers.Data(b'abcdefgh' * 10)) + + @metadata( + pts_test_id="IAL/CIS/UNF/SLA/BV-25-C", + pts_test_name="connected isochronous stream, unframed data, peripheral role") + def test_iso_cis_unf_sla_bv_25_c(self): + """ + Verify that the IUT can send an SDU with length ≤ the Isochronous PDU length. + """ + cig_id = 0x01 + sdu_interval_m_to_s = 0x7530 + sdu_interval_s_to_m = 0x7530 + ft_m_to_s = 3 + ft_s_to_m = 2 + iso_interval = 0x18 + peripherals_clock_accuracy = 0 + packing = 0 + framing = 0 + max_transport_latency_m_to_s = 0 + max_transport_latency_s_to_m = 0 + cis_configs = [ + CisTestParameters( + cis_id=0x01, + nse=5, + max_sdu_m_to_s=100, + max_sdu_s_to_m=100, + max_pdu_m_to_s=100, + max_pdu_s_to_m=100, + phy_m_to_s=0x02, + phy_s_to_m=0x00, + bn_m_to_s=3, + bn_s_to_m=1, + ) + ] + + self._setup_link_from_cert() + (dut_cis_stream, cert_cis_stream) = self._setup_cis_from_cert( + cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, iso_interval, + peripherals_clock_accuracy, packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, + cis_configs) + dut_cis_stream.send(b'abcdefgh' * 10) + assertThat(cert_cis_stream).emits(IsoMatchers.Data(b'abcdefgh' * 10)) + + @metadata( + pts_test_id="IAL/CIS/FRA/SLA/BV-03-C", + pts_test_name="connected isochronous stream, framed data, peripheral role") + def test_iso_cis_fra_sla_bv_03_c(self): + """ + Verify that the IUT can send an SDU with length ≤ the Isochronous PDU length. + """ + cig_id = 0x01 + sdu_interval_m_to_s = 0x0000 + sdu_interval_s_to_m = 0x4e30 + ft_m_to_s = 0 + ft_s_to_m = 2 + iso_interval = 0x14 + peripherals_clock_accuracy = 0 + packing = 0 + framing = 1 + max_transport_latency_m_to_s = 0 + max_transport_latency_s_to_m = 0 + cis_configs = [ + CisTestParameters( + cis_id=0x01, + nse=4, + max_sdu_m_to_s=100, + max_sdu_s_to_m=100, + max_pdu_m_to_s=100, + max_pdu_s_to_m=100, + phy_m_to_s=0x02, + phy_s_to_m=0x00, + bn_m_to_s=0, + bn_s_to_m=2, + ) + ] + + self._setup_link_from_cert() + (dut_cis_stream, cert_cis_stream) = self._setup_cis_from_cert( + cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, iso_interval, + peripherals_clock_accuracy, packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, + cis_configs) + dut_cis_stream.send(b'abcdefgh' * 10) + assertThat(cert_cis_stream).emits(IsoMatchers.Data(b'abcdefgh' * 10)) + + @metadata( + pts_test_id="IAL/CIS/FRA/SLA/BV-26-C", + pts_test_name="connected isochronous stream, framed data, peripheral role") + def test_iso_cis_fra_sla_bv_26_c(self): + """ + Verify that the IUT can send an SDU with length ≤ the Isochronous PDU length. + """ + cig_id = 0x01 + sdu_interval_m_to_s = 0x14D5 + sdu_interval_s_to_m = 0x14D5 + ft_m_to_s = 1 + ft_s_to_m = 1 + iso_interval = 0x08 + peripherals_clock_accuracy = 0 + packing = 0 + framing = 1 + max_transport_latency_m_to_s = 0 + max_transport_latency_s_to_m = 0 + cis_configs = [ + CisTestParameters( + cis_id=0x01, + nse=2, + max_sdu_m_to_s=100, + max_sdu_s_to_m=100, + max_pdu_m_to_s=100, + max_pdu_s_to_m=100, + phy_m_to_s=0x02, + phy_s_to_m=0x00, + bn_m_to_s=1, + bn_s_to_m=1, + ) + ] + + self._setup_link_from_cert() + (dut_cis_stream, cert_cis_stream) = self._setup_cis_from_cert( + cig_id, sdu_interval_m_to_s, sdu_interval_s_to_m, ft_m_to_s, ft_s_to_m, iso_interval, + peripherals_clock_accuracy, packing, framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, + cis_configs) + dut_cis_stream.send(b'abcdefgh' * 10) + assertThat(cert_cis_stream).emits(IsoMatchers.Data(b'abcdefgh' * 10)) diff --git a/gd/iso/facade.cc b/gd/iso/facade.cc new file mode 100644 index 000000000..9b5c88e8b --- /dev/null +++ b/gd/iso/facade.cc @@ -0,0 +1,232 @@ +/* + * Copyright 2021 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 "iso/facade.h" + +#include "common/contextual_callback.h" +#include "grpc/grpc_event_queue.h" +#include "hci/acl_manager.h" +#include "hci/address_with_type.h" +#include "hci/le_address_manager.h" +#include "iso/facade.grpc.pb.h" +#include "iso/iso_module.h" +#include "os/handler.h" + +using bluetooth::hci::AclManager; + +namespace bluetooth { +namespace iso { + +class IsoModuleFacadeService : public IsoModuleFacade::Service { + public: + IsoModuleFacadeService(IsoModule* iso_module, AclManager* acl_manager, ::bluetooth::os::Handler* iso_handler) + : iso_module_(iso_module), acl_manager_(acl_manager), iso_handler_(iso_handler) { + ASSERT(iso_module_); + ASSERT(iso_handler_); + + iso_module_->GetIsoManager()->RegisterIsoEstablishedCallback(iso_handler_->Bind( + [](::bluetooth::grpc::GrpcEventQueue* le_iso_events_, uint16_t cis_connection_handle) { + LeIsoEventsMsg msg; + msg.set_message_type(IsoMsgType::ISO_CIS_ESTABLISHED); + msg.add_cis_handle(cis_connection_handle); + le_iso_events_->OnIncomingEvent(msg); + }, + &le_iso_events_)); + + iso_module_->GetIsoManager()->RegisterIsoDataCallback( + iso_handler_->BindOn(this, &IsoModuleFacadeService::OnIsoPacketReceived)); + } + + ::grpc::Status LeSetCigParameters( + ::grpc::ServerContext* context, + const ::bluetooth::iso::LeSetCigParametersRequest* request, + ::google::protobuf::Empty* response) override { + std::vector cis_config; + + hci::CisParametersConfig cfg; + cfg.cis_id_ = request->cis_id(); + cfg.max_sdu_m_to_s_ = request->max_sdu_m_to_s(); + cfg.max_sdu_s_to_m_ = request->max_sdu_s_to_m(); + cfg.phy_m_to_s_ = request->phy_m_to_s(); + cfg.phy_s_to_m_ = request->phy_s_to_m(); + cfg.rtn_m_to_s_ = request->rtn_m_to_s(); + cfg.rtn_s_to_m_ = request->rtn_s_to_m(); + + cis_config.push_back(cfg); + + iso_module_->GetIsoManager()->SetCigParameters( + request->cig_id(), + request->sdu_interval_m_to_s(), + request->sdu_interval_s_to_m(), + static_cast(request->peripherals_clock_accuracy()), + static_cast(request->packing()), + static_cast(request->framing()), + request->max_transport_latency_m_to_s(), + request->max_transport_latency_s_to_m(), + cis_config, + iso_handler_->BindOnce( + [](::bluetooth::grpc::GrpcEventQueue* le_iso_events_, std::vector conn_handles) { + LeIsoEventsMsg msg; + + msg.set_message_type(IsoMsgType::ISO_PARAMETERS_SET_COMPLETE); + for (const uint16_t conn_handle : conn_handles) { + msg.add_cis_handle(conn_handle); + } + le_iso_events_->OnIncomingEvent(msg); + }, + &le_iso_events_)); + return ::grpc::Status::OK; + } + + ::grpc::Status LeSetCigParametersTest( + ::grpc::ServerContext* context, + const ::bluetooth::iso::LeSetCigParametersTestRequest* request, + ::google::protobuf::Empty* response) override { + std::vector cis_config; + + for (const auto& cc : request->cis_configs()) { + hci::LeCisParametersTestConfig cfg; + cfg.cis_id_ = cc.cis_id(); + cfg.nse_ = cc.nse(); + cfg.max_sdu_m_to_s_ = cc.max_sdu_m_to_s(); + cfg.max_sdu_s_to_m_ = cc.max_sdu_s_to_m(); + cfg.max_pdu_m_to_s_ = cc.max_pdu_m_to_s(); + cfg.max_pdu_s_to_m_ = cc.max_pdu_s_to_m(); + cfg.phy_m_to_s_ = cc.phy_m_to_s(); + cfg.phy_s_to_m_ = cc.phy_s_to_m(); + cfg.bn_m_to_s_ = cc.bn_m_to_s(); + cfg.bn_s_to_m_ = cc.bn_s_to_m(); + cis_config.push_back(cfg); + } + iso_module_->GetIsoManager()->SetCigParametersTest( + request->cig_id(), + request->sdu_interval_m_to_s(), + request->sdu_interval_s_to_m(), + request->ft_m_to_s(), + request->ft_s_to_m(), + request->iso_interval(), + static_cast(request->peripherals_clock_accuracy()), + static_cast(request->packing()), + static_cast(request->framing()), + request->max_transport_latency_m_to_s(), + request->max_transport_latency_s_to_m(), + cis_config, + iso_handler_->BindOnce( + [](::bluetooth::grpc::GrpcEventQueue* le_iso_events_, std::vector conn_handles) { + LeIsoEventsMsg msg; + + msg.set_message_type(IsoMsgType::ISO_PARAMETERS_SET_COMPLETE); + for (const uint16_t conn_handle : conn_handles) { + msg.add_cis_handle(conn_handle); + } + le_iso_events_->OnIncomingEvent(msg); + }, + &le_iso_events_)); + return ::grpc::Status::OK; + } + + ::grpc::Status LeCreateCis( + ::grpc::ServerContext* context, + const ::bluetooth::iso::LeCreateCisRequest* request, + ::google::protobuf::Empty* response) override { + std::vector> create_cis_params; + for (const auto& handle_pair : request->handle_pair()) { + create_cis_params.push_back( + std::make_pair(handle_pair.cis_handle(), handle_pair.acl_handle())); + } + iso_module_->GetIsoManager()->LeCreateCis(create_cis_params); + + return ::grpc::Status::OK; + } + + ::grpc::Status FetchIsoData( + ::grpc::ServerContext* context, const LeCisHandleMsg* request, ::grpc::ServerWriter* writer) override { + return le_iso_data_.RunLoop(context, writer); + } + + ::grpc::Status FetchIsoEvents( + ::grpc::ServerContext* context, + const google::protobuf::Empty* request, + ::grpc::ServerWriter* writer) override { + return le_iso_events_.RunLoop(context, writer); + } + + ::grpc::Status SendIsoPacket( + ::grpc::ServerContext* context, + const ::bluetooth::iso::IsoPacket* request, + ::google::protobuf::Empty* response) override { + std::vector packet(request->payload().begin(), request->payload().end()); + iso_module_->GetIsoManager()->SendIsoPacket(request->handle(), packet); + return ::grpc::Status::OK; + } + + void OnIsoPacketReceived(std::unique_ptr iso_view) { + ASSERT(iso_view->IsValid()); + + IsoPacket packet; + packet.set_handle(iso_view->GetConnectionHandle()); + + if (iso_view->GetTsFlag() == hci::TimeStampFlag::NOT_PRESENT) { + hci::IsoWithoutTimestampView nts = hci::IsoWithoutTimestampView::Create(*iso_view); + ASSERT(nts.IsValid()); + + auto data_vec = nts.GetPayload(); + std::string data = std::string(data_vec.begin(), data_vec.end()); + packet.set_payload(data); + le_iso_data_.OnIncomingEvent(packet); + } else { + hci::IsoWithTimestampView tsv = hci::IsoWithTimestampView::Create(*iso_view); + ASSERT(tsv.IsValid()); + + auto data_vec = tsv.GetPayload(); + std::string data = std::string(data_vec.begin(), data_vec.end()); + packet.set_payload(data); + le_iso_data_.OnIncomingEvent(packet); + } + } + + private: + IsoModule* iso_module_; + ::bluetooth::grpc::GrpcEventQueue le_iso_events_{"LE ISO events"}; + ::bluetooth::grpc::GrpcEventQueue le_iso_data_{"LE ISO data"}; + AclManager* acl_manager_ __attribute__((unused)); + ::bluetooth::os::Handler* iso_handler_; +}; + +void IsoModuleFacadeModule::ListDependencies(ModuleList* list) { + ::bluetooth::grpc::GrpcFacadeModule::ListDependencies(list); + list->add(); + list->add(); +} + +void IsoModuleFacadeModule::Start() { + ::bluetooth::grpc::GrpcFacadeModule::Start(); + service_ = new IsoModuleFacadeService(GetDependency(), GetDependency(), GetHandler()); +} + +void IsoModuleFacadeModule::Stop() { + delete service_; + ::bluetooth::grpc::GrpcFacadeModule::Stop(); +} + +::grpc::Service* IsoModuleFacadeModule::GetService() const { + return service_; +} + +const ModuleFactory IsoModuleFacadeModule::Factory = + ::bluetooth::ModuleFactory([]() { return new IsoModuleFacadeModule(); }); + +} // namespace iso +} // namespace bluetooth diff --git a/gd/iso/facade.h b/gd/iso/facade.h new file mode 100644 index 000000000..266bb0958 --- /dev/null +++ b/gd/iso/facade.h @@ -0,0 +1,41 @@ +/* + * Copyright 2019 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. + */ +#pragma once + +#include + +#include "grpc/grpc_module.h" + +namespace bluetooth { +namespace iso { + +class IsoModuleFacadeService; + +class IsoModuleFacadeModule : public ::bluetooth::grpc::GrpcFacadeModule { + public: + static const ModuleFactory Factory; + + void ListDependencies(ModuleList* list) override; + void Start() override; + void Stop() override; + ::grpc::Service* GetService() const override; + + private: + IsoModuleFacadeService* service_; +}; + +} // namespace iso +} // namespace bluetooth diff --git a/gd/iso/facade.proto b/gd/iso/facade.proto new file mode 100644 index 000000000..28f9d27bc --- /dev/null +++ b/gd/iso/facade.proto @@ -0,0 +1,90 @@ +syntax = "proto3"; + +package bluetooth.iso; + +import "google/protobuf/empty.proto"; +import "facade/common.proto"; + +service IsoModuleFacade { + rpc LeSetCigParameters(LeSetCigParametersRequest) returns (google.protobuf.Empty) {} + rpc LeSetCigParametersTest(LeSetCigParametersTestRequest) returns (google.protobuf.Empty) {} + rpc LeCreateCis(LeCreateCisRequest) returns (google.protobuf.Empty) {} + rpc FetchIsoData(LeCisHandleMsg) returns (stream IsoPacket) {} + rpc FetchIsoEvents(google.protobuf.Empty) returns (stream LeIsoEventsMsg) {} + rpc SendIsoPacket(IsoPacket) returns (google.protobuf.Empty) {} +} + +message IsoPacket { + uint32 handle = 1; + bytes payload = 3; +} + +message LeSetCigParametersRequest { + uint32 cig_id = 1; + uint32 sdu_interval_m_to_s = 2; + uint32 sdu_interval_s_to_m = 3; + uint32 peripherals_clock_accuracy = 4; + uint32 packing = 5; + uint32 framing = 6; + uint32 max_transport_latency_m_to_s = 7; + uint32 max_transport_latency_s_to_m = 8; + uint32 cis_id = 9; + uint32 max_sdu_m_to_s = 10; + uint32 max_sdu_s_to_m = 11; + uint32 phy_m_to_s = 12; + uint32 phy_s_to_m = 13; + uint32 rtn_m_to_s = 14; + uint32 rtn_s_to_m = 15; +} + +message LeSetCigParametersTestRequest { + uint32 cig_id = 1; + uint32 sdu_interval_m_to_s = 2; + uint32 sdu_interval_s_to_m = 3; + uint32 ft_m_to_s = 4; + uint32 ft_s_to_m = 5; + uint32 iso_interval = 6; + uint32 peripherals_clock_accuracy = 7; + uint32 packing = 8; + uint32 framing = 9; + uint32 max_transport_latency_m_to_s = 10; + uint32 max_transport_latency_s_to_m = 11; + + message LeCisParametersTestConfig { + uint32 cis_id = 1; + uint32 nse = 2; + uint32 max_sdu_m_to_s = 3; + uint32 max_sdu_s_to_m = 4; + uint32 max_pdu_m_to_s = 5; + uint32 max_pdu_s_to_m = 6; + uint32 phy_m_to_s = 7; + uint32 phy_s_to_m = 8; + uint32 bn_m_to_s = 9; + uint32 bn_s_to_m = 10; + } + + repeated LeCisParametersTestConfig cis_configs = 12; +} + +message LeCreateCisRequest { + message HandlePair { + uint32 cis_handle = 1; + uint32 acl_handle = 2; + } + + repeated HandlePair handle_pair = 1; +} + +enum IsoMsgType { + ISO_PARAMETERS_SET_COMPLETE = 0; + ISO_CIS_ESTABLISHED = 1; +} + +message LeIsoEventsMsg { + IsoMsgType message_type = 1; + repeated uint32 cis_handle = 2; +} + +message LeCisHandleMsg { + uint32 handle = 1; +} diff --git a/gd/iso/internal/iso_manager_impl.cc b/gd/iso/internal/iso_manager_impl.cc index 8f17a1494..1ea385894 100644 --- a/gd/iso/internal/iso_manager_impl.cc +++ b/gd/iso/internal/iso_manager_impl.cc @@ -18,22 +18,63 @@ #include "iso_manager_impl.h" #include "common/bind.h" +#include "hci/hci_packets.h" #include "iso/iso_manager.h" +#include "os/handler.h" #include "os/log.h" +#include "packet/raw_builder.h" namespace bluetooth { namespace iso { namespace internal { +using bluetooth::hci::IsoBuilder; + IsoManagerImpl::IsoManagerImpl(os::Handler* iso_handler, hci::HciLayer* hci_layer, hci::Controller* controller) : iso_handler_(iso_handler), + hci_layer_(hci_layer), hci_le_iso_interface_(hci_layer->GetLeIsoInterface(iso_handler_->BindOn(this, &IsoManagerImpl::OnHciLeEvent))), - controller_(controller) {} + controller_(controller) { + hci_layer_->GetIsoQueueEnd()->RegisterDequeue( + iso_handler_, common::Bind(&IsoManagerImpl::OnIncomingPacket, common::Unretained(this))); + iso_enqueue_buffer_ = std::make_unique>(hci_layer_->GetIsoQueueEnd()); +} + +IsoManagerImpl::~IsoManagerImpl() { + hci_layer_->GetIsoQueueEnd()->UnregisterDequeue(); + iso_enqueue_buffer_ = nullptr; +} void IsoManagerImpl::OnHciLeEvent(hci::LeMetaEventView event) { hci::SubeventCode code = event.GetSubeventCode(); + if (code == hci::SubeventCode::CIS_ESTABLISHED) { + hci::LeCisEstablishedView le_cis_established_view = hci::LeCisEstablishedView::Create(event); + if (!le_cis_established_view.IsValid()) { + LOG_ERROR("Invalid LeCisEstablishedView packet received"); + return; + } + + cis_established_callback.Invoke(le_cis_established_view.GetConnectionHandle()); + return; + } else if (code == hci::SubeventCode::CIS_REQUEST) { + hci::LeCisRequestView le_cis_request_view = hci::LeCisRequestView::Create(event); + if (!le_cis_request_view.IsValid()) { + LOG_ERROR("Invalid LeCisRequestView packet received"); + return; + } + + hci_le_iso_interface_->EnqueueCommand( + hci::LeAcceptCisRequestBuilder::Create(le_cis_request_view.GetCisConnectionHandle()), + iso_handler_->BindOnce([](hci::CommandStatusView command_status) { + LOG_INFO("command_status=%hhu ", command_status.GetStatus()); + })); + + return; + } + LOG_ERROR("Unhandled HCI LE ISO event, code %s", hci::SubeventCodeText(code).c_str()); + ASSERT_LOG(false, "Unhandled HCI LE ISO event"); } void IsoManagerImpl::SetCigParameters( @@ -45,7 +86,8 @@ void IsoManagerImpl::SetCigParameters( hci::Enable framing, uint16_t max_transport_latency_m_to_s, uint16_t max_transport_latency_s_to_m, - const std::vector& cis_config) { + const std::vector& cis_configs, + SetCigParametersCallback command_complete_callback) { hci_le_iso_interface_->EnqueueCommand( hci::LeSetCigParametersBuilder::Create( cig_id, @@ -56,22 +98,149 @@ void IsoManagerImpl::SetCigParameters( framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, - cis_config), - iso_handler_->BindOnce(&IsoManagerImpl::SetCigParametersComplete, base::Unretained(this))); + cis_configs), + iso_handler_->BindOnce( + &IsoManagerImpl::SetCigParametersComplete, + common::Unretained(this), + cig_id, + cis_configs, + std::move(command_complete_callback))); } -void IsoManagerImpl::SetCigParametersComplete(hci::CommandCompleteView command_complete) { +void IsoManagerImpl::SetCigParametersComplete( + uint8_t cig_id, + const std::vector& cis_configs, + SetCigParametersCallback command_complete_callback, + hci::CommandCompleteView command_complete) { ASSERT(command_complete.IsValid()); hci::LeSetCigParametersCompleteView setCigParamsComplete = hci::LeSetCigParametersCompleteView::Create(command_complete); ASSERT(setCigParamsComplete.IsValid()); + + if (setCigParamsComplete.GetStatus() == hci::ErrorCode::SUCCESS) { + uint8_t cig_id_back_from_ctrl = setCigParamsComplete.GetCigId(); + auto conn_handles = setCigParamsComplete.GetConnectionHandle(); + + ASSERT(cig_id_back_from_ctrl == cig_id); + ASSERT(conn_handles.size() == cis_configs.size()); + + auto cis_it = cis_configs.begin(); + auto handle_it = conn_handles.begin(); + + std::vector handles; + while (cis_it != cis_configs.end()) { + iso_connections_.push_back({ + .cig_id = cig_id, + .cis_id = cis_it->cis_id_, + .connection_handle = *handle_it, + }); + + handles.push_back(*handle_it); + + cis_it++; + handle_it++; + } + + command_complete_callback.Invoke(handles); + } +} + +void IsoManagerImpl::SetCigParametersTest( + uint8_t cig_id, + uint32_t sdu_interval_m_to_s, + uint32_t sdu_interval_s_to_m, + uint8_t ft_m_to_s, + uint8_t ft_s_to_m, + uint16_t iso_interval, + hci::ClockAccuracy peripherals_clock_accuracy, + hci::Packing packing, + hci::Enable framing, + uint16_t max_transport_latency_m_to_s, + uint16_t max_transport_latency_s_to_m, + const std::vector& cis_test_configs, + SetCigParametersCallback command_complete_callback) { + hci_le_iso_interface_->EnqueueCommand( + hci::LeSetCigParametersTestBuilder::Create( + cig_id, + sdu_interval_m_to_s, + sdu_interval_s_to_m, + ft_m_to_s, + ft_s_to_m, + iso_interval, + peripherals_clock_accuracy, + packing, + framing, + cis_test_configs), + iso_handler_->BindOnce( + &IsoManagerImpl::SetCigParametersTestComplete, + common::Unretained(this), + cig_id, + cis_test_configs, + std::move(command_complete_callback))); +} + +void IsoManagerImpl::SetCigParametersTestComplete( + uint8_t cig_id, + const std::vector& cis_configs, + SetCigParametersCallback command_complete_callback, + hci::CommandCompleteView command_complete) { + ASSERT(command_complete.IsValid()); + + hci::LeSetCigParametersTestCompleteView setCigParamsComplete = + hci::LeSetCigParametersTestCompleteView::Create(command_complete); + ASSERT(setCigParamsComplete.IsValid()); + + if (setCigParamsComplete.GetStatus() == hci::ErrorCode::SUCCESS) { + uint8_t cig_id_back_from_ctrl = setCigParamsComplete.GetCigId(); + auto conn_handles = setCigParamsComplete.GetConnectionHandle(); + + ASSERT(cig_id_back_from_ctrl == cig_id); + ASSERT(conn_handles.size() == cis_configs.size()); + + auto cis_it = cis_configs.begin(); + auto handle_it = conn_handles.begin(); + + std::vector handles; + while (cis_it != cis_configs.end()) { + iso_connections_.push_back({ + .cig_id = cig_id, + .cis_id = cis_it->cis_id_, + .connection_handle = *handle_it, + }); + + handles.push_back(*handle_it); + + cis_it++; + handle_it++; + } + + command_complete_callback.Invoke(handles); + } +} + +void IsoManagerImpl::LeCreateCis(std::vector> cis_and_acl_handles) { + std::vector cis_configs; + + for (const auto& handle_pair : cis_and_acl_handles) { + hci::CreateCisConfig config; + config.cis_connection_handle_ = handle_pair.first; + config.acl_connection_handle_ = handle_pair.second; + cis_configs.push_back(config); + } + + hci_le_iso_interface_->EnqueueCommand( + hci::LeCreateCisBuilder::Create(cis_configs), iso_handler_->BindOnce([](hci::CommandStatusView command_status) { + LOG_INFO("command_status=%hhu ", command_status.GetStatus()); + })); } void IsoManagerImpl::RemoveCig(uint8_t cig_id) { + ASSERT(IsKnownCig(cig_id)); + hci_le_iso_interface_->EnqueueCommand( hci::LeRemoveCigBuilder::Create(cig_id), - iso_handler_->BindOnce(&IsoManagerImpl::RemoveCigComplete, base::Unretained(this))); + iso_handler_->BindOnce(&IsoManagerImpl::RemoveCigComplete, common::Unretained(this))); } void IsoManagerImpl::RemoveCigComplete(hci::CommandCompleteView command_complete) { @@ -81,6 +250,22 @@ void IsoManagerImpl::RemoveCigComplete(hci::CommandCompleteView command_complete ASSERT(removeCigComplete.IsValid()); } +void IsoManagerImpl::SendIsoPacket(uint16_t cis_handle, std::vector packet) { + auto builder = hci::IsoWithoutTimestampBuilder::Create( + cis_handle, + hci::IsoPacketBoundaryFlag::COMPLETE_SDU, + 0 /* sequence_number*/, + hci::IsoPacketStatusFlag::VALID, + std::make_unique(packet)); + LOG_INFO("%c%c", packet[0], packet[1]); + iso_enqueue_buffer_->Enqueue(std::move(builder), iso_handler_); +} + +void IsoManagerImpl::OnIncomingPacket() { + std::unique_ptr packet = hci_layer_->GetIsoQueueEnd()->TryDequeue(); + iso_data_callback.Invoke(std::move(packet)); +} + } // namespace internal } // namespace iso } // namespace bluetooth diff --git a/gd/iso/internal/iso_manager_impl.h b/gd/iso/internal/iso_manager_impl.h index 946c2ed05..b29702505 100644 --- a/gd/iso/internal/iso_manager_impl.h +++ b/gd/iso/internal/iso_manager_impl.h @@ -21,14 +21,34 @@ #include "hci/hci_packets.h" #include "os/handler.h" +#include + namespace bluetooth { namespace iso { +using SetCigParametersCallback = common::ContextualOnceCallback)>; +using CisEstablishedCallback = common::ContextualCallback; +using IsoDataCallback = common::ContextualCallback)>; namespace internal { +struct IsochronousConnection { + uint16_t connection_handle; + uint8_t cig_id; + uint8_t cis_id; +}; + class IsoManagerImpl { public: explicit IsoManagerImpl(os::Handler* iso_handler, hci::HciLayer* hci_layer, hci::Controller* controller); + ~IsoManagerImpl(); + + void RegisterIsoEstablishedCallback(CisEstablishedCallback cb) { + this->cis_established_callback = cb; + } + + void RegisterIsoDataCallback(IsoDataCallback cb) { + this->iso_data_callback = cb; + } void OnHciLeEvent(hci::LeMetaEventView event); @@ -41,15 +61,57 @@ class IsoManagerImpl { hci::Enable framing, uint16_t max_transport_latency_m_to_s, uint16_t max_transport_latency_s_to_m, - const std::vector& cis_config); - void SetCigParametersComplete(hci::CommandCompleteView command_complete); + const std::vector& cis_config, + SetCigParametersCallback command_complete_callback); + void SetCigParametersComplete( + uint8_t cig_id, + const std::vector& cis_configs, + SetCigParametersCallback command_complete_callback, + hci::CommandCompleteView command_complete); + + void SetCigParametersTest( + uint8_t cig_id, + uint32_t sdu_interval_m_to_s, + uint32_t sdu_interval_s_to_m, + uint8_t ft_m_to_s, + uint8_t ft_s_to_m, + uint16_t iso_interval, + hci::ClockAccuracy peripherals_clock_accuracy, + hci::Packing packing, + hci::Enable framing, + uint16_t max_transport_latency_m_to_s, + uint16_t max_transport_latency_s_to_m, + const std::vector& cis_config, + SetCigParametersCallback command_complete_callback); + void SetCigParametersTestComplete( + uint8_t cig_id, + const std::vector& cis_configs, + SetCigParametersCallback command_complete_callback, + hci::CommandCompleteView command_complete); + + void LeCreateCis(std::vector> cis_and_acl_handles); + void RemoveCig(uint8_t cig_id); void RemoveCigComplete(hci::CommandCompleteView command_complete); + void SendIsoPacket(uint16_t cis_handle, std::vector packet); + void OnIncomingPacket(); + + bool IsKnownCig(uint8_t cig_id) { + return find_if(iso_connections_.begin(), iso_connections_.end(), [cig_id](const IsochronousConnection& c) { + return c.cig_id == cig_id; + }) != iso_connections_.end(); + } + private: - os::Handler* iso_handler_ __attribute__((unused)); + os::Handler* iso_handler_; + hci::HciLayer* hci_layer_; hci::LeIsoInterface* hci_le_iso_interface_; + std::unique_ptr> iso_enqueue_buffer_; hci::Controller* controller_ __attribute__((unused)); + std::list iso_connections_; + CisEstablishedCallback cis_established_callback; + IsoDataCallback iso_data_callback; }; } // namespace internal } // namespace iso diff --git a/gd/iso/iso_manager.cc b/gd/iso/iso_manager.cc index 3d198c68a..c072bae56 100644 --- a/gd/iso/iso_manager.cc +++ b/gd/iso/iso_manager.cc @@ -22,6 +22,14 @@ namespace bluetooth { namespace iso { +void IsoManager::RegisterIsoEstablishedCallback(CisEstablishedCallback cb) { + iso_handler_->CallOn(iso_manager_impl_, &internal::IsoManagerImpl::RegisterIsoEstablishedCallback, cb); +} + +void IsoManager::RegisterIsoDataCallback(IsoDataCallback cb) { + iso_handler_->CallOn(iso_manager_impl_, &internal::IsoManagerImpl::RegisterIsoDataCallback, cb); +} + void IsoManager::SetCigParameters( uint8_t cig_id, uint32_t sdu_interval_m_to_s, @@ -31,7 +39,8 @@ void IsoManager::SetCigParameters( hci::Enable framing, uint16_t max_transport_latency_m_to_s, uint16_t max_transport_latency_s_to_m, - const std::vector& cis_config) { + std::vector cis_config, + SetCigParametersCallback command_complete_callback) { iso_handler_->CallOn( iso_manager_impl_, &internal::IsoManagerImpl::SetCigParameters, @@ -43,12 +52,53 @@ void IsoManager::SetCigParameters( framing, max_transport_latency_m_to_s, max_transport_latency_s_to_m, - cis_config); + cis_config, + std::move(command_complete_callback)); +} + +void IsoManager::SetCigParametersTest( + uint8_t cig_id, + uint32_t sdu_interval_m_to_s, + uint32_t sdu_interval_s_to_m, + uint8_t ft_m_to_s, + uint8_t ft_s_to_m, + uint16_t iso_interval, + hci::ClockAccuracy peripherals_clock_accuracy, + hci::Packing packing, + hci::Enable framing, + uint16_t max_transport_latency_m_to_s, + uint16_t max_transport_latency_s_to_m, + std::vector cis_config, + SetCigParametersCallback command_complete_callback) { + iso_handler_->CallOn( + iso_manager_impl_, + &internal::IsoManagerImpl::SetCigParametersTest, + cig_id, + sdu_interval_m_to_s, + sdu_interval_s_to_m, + ft_m_to_s, + ft_s_to_m, + iso_interval, + peripherals_clock_accuracy, + packing, + framing, + max_transport_latency_m_to_s, + max_transport_latency_s_to_m, + cis_config, + std::move(command_complete_callback)); +} + +void IsoManager::LeCreateCis(std::vector> cis_and_acl_handles) { + iso_handler_->CallOn(iso_manager_impl_, &internal::IsoManagerImpl::LeCreateCis, cis_and_acl_handles); } void IsoManager::RemoveCig(uint8_t cig_id) { iso_handler_->CallOn(iso_manager_impl_, &internal::IsoManagerImpl::RemoveCig, cig_id); } +void IsoManager::SendIsoPacket(uint16_t cis_handle, std::vector packet) { + iso_handler_->CallOn(iso_manager_impl_, &internal::IsoManagerImpl::SendIsoPacket, cis_handle, packet); +} + } // namespace iso } // namespace bluetooth diff --git a/gd/iso/iso_manager.h b/gd/iso/iso_manager.h index a396108b4..d38405c4e 100644 --- a/gd/iso/iso_manager.h +++ b/gd/iso/iso_manager.h @@ -23,9 +23,13 @@ #include "hci/address_with_type.h" #include "iso/internal/iso_manager_impl.h" +#include "os/handler.h" namespace bluetooth { namespace iso { +using SetCigParametersCallback = common::ContextualOnceCallback /* connectino handles*/)>; +using CisEstablishedCallback = common::ContextualCallback; +using IsoDataCallback = common::ContextualCallback)>; /** * Manages the iso attributes, pairing, bonding of devices, and the @@ -35,6 +39,9 @@ class IsoManager { public: friend class IsoModule; + void RegisterIsoEstablishedCallback(CisEstablishedCallback cb); + void RegisterIsoDataCallback(IsoDataCallback cb); + void SetCigParameters( uint8_t cig_id, uint32_t sdu_interval_m_to_s, @@ -44,9 +51,28 @@ class IsoManager { hci::Enable framing, uint16_t max_transport_latency_m_to_s, uint16_t max_transport_latency_s_to_m, - const std::vector& cis_config); + std::vector cis_config, + SetCigParametersCallback command_complete_callback); + void SetCigParametersTest( + uint8_t cig_id, + uint32_t sdu_interval_m_to_s, + uint32_t sdu_interval_s_to_m, + uint8_t ft_m_to_s, + uint8_t ft_s_to_m, + uint16_t iso_interval, + hci::ClockAccuracy peripherals_clock_accuracy, + hci::Packing packing, + hci::Enable framing, + uint16_t max_transport_latency_m_to_s, + uint16_t max_transport_latency_s_to_m, + std::vector cis_config, + SetCigParametersCallback command_complete_callback); + + void LeCreateCis(std::vector> cis_and_acl_handles); void RemoveCig(uint8_t cig_id); + void SendIsoPacket(uint16_t cis_handle, std::vector packet); + protected: IsoManager(os::Handler* iso_handler, internal::IsoManagerImpl* iso_manager_impl) : iso_handler_(iso_handler), iso_manager_impl_(iso_manager_impl) {} diff --git a/gd/iso/iso_module.cc b/gd/iso/iso_module.cc index fe5719655..367884181 100644 --- a/gd/iso/iso_module.cc +++ b/gd/iso/iso_module.cc @@ -24,7 +24,6 @@ #include "hci/acl_manager.h" #include "hci/hci_layer.h" #include "iso/iso_module.h" -#include "l2cap/le/l2cap_le_module.h" namespace bluetooth { namespace iso { -- 2.11.0