diff options
-rw-r--r-- | android/app/src/com/android/bluetooth/le_scan/AppScanStats.java | 14 | ||||
-rw-r--r-- | android/pandora/test/main.py | 2 | ||||
-rw-r--r-- | android/pandora/test/rfcomm_test.py | 110 | ||||
-rw-r--r-- | flags/sockets.aconfig | 10 | ||||
-rw-r--r-- | offload/leaudio/hci/proxy.rs | 30 | ||||
-rw-r--r-- | system/gd/hci/distance_measurement_manager_test.cc | 2 | ||||
-rw-r--r-- | system/pdl/hci/hci_packets.pdl | 4 | ||||
-rw-r--r-- | system/stack/rfcomm/port_api.cc | 4 | ||||
-rw-r--r-- | system/stack/rfcomm/rfc_l2cap_if.cc | 54 | ||||
-rw-r--r-- | system/test/README.md | 81 |
10 files changed, 190 insertions, 121 deletions
diff --git a/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java b/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java index 97e1f5ede5..b5090eabd7 100644 --- a/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java +++ b/android/app/src/com/android/bluetooth/le_scan/AppScanStats.java @@ -195,6 +195,7 @@ class AppScanStats { mTimeProvider = requireNonNull(timeProvider); } + @Nullable private synchronized LastScan getScanFromScannerId(int scannerId) { return mOngoingScans.get(scannerId); } @@ -530,7 +531,7 @@ class AppScanStats { convertScanType(getScanFromScannerId(scannerId)), BluetoothStatsLog.LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_SCAN_TIMEOUT, scanTimeoutMillis, - getScanFromScannerId(scannerId).getAttributionTag()); + getAttributionTagFromScannerId(scannerId)); } MetricsLogger.getInstance() .cacheCount(BluetoothProtoEnums.LE_SCAN_ABUSE_COUNT_SCAN_TIMEOUT, 1); @@ -546,7 +547,7 @@ class AppScanStats { convertScanType(getScanFromScannerId(scannerId)), BluetoothStatsLog.LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_HW_FILTER_NA, numOfFilterSupported, - getScanFromScannerId(scannerId).getAttributionTag()); + getAttributionTagFromScannerId(scannerId)); } MetricsLogger.getInstance() .cacheCount(BluetoothProtoEnums.LE_SCAN_ABUSE_COUNT_HW_FILTER_NOT_AVAILABLE, 1); @@ -563,7 +564,7 @@ class AppScanStats { BluetoothStatsLog .LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_TRACKING_HW_FILTER_NA, numOfTrackableAdv, - getScanFromScannerId(scannerId).getAttributionTag()); + getAttributionTagFromScannerId(scannerId)); } MetricsLogger.getInstance() .cacheCount( @@ -596,7 +597,7 @@ class AppScanStats { sRadioScanIntervalMs = scanIntervalMs; sIsRadioStarted = true; sRadioScanAppImportance = stats.mAppImportance; - sRadioScanAttributionTag = stats.getScanFromScannerId(scannerId).getAttributionTag(); + sRadioScanAttributionTag = stats.getAttributionTagFromScannerId(scannerId); } return true; } @@ -849,6 +850,11 @@ class AppScanStats { < LARGE_SCAN_TIME_GAP_MS); } + private String getAttributionTagFromScannerId(int scannerId) { + LastScan scan = getScanFromScannerId(scannerId); + return scan == null ? "" : scan.getAttributionTag(); + } + private static String filterToStringWithoutNullParam(ScanFilter filter) { StringBuilder filterString = new StringBuilder("BluetoothLeScanFilter ["); if (filter.getDeviceName() != null) { diff --git a/android/pandora/test/main.py b/android/pandora/test/main.py index d5c5da0bad..7e49321932 100644 --- a/android/pandora/test/main.py +++ b/android/pandora/test/main.py @@ -25,6 +25,7 @@ import avatar.cases.security_test import gatt_test import hap_test import hfpclient_test +import rfcomm_test import sdp_test from pairing import _test_class_list as _pairing_test_class_list @@ -81,6 +82,7 @@ _TEST_CLASSES_LIST = [ hap_test.HapTest, asha_test.AshaTest, hfpclient_test.HfpClientTest, + rfcomm_test.RfcommTest, ] + _pairing_test_class_list diff --git a/android/pandora/test/rfcomm_test.py b/android/pandora/test/rfcomm_test.py new file mode 100644 index 0000000000..4409b02e25 --- /dev/null +++ b/android/pandora/test/rfcomm_test.py @@ -0,0 +1,110 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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 asyncio +import avatar +import grpc +import logging + +from avatar import PandoraDevices +from avatar.aio import asynchronous +from avatar.pandora_client import BumblePandoraClient, PandoraClient +from bumble.rfcomm import Server +from bumble_experimental.rfcomm import RFCOMMService +from mobly import base_test, test_runner +from mobly.asserts import assert_equal # type: ignore +from mobly.asserts import assert_in # type: ignore +from mobly.asserts import assert_is_not_none # type: ignore +from mobly.asserts import fail # type: ignore +from pandora_experimental.rfcomm_grpc_aio import RFCOMM +from pandora_experimental.rfcomm_pb2 import ( + AcceptConnectionRequest, + RxRequest, + StartServerRequest, + StopServerRequest, + TxRequest, +) +from typing import Optional, Tuple + +SERIAL_PORT_UUID = "00001101-0000-1000-8000-00805F9B34FB" +TEST_SERVER_NAME = "RFCOMM-Server" + + +class RfcommTest(base_test.BaseTestClass): + devices: Optional[PandoraDevices] = None + dut: PandoraClient + ref: BumblePandoraClient + + def setup_class(self) -> None: + self.devices = PandoraDevices(self) + self.dut, ref, *_ = self.devices + assert isinstance(ref, BumblePandoraClient) + self.ref = ref + # Enable BR/EDR mode and SSP for Bumble devices. + self.ref.config.setdefault('classic_enabled', True) + self.ref.config.setdefault('classic_ssp_enabled', True) + self.ref.config.setdefault( + 'server', + { + 'io_capability': 'no_output_no_input', + }, + ) + + def teardown_class(self) -> None: + if self.devices: + self.devices.stop_all() + + @avatar.asynchronous + async def setup_test(self) -> None: + await asyncio.gather(self.dut.reset(), self.ref.reset()) + + ref_server = Server(self.ref.device) + self.ref.rfcomm = RFCOMMService(self.ref.device, ref_server) + self.dut.rfcomm = RFCOMM(channel=self.dut.aio.channel) + + @avatar.asynchronous + async def test_client_connect_and_exchange_data(self) -> None: + # dut is client, ref is server + context = grpc.ServicerContext + server = await self.ref.rfcomm.StartServer(StartServerRequest(name=TEST_SERVER_NAME, uuid=SERIAL_PORT_UUID), + context=context) + # Convert StartServerResponse to its server + server = server.server + rfc_dut_ref, rfc_ref_dut = await asyncio.gather( + self.dut.rfcomm.ConnectToServer(address=self.ref.address, uuid=SERIAL_PORT_UUID), + self.ref.rfcomm.AcceptConnection(request=AcceptConnectionRequest(server=server), context=context)) + # Convert Responses to their corresponding RfcommConnection + rfc_dut_ref = rfc_dut_ref.connection + rfc_ref_dut = rfc_ref_dut.connection + + # Transmit data + tx_data = b'Data from dut to ref' + await self.dut.rfcomm.Send(data=tx_data, connection=rfc_dut_ref) + ref_receive = await self.ref.rfcomm.Receive(request=RxRequest(connection=rfc_ref_dut), context=context) + assert_equal(ref_receive.data, tx_data) + + # Receive data + rx_data = b'Data from ref to dut' + await self.ref.rfcomm.Send(request=TxRequest(connection=rfc_ref_dut, data=rx_data), context=context) + dut_receive = await self.dut.rfcomm.Receive(connection=rfc_dut_ref) + assert_equal(dut_receive.data.rstrip(b'\x00'), rx_data) + + # Disconnect (from dut) + await self.dut.rfcomm.Disconnect(connection=rfc_dut_ref) + await self.ref.rfcomm.StopServer(request=StopServerRequest(server=server), context=context) + + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + test_runner.main() # type: ignore diff --git a/flags/sockets.aconfig b/flags/sockets.aconfig index 2eb23b1fcb..6b31e06705 100644 --- a/flags/sockets.aconfig +++ b/flags/sockets.aconfig @@ -92,3 +92,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "fix_lecoc_socket_available" + namespace: "bluetooth" + description: "Fix Bluetooth Socket available API for LECOC socket" + bug: "402536099" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/offload/leaudio/hci/proxy.rs b/offload/leaudio/hci/proxy.rs index 933389d315..adc2dbd34d 100644 --- a/offload/leaudio/hci/proxy.rs +++ b/offload/leaudio/hci/proxy.rs @@ -195,10 +195,16 @@ impl Module for LeAudioModule { ); } - Ok(Command::LeSetupIsoDataPath(ref c)) if c.data_path_id == DATA_PATH_ID_SOFTWARE => { + Ok(Command::LeSetupIsoDataPath(ref c)) if c.data_path_id == DATA_PATH_ID_SOFTWARE => 'command: { assert_eq!(c.data_path_direction, hci::LeDataPathDirection::Input); let mut state = self.state.lock().unwrap(); - let stream = state.stream.get_mut(&c.connection_handle).unwrap(); + let Some(stream) = state.stream.get_mut(&c.connection_handle) else { + log::warn!( + "Setup ISO Data Path on non existing BIS/CIS handle: 0x{:03x}", + c.connection_handle + ); + break 'command; + }; stream.state = StreamState::Enabling; // Phase 1 limitation: The controller does not implement HCI Link Feedback event, @@ -209,6 +215,16 @@ impl Module for LeAudioModule { return; } + Ok(Command::LeRemoveIsoDataPath(ref c)) => { + let mut state = self.state.lock().unwrap(); + if state.stream.get_mut(&c.connection_handle).is_none() { + log::warn!( + "Remove ISO Data Path on non existing BIS/CIS handle: 0x{:03x}", + c.connection_handle + ); + } + } + _ => (), } @@ -250,7 +266,9 @@ impl Module for LeAudioModule { ReturnParameters::LeSetupIsoDataPath(ref ret) => 'event: { let mut state = self.state.lock().unwrap(); - let stream = state.stream.get_mut(&ret.connection_handle).unwrap(); + let Some(stream) = state.stream.get_mut(&ret.connection_handle) else { + break 'event; + }; stream.state = if stream.state == StreamState::Enabling && ret.status == Status::Success { StreamState::Enabled @@ -278,9 +296,11 @@ impl Module for LeAudioModule { ); } - ReturnParameters::LeRemoveIsoDataPath(ref ret) if ret.status == Status::Success => { + ReturnParameters::LeRemoveIsoDataPath(ref ret) if ret.status == Status::Success => 'event: { let mut state = self.state.lock().unwrap(); - let stream = state.stream.get_mut(&ret.connection_handle).unwrap(); + let Some(stream) = state.stream.get_mut(&ret.connection_handle) else { + break 'event; + }; if stream.state == StreamState::Enabled { Service::stop_stream(ret.connection_handle); } diff --git a/system/gd/hci/distance_measurement_manager_test.cc b/system/gd/hci/distance_measurement_manager_test.cc index d3b7706ad0..3f6e16ba99 100644 --- a/system/gd/hci/distance_measurement_manager_test.cc +++ b/system/gd/hci/distance_measurement_manager_test.cc @@ -78,7 +78,7 @@ struct CsReadCapabilitiesCompleteEvent { CsOptionalNadmSoundingCapability nadm_sounding_capability = { /*normalized_attack_detector_metric=*/1}; CsOptionalNadmRandomCapability nadm_random_capability = {/*normalized_attack_detector_metric=*/1}; - CsOptionalCsSyncPhysSupported cs_sync_phys_supported = {/*le_2m_phy=*/1}; + CsOptionalCsSyncPhysSupported cs_sync_phys_supported = {/*le_2m_phy=*/1, /*le_2m_2bt_phy=*/0}; CsOptionalSubfeaturesSupported subfeatures_supported = {/*no_frequency_actuation_error=*/1, /*channel_selection_algorithm=*/1, /*phase_based_ranging=*/1}; diff --git a/system/pdl/hci/hci_packets.pdl b/system/pdl/hci/hci_packets.pdl index fcdd255b99..838b1fa586 100644 --- a/system/pdl/hci/hci_packets.pdl +++ b/system/pdl/hci/hci_packets.pdl @@ -4949,7 +4949,8 @@ struct CsOptionalNadmRandomCapability { struct CsOptionalCsSyncPhysSupported { le_2m_phy : 1, - _reserved_ : 7, + le_2m_2bt_phy : 1, + _reserved_ : 6, } struct CsOptionalSubfeaturesSupported { @@ -5151,6 +5152,7 @@ enum CsConfigRttType : 8 { enum CsSyncPhy : 8 { LE_1M_PHY = 0x01, LE_2M_PHY = 0x02, + LE_2M_2BT_PHY = 0x03, } enum CsChannelSelectionType : 8 { diff --git a/system/stack/rfcomm/port_api.cc b/system/stack/rfcomm/port_api.cc index a78f3ea890..6ab7cdccea 100644 --- a/system/stack/rfcomm/port_api.cc +++ b/system/stack/rfcomm/port_api.cc @@ -1229,7 +1229,9 @@ int PORT_GetChannelInfo(uint16_t handle, uint16_t* local_mtu, uint16_t* remote_m return PORT_NOT_OPENED; } - if (p_port->line_status) { + if (p_port->rfc.p_mcb == nullptr || p_port->line_status) { + log::warn("PORT_LINE_ERR - p_port->rfc.p_mcb == nullptr:{} p_port->line_status:{}", + (p_port->rfc.p_mcb == nullptr) ? "T" : "F", p_port->line_status); return PORT_LINE_ERR; } diff --git a/system/stack/rfcomm/rfc_l2cap_if.cc b/system/stack/rfcomm/rfc_l2cap_if.cc index 5b7fc00185..10826d235a 100644 --- a/system/stack/rfcomm/rfc_l2cap_if.cc +++ b/system/stack/rfcomm/rfc_l2cap_if.cc @@ -91,28 +91,26 @@ void rfcomm_l2cap_if_init(void) { void RFCOMM_ConnectInd(const RawAddress& bd_addr, uint16_t lcid, uint16_t /* psm */, uint8_t id) { tRFC_MCB* p_mcb = rfc_alloc_multiplexer_channel(bd_addr, false); - if ((p_mcb) && (p_mcb->state != RFC_MX_STATE_IDLE)) { - /* if this is collision case */ - if ((p_mcb->is_initiator) && (p_mcb->state == RFC_MX_STATE_WAIT_CONN_CNF)) { - p_mcb->pending_lcid = lcid; - - /* wait random timeout (2 - 12) to resolve collision */ - /* if peer gives up then local device rejects incoming connection and - * continues as initiator */ - /* if timeout, local device disconnects outgoing connection and continues - * as acceptor */ - log::verbose( - "RFCOMM_ConnectInd start timer for collision, initiator's " - "LCID(0x{:x}), acceptor's LCID(0x{:x})", - p_mcb->lcid, p_mcb->pending_lcid); - - rfc_timer_start(p_mcb, (uint16_t)(bluetooth::common::time_get_os_boottime_ms() % 10 + 2)); - return; - } else { - /* we cannot accept connection request from peer at this state */ - /* don't update lcid */ - p_mcb = nullptr; - } + if (p_mcb != nullptr && p_mcb->is_initiator && p_mcb->state == RFC_MX_STATE_WAIT_CONN_CNF) { + p_mcb->pending_lcid = lcid; + + /* wait random timeout (2 - 12) to resolve collision */ + /* if peer gives up then local device rejects incoming connection and + * continues as initiator */ + /* if timeout, local device disconnects outgoing connection and continues + * as acceptor */ + log::verbose( + "RFCOMM_ConnectInd start timer for collision, initiator's " + "LCID(0x{:x}), acceptor's LCID(0x{:x})", + p_mcb->lcid, p_mcb->pending_lcid); + + rfc_timer_start(p_mcb, (uint16_t)(bluetooth::common::time_get_os_boottime_ms() % 10 + 2)); + return; + } + if (p_mcb != nullptr && p_mcb->is_initiator && p_mcb->state != RFC_MX_STATE_IDLE) { + /* we cannot accept connection request from peer at this state */ + /* don't update lcid */ + p_mcb = nullptr; } else { /* store mcb even if null */ rfc_save_lcid_mcb(p_mcb, lcid); @@ -141,7 +139,7 @@ void RFCOMM_ConnectInd(const RawAddress& bd_addr, uint16_t lcid, uint16_t /* psm void RFCOMM_ConnectCnf(uint16_t lcid, tL2CAP_CONN result) { tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::error("RFCOMM_ConnectCnf LCID:0x{:x}", lcid); return; } @@ -188,7 +186,7 @@ void RFCOMM_ConfigInd(uint16_t lcid, tL2CAP_CFG_INFO* p_cfg) { tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::error("RFCOMM_ConfigInd LCID:0x{:x}", lcid); for (auto& [cid, mcb] : rfc_lcid_mcb) { if (mcb != nullptr && mcb->pending_lcid == lcid) { @@ -218,7 +216,7 @@ void RFCOMM_ConfigCnf(uint16_t lcid, uint16_t /* initiator */, tL2CAP_CFG_INFO* tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::error("RFCOMM_ConfigCnf no MCB LCID:0x{:x}", lcid); return; } @@ -237,7 +235,7 @@ void RFCOMM_ConfigCnf(uint16_t lcid, uint16_t /* initiator */, tL2CAP_CFG_INFO* void RFCOMM_DisconnectInd(uint16_t lcid, bool is_conf_needed) { log::verbose("lcid:0x{:x}, is_conf_needed:{}", lcid, is_conf_needed); tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::warn("no mcb for lcid 0x{:x}", lcid); return; } @@ -257,7 +255,7 @@ void RFCOMM_DisconnectInd(uint16_t lcid, bool is_conf_needed) { void RFCOMM_BufDataInd(uint16_t lcid, BT_HDR* p_buf) { tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::warn("Cannot find RFCOMM multiplexer for lcid 0x{:x}", lcid); osi_free(p_buf); return; @@ -351,7 +349,7 @@ void RFCOMM_BufDataInd(uint16_t lcid, BT_HDR* p_buf) { void RFCOMM_CongestionStatusInd(uint16_t lcid, bool is_congested) { tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); - if (!p_mcb) { + if (p_mcb == nullptr) { log::error("RFCOMM_CongestionStatusInd dropped LCID:0x{:x}", lcid); return; } else { diff --git a/system/test/README.md b/system/test/README.md deleted file mode 100644 index 1f43e952ef..0000000000 --- a/system/test/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Fluoride Bluetooth Tests - -This document refers to the tests in the packages/modules/Bluetooth/system/test directory. - -The tests are designed to be run when the Android runtime is not running. From a terminal, run: - -## Before you run tests -```sh -adb shell stop -``` - -## After you're done -```sh -adb shell start -``` - -## Running tests -Then see what options the test script provides: - -```sh -./run_unit_tests.sh --help -``` - -But for the impatient, run specific groups of tests like this: - -```sh -./run_unit_tests.sh net_test_bluetooth -``` - -a single test: - -```sh -./run_unit_tests.sh net_test_bluetooth.BluetoothTest.AdapterRepeatedEnableDisable -``` - -## Sample Output - -packages/modules/Bluetooth/system/test$ ./run_unit_tests.sh net_test_bluetooth ---- net_test_bluetooth --- -pushing... -/tbd/aosp-master/out/target/product/bullhead/data/nativetest/n...st_bluetooth: 1 file pushed. 9.2 MB/s (211832 bytes in 0.022s) -running... - -Running main() from gtest_main.cc -[==========] Running 11 tests from 2 test cases. -[----------] Global test environment set-up. -[----------] 6 tests from BluetoothTest -[ RUN ] BluetoothTest.AdapterEnableDisable -[ OK ] BluetoothTest.AdapterEnableDisable (2538 ms) -[ RUN ] BluetoothTest.AdapterRepeatedEnableDisable -[ OK ] BluetoothTest.AdapterRepeatedEnableDisable (11384 ms) -[ RUN ] BluetoothTest.AdapterSetGetName -[ OK ] BluetoothTest.AdapterSetGetName (2378 ms) -[ RUN ] BluetoothTest.AdapterStartDiscovery -[ OK ] BluetoothTest.AdapterStartDiscovery (2397 ms) -[ RUN ] BluetoothTest.AdapterCancelDiscovery -[ OK ] BluetoothTest.AdapterCancelDiscovery (2401 ms) -[ RUN ] BluetoothTest.AdapterDisableDuringBonding -[ OK ] BluetoothTest.AdapterDisableDuringBonding (11689 ms) -[----------] 6 tests from BluetoothTest (32789 ms total) - -[----------] 5 tests from GattTest -[ RUN ] GattTest.GattClientRegister -[ OK ] GattTest.GattClientRegister (2370 ms) -[ RUN ] GattTest.GattClientScanRemoteDevice -[ OK ] GattTest.GattClientScanRemoteDevice (2273 ms) -[ RUN ] GattTest.GattClientAdvertise -[ OK ] GattTest.GattClientAdvertise (2236 ms) -[ RUN ] GattTest.GattServerRegister -[ OK ] GattTest.GattServerRegister (2391 ms) -[ RUN ] GattTest.GattServerBuild -[ OK ] GattTest.GattServerBuild (2435 ms) -[----------] 5 tests from GattTest (11706 ms total) - -[----------] Global test environment tear-down -[==========] 11 tests from 2 test cases ran. (44495 ms total) -[ PASSED ] 11 tests. - -## Troubleshooting: Your phone is bricked! -Probably not. See [After you're done](#After-you're-done) - |