* [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP
@ 2026-09-09 19:22 Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST Luiz Augusto von Dentz
` (12 more replies)
0 siblings, 13 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:22 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds functional tests for A2DP and BAP, driven through
bluetoothctl, together with the endpoint registration scripts they use
and the documentation describing what each of them does.
The test documentation is reworked to describe every case as setup,
steps, expected outcome and notes, so a test can be reproduced and
reviewed without reading its source, and the reason behind the way it
is written is not lost. The setup of each case includes a topology
diagram showing how many hosts are used and the role each of them
takes. Tests of a profile need more context than the core ones, so they
are documented separately, in doc/functional-<profile>.rst.
The cases added are:
- A2DP: a transport is created on both sides once the stream is
configured, and the central can acquire it.
- BAP unicast: the initiator configures the remote PAC Sink endpoint,
a transport is created per audio location, and acquiring all of them
establishes the CIS. All the transports of a CIG have to be acquired,
as the CIS are only created once every CIS of the CIG is ready.
- BAP broadcast: a Broadcast Sink scans, syncs to a Broadcast Source
on its own and acquires a transport, for a plain broadcast and for a
Public Broadcast Profile one.
- BAP broadcast assistant: a Broadcast Assistant colocated with a
Broadcast Source shares its own broadcast with a Scan Delegator,
which syncs using PAST. The topology where the assistant relays a
source it scanned, which does not use PAST, needs the assistant on a
third host and can be added later.
Writing the tests exposed two issues, which are fixed here as well:
- Registering an A2DP endpoint prompted for Locations, Supported
Context, Context, CIG and CIS, which are LE Audio specific and have
no meaning for A2DP. Answering Locations with a single location also
pushes an LC3_CHAN_COUNT LTV into the capabilities, corrupting the
SBC capabilities.
- A Broadcast Sink established a PA sync to read the BASE, closed it,
and established a new one to sync to the BIG, delaying the start of
the stream. The sync is now reused, but only while there is nothing
else to discover, and it is released once another Broadcast Source
is probed or after a grace timeout.
The broadcast tests also depend on the following kernel fix, without
which the BIS connections are not notified to the listening socket and
the transport acquire never completes:
Bluetooth: ISO: set BT_LISTEN before requesting a BIG sync
Note that the broadcast tests are still occasionally slow: the first
LE BIG Create Sync may expire after 20 seconds and only the retry
succeeds, which is being looked at separately.
Luiz Augusto von Dentz (12):
build: add doc/test-functional.rst to EXTRA_DIST
doc: describe the functional test cases
client: do not prompt for LE Audio settings on A2DP endpoints
client: add A2DP endpoint registration scripts
test: functional: add A2DP tests
client: rename media endpoint scripts to include the codec
client: add BAP endpoint registration scripts
doc: bluetoothctl: document init script option and scripts
test: functional: add BAP unicast tests
test: functional: add BAP broadcast tests
test: functional: add BAP broadcast assistant test
bap: reuse the PA sync established to discover a Broadcast Source
Makefile.am | 6 +
client/player.c | 15 +-
client/scripts/a2dp-sink-sbc.bt | 19 ++
client/scripts/a2dp-source-sbc.bt | 24 +++
client/scripts/bap-sink-lc3.bt | 32 +++
client/scripts/bap-source-lc3.bt | 34 +++
doc/bluetoothctl.rst | 105 +++++++++
doc/functional-a2dp.rst | 99 +++++++++
doc/functional-avrcp.rst | 64 ++++++
doc/functional-bap.rst | 290 +++++++++++++++++++++++++
doc/functional-obex.rst | 90 ++++++++
doc/functional-testing.rst | 340 ++++++++++++++++++++++++++++++
profiles/audio/bap.c | 115 +++++++++-
test/functional/test_a2dp.py | 144 +++++++++++++
test/functional/test_bap.py | 318 ++++++++++++++++++++++++++++
15 files changed, 1685 insertions(+), 10 deletions(-)
create mode 100644 client/scripts/a2dp-sink-sbc.bt
create mode 100644 client/scripts/a2dp-source-sbc.bt
create mode 100644 client/scripts/bap-sink-lc3.bt
create mode 100644 client/scripts/bap-source-lc3.bt
create mode 100644 doc/functional-a2dp.rst
create mode 100644 doc/functional-avrcp.rst
create mode 100644 doc/functional-bap.rst
create mode 100644 doc/functional-obex.rst
create mode 100644 doc/functional-testing.rst
create mode 100644 test/functional/test_a2dp.py
create mode 100644 test/functional/test_bap.py
--
2.55.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
@ 2026-09-09 19:22 ` Luiz Augusto von Dentz
2026-09-10 18:28 ` Add functional tests for A2DP and BAP bluez.test.bot
2026-09-09 19:22 ` [PATCH BlueZ v1 02/12] doc: describe the functional test cases Luiz Augusto von Dentz
` (11 subsequent siblings)
12 siblings, 1 reply; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:22 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The functional testing documentation was not distributed with the
tarball, unlike the other documents in doc/.
Assisted-by: opencode:claude-opus-5
---
Makefile.am | 1 +
1 file changed, 1 insertion(+)
diff --git a/Makefile.am b/Makefile.am
index 3840bdbab17c..7895c3b2ab73 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -496,6 +496,7 @@ EXTRA_DIST += SECURITY.md
EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/test-coverage.txt \
doc/test-runner.rst \
+ doc/test-functional.rst \
doc/settings-storage.txt
EXTRA_DIST += doc/hci-protocol.rst doc/mgmt-protocol.rst \
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 02/12] doc: describe the functional test cases
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST Luiz Augusto von Dentz
@ 2026-09-09 19:22 ` Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 03/12] client: do not prompt for LE Audio settings on A2DP endpoints Luiz Augusto von Dentz
` (10 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:22 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add doc/functional-testing.rst describing the test cases under
test/functional as setup, steps, expected outcome and notes, so a test
can be reproduced and reviewed without reading its source, and the
reason behind the way it is written is not lost. The setup of each test
includes a topology diagram showing how many hosts are used and the
role each of them takes.
It also documents the pytest markers (vm, sa, tester) and the
convention of naming security advisory regression tests after their
GHSA id.
Tests for a specific profile need more context than the core ones, so
they are documented separately, in doc/functional-<profile>.rst.
Assisted-by: opencode:claude-opus-5
---
Makefile.am | 3 +
doc/functional-avrcp.rst | 64 +++++++
doc/functional-obex.rst | 90 ++++++++++
doc/functional-testing.rst | 338 +++++++++++++++++++++++++++++++++++++
4 files changed, 495 insertions(+)
create mode 100644 doc/functional-avrcp.rst
create mode 100644 doc/functional-obex.rst
create mode 100644 doc/functional-testing.rst
diff --git a/Makefile.am b/Makefile.am
index 7895c3b2ab73..afa213cf0b8c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -497,6 +497,9 @@ EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/test-coverage.txt \
doc/test-runner.rst \
doc/test-functional.rst \
+ doc/functional-testing.rst \
+ doc/functional-avrcp.rst \
+ doc/functional-obex.rst \
doc/settings-storage.txt
EXTRA_DIST += doc/hci-protocol.rst doc/mgmt-protocol.rst \
diff --git a/doc/functional-avrcp.rst b/doc/functional-avrcp.rst
new file mode 100644
index 000000000000..f77e2ed1d4ce
--- /dev/null
+++ b/doc/functional-avrcp.rst
@@ -0,0 +1,64 @@
+================
+functional-avrcp
+================
+
+DESCRIPTION
+===========
+
+AVRCP functional tests, `test/functional/test_avrcp.py`. See
+**functional-testing(7)** for the conventions used here, and
+**test-functional(1)** for how to run the suite.
+
+SETUP
+=====
+
+Two hosts, connected over BR/EDR:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | BR/EDR | host1 |
+ | victim | --------------> | attacker |
+ | bluetoothd | | bluetoothd -P avrcp |
+ | AVRCP Controller | AVCTP PSM 0x17 | malicious AVRCP Target |
+ | | <============== | (org.bluez.Profile1) |
+ +------------------------+ +------------------------+
+
+ --> connection is initiated by ==> malicious response is sent by
+
+TEST CASES
+==========
+
+test_avrcp_GHSA_m2vx_pw5f_rc8v
+------------------------------
+
+:Setup: Two hosts paired over BR/EDR. host1 runs `bluetoothd` with the
+ `avrcp` plugin and registers a malicious AVRCP Target through
+ ``org.bluez.ProfileManager1``: a server role profile on the AVCTP
+ PSM (0x17) with its own SDP record, which receives the accepted
+ AVCTP file descriptor through ``Profile1.NewConnection``.
+
+:Steps:
+ 1. host0 connects the AVRCP Controller UUID
+ (``0000110c-0000-1000-8000-00805f9b34fb``) with
+ ``org.bluez.Device1.ConnectProfile``.
+ 2. The AVRCP Target answers ``GetCapabilities``, and answers
+ ``ListPlayerAttributes`` with an attribute count of 255.
+ 3. host0 calls ``org.bluez.Device1.Disconnect``.
+
+:Expected:
+ 1. ``ConnectProfile`` replies.
+ 2. The target reports that it answered
+ ``ListPlayerAttributes``.
+ 3. `bluetoothd` on host0 has not crashed and still answers D-Bus,
+ so ``Disconnect`` replies.
+
+:Notes: Regression test for NN-2026-0145. The response is parsed by
+ ``avrcp_list_player_attributes_rsp()``
+ (`profiles/audio/avrcp.c`), which collects the attributes into a
+ buffer of ``AVRCP_ATTRIBUTE_LAST`` bytes without bounding the
+ count, and then passes that count to
+ ``avrcp_get_current_player_value()``, which copies it into a
+ similarly sized buffer. With 255 valid attributes both overflow.
+
+ Marked ``sa``.
diff --git a/doc/functional-obex.rst b/doc/functional-obex.rst
new file mode 100644
index 000000000000..6cca30978b42
--- /dev/null
+++ b/doc/functional-obex.rst
@@ -0,0 +1,90 @@
+===============
+functional-obex
+===============
+
+DESCRIPTION
+===========
+
+OBEX functional tests, `test/functional/test_obex.py`. See
+**functional-testing(7)** for the conventions used here, and
+**test-functional(1)** for how to run the suite.
+
+SETUP
+=====
+
+Two hosts, connected over BR/EDR:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | BR/EDR | host1 |
+ | FTP client | --------------> | FTP server |
+ | bluetoothd, obexd | | bluetoothd, obexd |
+ | org.bluez.obex client, | OBEX FTP | OBEX agent, |
+ | or obexctl | <============== | files in /run/obex |
+ +------------------------+ +------------------------+
+
+ --> connection is initiated by ==> files are transferred towards
+
+Two hosts paired over BR/EDR, both running `obexd`:
+
+host0
+ Acts as File Transfer client, through the **org.bluez.obex** API
+ or through **obexctl(1)**.
+
+host1
+ Acts as server, with an OBEX agent registered, and serves the
+ files in ``/run/obex``.
+
+The session is created with
+``org.bluez.obex.Client1.CreateSession`` using the ``ftp`` target,
+which the agent of host1 has to authorize.
+
+TEST CASES
+==========
+
+test_obex_ftp_list
+------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Create the FTP session from host0 and authorize it on host1.
+ 2. Write a file named ``test`` with 4 bytes of content on host1.
+ 3. host0 calls
+ ``org.bluez.obex.FileTransfer1.ListFolder``.
+
+:Expected:
+ 1. host1 receives ``org.bluez.Agent1.AuthorizeService`` for the
+ FTP UUID, and ``CreateSession`` replies.
+ 2. ``ListFolder`` returns a single entry, with ``Type`` ``file``,
+ ``Name`` ``test`` and ``Size`` 4.
+
+test_obex_ftp_get
+-----------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Write a file named ``test`` with the content ``1234`` on host1.
+ 2. host0 calls ``org.bluez.obex.FileTransfer1.GetFile``.
+
+:Expected:
+ 1. The transfer object reaches ``Status`` ``complete``, tracked
+ through ``PropertiesChanged`` on the
+ ``org.bluez.obex.Transfer1`` object.
+ 2. The received file has the content ``1234``.
+
+test_obexctl_list
+-----------------
+
+:Setup: As above, with the client driven through **obexctl(1)**.
+
+:Steps:
+ 1. host0: ``connect <host1 bdaddr> <FTP UUID>``.
+ 2. host1 authorizes the service.
+ 3. host0: ``select <session>`` then ``ls``.
+
+:Expected:
+ 1. ``Connection successful``.
+ 2. ``ls`` prints ``Type: file``, ``Name: test`` and ``Size: 4``.
diff --git a/doc/functional-testing.rst b/doc/functional-testing.rst
new file mode 100644
index 000000000000..29436d4454c0
--- /dev/null
+++ b/doc/functional-testing.rst
@@ -0,0 +1,338 @@
+==================
+functional-testing
+==================
+
+DESCRIPTION
+===========
+
+This document describes the test cases run by **test-functional(1)**,
+i.e. the test modules under `test/functional`. For how to build,
+configure and run the suite, see **test-functional(1)**.
+
+This document covers the core test cases. Tests for a specific profile
+are documented separately:
+
+- **functional-avrcp(7)**: `test/functional/test_avrcp.py`
+- **functional-obex(7)**: `test/functional/test_obex.py`
+
+Each test case is described as:
+
+:Setup: The hosts, the plugins running on them and their
+ configuration, with a topology diagram showing how many hosts are
+ used and the role each of them takes.
+:Steps: The actions the test performs, in order.
+:Expected: What has to be observed for the test to pass.
+:Notes: Caveats, and why the test is written the way it is.
+
+In the topology diagrams, ``-->`` points at the host that accepts the
+connection, and ``==>`` at the host the data flows towards.
+
+MARKERS
+=======
+
+Markers are defined in `test/pytest.ini` and can be selected with
+``-m``:
+
+``vm``
+ Test requires a VM image (``--kernel``). Skipped if none is
+ available.
+
+``sa``
+ Security advisory regression test. Added automatically to tests
+ whose name matches ``_GHSA_xxxx_xxxx_xxxx``.
+
+``tester``
+ Kernel testers. These exercise the kernel rather than BlueZ
+ userspace and are excluded from ``make check-functional``.
+
+test_agent.py
+=============
+
+Pairing over D-Bus, driven directly through the **org.bluez** API using
+the `Agent` plugin on both hosts.
+
+test_agent_pair_bredr[accept]
+-----------------------------
+
+:Setup: Two hosts, each running `bluetoothd` with an agent registered
+ on D-Bus.
+
+ .. code-block::
+
+ +--------------------+ +--------------------+
+ | host0 | BR/EDR | host1 |
+ | bluetoothd, agent | --------------> | bluetoothd, agent |
+ | discovers, pairs | | pairable, |
+ | | | discoverable |
+ +--------------------+ +--------------------+
+
+:Steps:
+ 1. host0 calls ``org.bluez.Adapter1.StartDiscovery``.
+ 2. host1 sets ``Pairable`` and ``Discoverable`` to true.
+ 3. Wait until host0 has a device object for host1.
+ 4. host0 calls ``org.bluez.Device1.Pair``.
+ 5. Both agents reply to ``org.bluez.Agent1.RequestConfirmation``.
+
+:Expected:
+ 1. ``StartDiscovery`` replies.
+ 2. host0 discovers host1.
+ 3. Both agents receive ``RequestConfirmation`` with the *same*
+ passkey.
+ 4. ``org.bluez.Device1.Pair`` replies successfully.
+
+:Notes: This test is also used as the ``paired_hosts_bredr`` fixture
+ (see `test/functional/conftest.py`), which other tests reuse to
+ get two already paired hosts.
+
+test_agent_pair_bredr[reject]
+-----------------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Pair as above, up to the confirmation.
+ 2. host0 accepts the confirmation, host1 replies with an error.
+
+:Expected: ``org.bluez.Device1.Pair`` returns an error.
+
+test_bluetoothctl.py
+====================
+
+End to end tests of the **bluetoothctl(1)** client, driven through its
+interactive prompt or its command line.
+
+test_bluetoothctl_pair_bredr
+----------------------------
+
+:Setup: Two hosts, each running `bluetoothctl`.
+
+ .. code-block::
+
+ +--------------------+ +--------------------+
+ | host0 | BR/EDR | host1 |
+ | bluetoothctl | --------------> | bluetoothctl |
+ | scan on, pair | | pairable on, |
+ | | | discoverable on |
+ +--------------------+ +--------------------+
+
+:Steps:
+ 1. host0: ``scan on``.
+ 2. host1: ``pairable on`` and ``discoverable on``.
+ 3. host0: ``pair <host1 bdaddr>`` once host1 is discovered.
+ 4. Both sides answer ``yes`` to the passkey confirmation.
+
+:Expected:
+ 1. ``Controller <host0> Discovering: yes``.
+ 2. ``Changing pairable on succeeded`` and
+ ``Controller <host1> Discoverable: yes``.
+ 3. host0 prints ``Device <host1>``, then both sides prompt to
+ confirm the *same* passkey.
+ 4. host0 prints ``Pairing successful``.
+
+test_bluetoothctl_pair_le
+-------------------------
+
+:Setup: Two hosts, each running `bluetoothd` with
+ ``ControllerMode = le`` and `bluetoothctl`.
+
+ .. code-block::
+
+ +--------------------+ +--------------------+
+ | host0 | LE | host1 |
+ | bluetoothctl | --------------> | bluetoothctl |
+ | scan on, pair | | advertise on |
+ +--------------------+ +--------------------+
+
+:Steps:
+ 1. host0: ``scan on``.
+ 2. host1: ``advertise on``.
+ 3. host0: ``pair <host1 bdaddr>`` once host1 is discovered.
+ 4. Answer the passkey confirmation, or enter the passkey on host1
+ if legacy pairing was used.
+
+:Expected:
+ 1. ``Controller <host0> Discovering: yes``.
+ 2. ``Advertising object registered``.
+ 3. host0 prints ``Device <host1>``.
+ 4. host0 prints ``Pairing successful``.
+
+:Notes: If the controller is power cycled before `bluetoothd` starts,
+ which is what the tester does, enabling Secure Connections Host
+ Support may fail and pairing falls back to legacy passkey entry.
+ The test accepts both, but warns when the legacy path is taken.
+
+test_bluetoothctl_show
+----------------------
+
+:Setup: One host running `bluetoothd`, reused across the tests of this
+ module.
+
+ .. code-block::
+
+ +--------------------------+
+ | host0 |
+ | bluetoothd, bluetoothctl |
+ +--------------------------+
+
+:Steps: Run ``bluetoothctl show``.
+
+:Expected: Exit status 0, and the output reports
+ ``Controller <bdaddr>``, ``Powered:`` and ``Discoverable: no``.
+
+test_bluetoothctl_list
+----------------------
+
+:Setup: As above.
+
+:Steps: Run ``bluetoothctl list``.
+
+:Expected: Exit status 0, and the controller is listed and marked
+ ``[default]``.
+
+test_bluetoothctl_script_show
+-----------------------------
+
+:Setup: As above.
+
+:Steps: Run ``show`` through ``bluetoothctl --init-script``.
+
+:Expected: Same as ``test_bluetoothctl_show``.
+
+:Notes: Covers the script input path rather than the command line.
+
+test_bluetoothctl_script_list
+-----------------------------
+
+:Setup: As above.
+
+:Steps: Run ``list`` through ``bluetoothctl --init-script``.
+
+:Expected: Same as ``test_bluetoothctl_list``.
+
+test_btmgmt.py
+==============
+
+test_btmgmt_info
+----------------
+
+:Setup: One host with a controller and no `bluetoothd` running.
+
+ .. code-block::
+
+ +--------------------------+
+ | host0 |
+ | btmgmt, no bluetoothd |
+ +--------------------------+
+
+:Steps: Run ``btmgmt --index 0 info``.
+
+:Expected: Exit status 0, and the output contains
+ ``addr <bdaddr>`` for the controller of the host.
+
+:Notes: Skipped if `btmgmt` is not built. Checks the mgmt interface is
+ usable without a daemon.
+
+test_adv_monitor.py
+===================
+
+test_adv_monitor_GHSA_hhgc_hfgf_8m4x
+------------------------------------
+
+:Setup: One host running `bluetoothd` with ``Experimental = true``.
+
+ .. code-block::
+
+ +---------------------------------+
+ | host0 |
+ | bluetoothd (Experimental) |
+ | advertisement monitor app |
+ +---------------------------------+
+
+:Steps:
+ 1. Register an advertisement monitor application exposing one
+ monitor with 8 patterns of 31 bytes each.
+ 2. Call
+ ``org.bluez.AdvertisementMonitorManager1.RegisterMonitor``.
+
+:Expected: ``RegisterMonitor`` completes, with either a reply or an
+ error, and `bluetoothd` does not crash.
+
+:Notes: Regression test for NN-2026-0142, a heap overflow caused by
+ ``uint8`` length truncation when the mgmt command carrying the
+ patterns exceeds 255 bytes (`src/adv_monitor.c`). The overflow
+ happens during the ``ADD_ADV_PATTERNS_MONITOR`` mgmt call, before
+ the monitor is activated. Marked ``sa``.
+
+test_kernel_testers.py
+======================
+
+Kernel side tests: they run the BlueZ testers inside the VM against the
+kernel under test. Excluded from ``make check-functional``; run them
+with ``test/test-functional -m tester``.
+
+test_kernel_tester[<tester>]
+----------------------------
+
+:Setup: One host, without a controller, reused across the parameters.
+
+ .. code-block::
+
+ +----------------------------------+
+ | host0 |
+ | kernel under test, no controller |
+ | tester creates its own hciX |
+ +----------------------------------+
+
+:Steps:
+ 1. Run the tester in the VM.
+ 2. Parse its test summary.
+
+:Expected: No test is reported as ``Failed`` or ``Timed out``, except
+ the ones listed in the ``XFAIL`` table of the module.
+
+:Notes: The testers covered are `mgmt-tester`, `smp-tester`,
+ `l2cap-tester`, `rfcomm-tester`, `sco-tester`, `iso-tester`,
+ `mesh-tester`, `ioctl-tester`, `bnep-tester`, `userchan-tester`
+ and `6lowpan-tester`. A known failing case that unexpectedly
+ passes emits an ``XPASS`` warning, so the entry can be dropped.
+ Marked ``tester``.
+
+test_kernel_selftest
+--------------------
+
+:Setup: As above.
+
+:Steps: Run `check-selftest`, which reads the kernel Bluetooth selftest
+ results.
+
+:Expected: Exit status 0, and the output contains ``PASS`` and no
+ ``FAIL``.
+
+:Notes: Skipped if ``CONFIG_BT_SELFTEST`` is not enabled, which is the
+ case when the output is empty.
+
+test_tests.py
+=============
+
+test_formatting
+---------------
+
+:Setup: None, the test does not use a VM.
+
+:Steps: Run `Black <https://black.readthedocs.io/en/stable/>`__ in
+ check mode over `test/functional`.
+
+:Expected: The sources are formatted. Formatting problems are reported
+ as a warning, not as a failure.
+
+:Notes: Skipped if `black` is not installed.
+
+ADDING TEST CASES
+=================
+
+When adding a test module or case, document it here as well, or in the
+matching profile document. For regression tests of security advisories,
+name the test ``test_<area>_GHSA_xxxx_xxxx_xxxx`` so it is
+automatically marked ``sa``, and describe the issue it covers.
+
+See **test-functional(1)** for how tests are written.
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 03/12] client: do not prompt for LE Audio settings on A2DP endpoints
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 02/12] doc: describe the functional test cases Luiz Augusto von Dentz
@ 2026-09-09 19:22 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 04/12] client: add A2DP endpoint registration scripts Luiz Augusto von Dentz
` (9 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:22 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Registering an A2DP endpoint prompted for Locations, Supported
Context, Context, CIG and CIS, which are LE Audio/ISO specific and
have no meaning for A2DP. Worse, answering Locations with a single
location pushes an LC3_CHAN_COUNT LTV into the capabilities, which
corrupts the SBC capabilities.
Register A2DP endpoints right after Max Transports instead, and use
the same helper in endpoint_init_defaults(), which compared against
A2DP_SOURCE_UUID twice and therefore left A2DP Sink endpoints with
ISO and broadcast defaults.
Assisted-by: opencode:claude-opus-5
---
client/player.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/client/player.c b/client/player.c
index c5e4beef0ed3..6cd5aeb4a175 100644
--- a/client/player.c
+++ b/client/player.c
@@ -3506,6 +3506,12 @@ static void endpoint_locations(const char *input, void *user_data)
endpoint_supported_context, ep);
}
+static bool endpoint_is_a2dp(struct endpoint *ep)
+{
+ return !strcasecmp(ep->uuid, A2DP_SOURCE_UUID) ||
+ !strcasecmp(ep->uuid, A2DP_SINK_UUID);
+}
+
static void endpoint_max_transports(const char *input, void *user_data)
{
struct endpoint *ep = user_data;
@@ -3525,6 +3531,12 @@ static void endpoint_max_transports(const char *input, void *user_data)
ep->max_transports = value;
}
+ /* Locations, contexts and ISO settings only apply to LE Audio */
+ if (endpoint_is_a2dp(ep)) {
+ endpoint_register(ep);
+ return;
+ }
+
bt_shell_prompt_input(ep->path, "Locations:", endpoint_locations, ep);
}
@@ -4629,8 +4641,7 @@ static void endpoint_init_defaults(struct endpoint *ep)
ep->max_transports = UINT8_MAX;
ep->auto_accept = true;
- if (!strcmp(ep->uuid, A2DP_SOURCE_UUID) ||
- !strcmp(ep->uuid, A2DP_SOURCE_UUID))
+ if (endpoint_is_a2dp(ep))
return;
ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 04/12] client: add A2DP endpoint registration scripts
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (2 preceding siblings ...)
2026-09-09 19:22 ` [PATCH BlueZ v1 03/12] client: do not prompt for LE Audio settings on A2DP endpoints Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 05/12] test: functional: add A2DP tests Luiz Augusto von Dentz
` (8 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add scripts registering an A2DP Source and an A2DP Sink endpoint with
SBC, so a peer can be set up with a single command:
bluetoothctl --init-script client/scripts/a2dp-source.bt
Assisted-by: opencode:claude-opus-5
---
client/scripts/a2dp-sink.bt | 19 +++++++++++++++++++
client/scripts/a2dp-source.bt | 24 ++++++++++++++++++++++++
2 files changed, 43 insertions(+)
create mode 100644 client/scripts/a2dp-sink.bt
create mode 100644 client/scripts/a2dp-source.bt
diff --git a/client/scripts/a2dp-sink.bt b/client/scripts/a2dp-sink.bt
new file mode 100644
index 000000000000..3c7c040bdb5d
--- /dev/null
+++ b/client/scripts/a2dp-sink.bt
@@ -0,0 +1,19 @@
+power on
+#
+#
+# Register an A2DP Sink endpoint with SBC, to act as the device
+# receiving audio, e.g. a speaker.
+#
+endpoint.register 0000110b-0000-1000-8000-00805f9b34fb 0x00
+# Auto Accept: accept the configuration chosen by the peer
+y
+# Max Transports: no limit
+a
+#
+#
+# The endpoint has to be registered before pairing, so the SDP record
+# is in place when the peer resolves the services.
+#
+# A transport is created once the peer configures the stream, and it is
+# the peer, as the A2DP Source, that acquires it to start the stream.
+#
diff --git a/client/scripts/a2dp-source.bt b/client/scripts/a2dp-source.bt
new file mode 100644
index 000000000000..8f582e9f2faa
--- /dev/null
+++ b/client/scripts/a2dp-source.bt
@@ -0,0 +1,24 @@
+power on
+#
+#
+# Register an A2DP Source endpoint with SBC, to act as the device
+# sending audio, e.g. a phone.
+#
+endpoint.register 0000110a-0000-1000-8000-00805f9b34fb 0x00
+# Auto Accept: accept the configuration chosen by the peer
+y
+# Max Transports: no limit
+a
+#
+#
+# The endpoint has to be registered before pairing, so the SDP record
+# is in place when the peer resolves the services, otherwise connecting
+# fails with org.bluez.Error.BREDR.ProfileUnavailable.
+#
+# Once connected, the stream is configured and a transport is created:
+#
+# [NEW] Transport /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/sep1/fd0
+#
+# Acquire it to start the stream:
+#
+# transport.acquire /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/sep1/fd0
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 05/12] test: functional: add A2DP tests
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (3 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 04/12] client: add A2DP endpoint registration scripts Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 06/12] client: rename media endpoint scripts to include the codec Luiz Augusto von Dentz
` (7 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add tests covering A2DP via bluetoothctl: the central registers an
A2DP Source endpoint and the peripheral an A2DP Sink endpoint, using
the endpoint registration scripts. The endpoints are registered before
pairing so the SDP records are in place when the peer resolves the
services.
test_a2dp_transport_created checks a MediaTransport1 object is created
on both sides with the expected endpoint, codec and state, and
test_a2dp_transport_acquire checks the central can acquire it.
Assisted-by: opencode:claude-opus-5
---
Makefile.am | 1 +
doc/functional-a2dp.rst | 99 ++++++++++++++++++++++++
doc/functional-testing.rst | 1 +
test/functional/test_a2dp.py | 144 +++++++++++++++++++++++++++++++++++
4 files changed, 245 insertions(+)
create mode 100644 doc/functional-a2dp.rst
create mode 100644 test/functional/test_a2dp.py
diff --git a/Makefile.am b/Makefile.am
index afa213cf0b8c..2eef04edd760 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -498,6 +498,7 @@ EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/test-runner.rst \
doc/test-functional.rst \
doc/functional-testing.rst \
+ doc/functional-a2dp.rst \
doc/functional-avrcp.rst \
doc/functional-obex.rst \
doc/settings-storage.txt
diff --git a/doc/functional-a2dp.rst b/doc/functional-a2dp.rst
new file mode 100644
index 000000000000..779bcdcc0ff8
--- /dev/null
+++ b/doc/functional-a2dp.rst
@@ -0,0 +1,99 @@
+===============
+functional-a2dp
+===============
+
+DESCRIPTION
+===========
+
+A2DP functional tests, `test/functional/test_a2dp.py`, driven through
+**bluetoothctl(1)**. See **functional-testing(7)** for the conventions
+used here, and **test-functional(1)** for how to run the suite.
+
+SETUP
+=====
+
+Two hosts, connected over BR/EDR:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | BR/EDR | host1 |
+ | central | --------------> | peripheral |
+ | bluetoothctl | | bluetoothctl |
+ | a2dp-source.bt | AVDTP (SBC) | a2dp-sink.bt |
+ | A2DP Source endpoint | ==============> | A2DP Sink endpoint |
+ +------------------------+ +------------------------+
+
+ --> connection is initiated by ==> audio flows towards
+
+Both hosts start `bluetoothctl` with an endpoint registration script:
+
+``client/scripts/a2dp-source.bt`` on host0
+ Registers a local A2DP Source endpoint
+ (``0000110a-0000-1000-8000-00805f9b34fb``) with SBC. host0 is the
+ central, i.e. the device sending audio.
+
+``client/scripts/a2dp-sink.bt`` on host1
+ Registers a local A2DP Sink endpoint
+ (``0000110b-0000-1000-8000-00805f9b34fb``) with SBC. host1 is the
+ peripheral, i.e. the device receiving audio.
+
+The endpoints are registered *before* pairing, so the SDP records are
+in place when the peer resolves the services. The hosts are then paired
+over BR/EDR, trust each other, and the central connects.
+
+TEST CASES
+==========
+
+test_a2dp_transport_created
+---------------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Start `bluetoothctl` with the scripts on both hosts.
+ 2. Pair over BR/EDR: ``scan on`` on the central,
+ ``pairable on`` and ``discoverable on`` on the peripheral,
+ ``pair``, and confirm the passkey on both sides.
+ 3. ``trust`` the peer on both sides.
+ 4. Central: ``connect <peripheral bdaddr>``.
+ 5. Central: ``transport.show <transport>``.
+ 6. Peripheral: ``transport.show <transport>``.
+
+:Expected:
+ 1. ``Endpoint /local/endpoint/ep0 registered`` on both hosts.
+ 2. ``Pairing successful``.
+ 3. ``trust succeeded`` on both hosts.
+ 4. ``Connection successful``, the stream is configured and a
+ transport appears on *both* hosts.
+ 5. The central transport reports
+ ``UUID: Audio Source (0000110a-...)``, ``Codec: 0x00``,
+ ``Media Codec: SBC``, ``Device:`` pointing at the peripheral
+ device object, and ``State: idle``.
+ 6. The peripheral transport reports
+ ``UUID: Audio Sink (0000110b-...)`` and ``Codec: 0x00``.
+
+:Notes: A transport reports the UUID of the *local* endpoint it was
+ created for, which is why the two sides differ. The transport
+ paths differ as well: the central knows the remote SEP, so it uses
+ ``.../dev_XX/sepN/fdN``, while the peripheral uses
+ ``.../dev_XX/fdN``.
+
+ Registering the endpoints after pairing makes ``connect`` fail
+ with ``org.bluez.Error.BREDR.ProfileUnavailable``, as the services
+ were already resolved. Without ``trust``, the peripheral blocks on
+ an ``org.bluez.Agent1.AuthorizeService`` prompt.
+
+test_a2dp_transport_acquire
+---------------------------
+
+:Setup: As above, with the transport already created.
+
+:Steps: Central: ``transport.acquire <transport>``.
+
+:Expected:
+ 1. ``Acquire successful: fd <fd> MTU <read>:<write>``.
+ 2. The transport moves to ``State: active``.
+
+:Notes: Acquiring on the source side starts the stream, so the
+ peripheral does not have to acquire its own transport.
diff --git a/doc/functional-testing.rst b/doc/functional-testing.rst
index 29436d4454c0..03539dfea0a5 100644
--- a/doc/functional-testing.rst
+++ b/doc/functional-testing.rst
@@ -12,6 +12,7 @@ configure and run the suite, see **test-functional(1)**.
This document covers the core test cases. Tests for a specific profile
are documented separately:
+- **functional-a2dp(7)**: `test/functional/test_a2dp.py`
- **functional-avrcp(7)**: `test/functional/test_avrcp.py`
- **functional-obex(7)**: `test/functional/test_obex.py`
diff --git a/test/functional/test_a2dp.py b/test/functional/test_a2dp.py
new file mode 100644
index 000000000000..f176e4296439
--- /dev/null
+++ b/test/functional/test_a2dp.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for A2DP using bluetoothctl in VM instances
+"""
+
+import pytest
+
+from pytest_bluezenv import Bluetoothd, Pexpect, find_exe, host_config
+from pytest_bluezenv.utils import bluez_src_dir
+
+pytestmark = [pytest.mark.vm]
+
+A2DP_SOURCE_UUID = "0000110a-0000-1000-8000-00805f9b34fb"
+A2DP_SINK_UUID = "0000110b-0000-1000-8000-00805f9b34fb"
+
+TRANSPORT_RE = r"Transport (/org/bluez/\S+/fd\d+)"
+
+
+def dev_addr(host):
+ return host.bdaddr.upper().replace(":", "_")
+
+
+def script(name):
+ src = bluez_src_dir()
+ if src is None:
+ pytest.skip("BlueZ source directory not known")
+
+ path = src / "client" / "scripts" / name
+ if not path.exists():
+ pytest.skip(f"{path} not found")
+
+ return str(path)
+
+
+def start_bluetoothctl(host, init_script):
+ """
+ Start bluetoothctl registering the endpoints of the given script.
+
+ The endpoints are registered before pairing, so that the SDP
+ records are in place when the peer resolves the services.
+ """
+ exe = find_exe("client", "bluetoothctl")
+ ctl = host.pexpect.spawn([exe, "--init-script", script(init_script)])
+ ctl.expect("Endpoint /local/endpoint/ep0 registered")
+ return ctl
+
+
+def pair(host0, ctl0, host1, ctl1):
+ ctl0.send("scan on\n")
+ ctl0.expect(f"Controller {host0.bdaddr.upper()} Discovering: yes")
+
+ ctl1.send("pairable on\n")
+ ctl1.expect("Changing pairable on succeeded")
+ ctl1.send("discoverable on\n")
+ ctl1.expect(f"Controller {host1.bdaddr.upper()} Discoverable: yes")
+
+ ctl0.expect(f"Device {host1.bdaddr.upper()}")
+ ctl0.send(f"pair {host1.bdaddr}\n")
+
+ idx, m = ctl0.expect(r"Confirm passkey (\d+).*:")
+ key = m[0].decode("utf-8")
+
+ ctl1.expect(f"Confirm passkey {key}")
+
+ ctl0.send("yes\n")
+ ctl1.send("yes\n")
+
+ ctl0.expect("Pairing successful")
+
+ ctl0.send("scan off\n")
+
+ # Avoid service authorization prompts when connecting
+ ctl1.send(f"trust {host0.bdaddr}\n")
+ ctl1.expect("trust succeeded")
+ ctl0.send(f"trust {host1.bdaddr}\n")
+ ctl0.expect("trust succeeded")
+
+
+a2dp_host_config = host_config(
+ [Bluetoothd(), Pexpect()],
+ [Bluetoothd(), Pexpect()],
+)
+
+
+@pytest.fixture
+def a2dp_hosts(hosts):
+ """
+ Two hosts with A2DP endpoints registered via bluetoothctl: host0 is
+ the central with an A2DP Source endpoint, host1 the peripheral with
+ an A2DP Sink endpoint. The hosts are paired and connected, so that
+ a stream is configured.
+ """
+ host0, host1 = hosts
+
+ source = start_bluetoothctl(host0, "a2dp-source.bt")
+ sink = start_bluetoothctl(host1, "a2dp-sink.bt")
+
+ pair(host0, source, host1, sink)
+
+ source.send(f"connect {host1.bdaddr}\n")
+ source.expect("Connection successful")
+
+ yield host0, host1, source, sink
+
+
+@a2dp_host_config
+def test_a2dp_transport_created(a2dp_hosts):
+ host0, host1, source, sink = a2dp_hosts
+
+ # Transport is created on both sides once the stream is configured
+ _, m = source.expect(TRANSPORT_RE)
+ transport = m[0].decode("utf-8")
+
+ _, m = sink.expect(TRANSPORT_RE)
+ sink_transport = m[0].decode("utf-8")
+
+ # Central holds the A2DP Source endpoint
+ source.send(f"transport.show {transport}\n")
+ source.expect(f"Transport {transport}")
+ source.expect(rf"UUID: Audio Source\s+\({A2DP_SOURCE_UUID}\)")
+ source.expect(r"Codec: 0x00")
+ source.expect("Media Codec: SBC")
+ source.expect(f"Device: /org/bluez/hci0/dev_{dev_addr(host1)}")
+ source.expect("State: idle")
+
+ # Peripheral holds the A2DP Sink endpoint
+ sink.send(f"transport.show {sink_transport}\n")
+ sink.expect(f"Transport {sink_transport}")
+ sink.expect(rf"UUID: Audio Sink\s+\({A2DP_SINK_UUID}\)")
+ sink.expect(r"Codec: 0x00")
+
+
+@a2dp_host_config
+def test_a2dp_transport_acquire(a2dp_hosts):
+ host0, host1, source, sink = a2dp_hosts
+
+ _, m = source.expect(TRANSPORT_RE)
+ transport = m[0].decode("utf-8")
+
+ source.send(f"transport.acquire {transport}\n")
+ source.expect(r"Acquire successful: fd \d+ MTU \d+:\d+")
+
+ source.expect(f"Transport {transport} State: active")
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 06/12] client: rename media endpoint scripts to include the codec
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (4 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 05/12] test: functional: add A2DP tests Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 07/12] client: add BAP endpoint registration scripts Luiz Augusto von Dentz
` (6 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Name the scripts registering a media endpoint
<profile>-<role>-<codec>[-<preset>].bt, so it is clear what they set
up without having to read them.
Assisted-by: opencode:claude-opus-5
---
client/scripts/{a2dp-sink.bt => a2dp-sink-sbc.bt} | 0
client/scripts/{a2dp-source.bt => a2dp-source-sbc.bt} | 0
doc/functional-a2dp.rst | 6 +++---
test/functional/test_a2dp.py | 4 ++--
4 files changed, 5 insertions(+), 5 deletions(-)
rename client/scripts/{a2dp-sink.bt => a2dp-sink-sbc.bt} (100%)
rename client/scripts/{a2dp-source.bt => a2dp-source-sbc.bt} (100%)
diff --git a/client/scripts/a2dp-sink.bt b/client/scripts/a2dp-sink-sbc.bt
similarity index 100%
rename from client/scripts/a2dp-sink.bt
rename to client/scripts/a2dp-sink-sbc.bt
diff --git a/client/scripts/a2dp-source.bt b/client/scripts/a2dp-source-sbc.bt
similarity index 100%
rename from client/scripts/a2dp-source.bt
rename to client/scripts/a2dp-source-sbc.bt
diff --git a/doc/functional-a2dp.rst b/doc/functional-a2dp.rst
index 779bcdcc0ff8..03b18a7201c2 100644
--- a/doc/functional-a2dp.rst
+++ b/doc/functional-a2dp.rst
@@ -20,7 +20,7 @@ Two hosts, connected over BR/EDR:
| host0 | BR/EDR | host1 |
| central | --------------> | peripheral |
| bluetoothctl | | bluetoothctl |
- | a2dp-source.bt | AVDTP (SBC) | a2dp-sink.bt |
+ | a2dp-source-sbc.bt | AVDTP (SBC) | a2dp-sink-sbc.bt |
| A2DP Source endpoint | ==============> | A2DP Sink endpoint |
+------------------------+ +------------------------+
@@ -28,12 +28,12 @@ Two hosts, connected over BR/EDR:
Both hosts start `bluetoothctl` with an endpoint registration script:
-``client/scripts/a2dp-source.bt`` on host0
+``client/scripts/a2dp-source-sbc.bt`` on host0
Registers a local A2DP Source endpoint
(``0000110a-0000-1000-8000-00805f9b34fb``) with SBC. host0 is the
central, i.e. the device sending audio.
-``client/scripts/a2dp-sink.bt`` on host1
+``client/scripts/a2dp-sink-sbc.bt`` on host1
Registers a local A2DP Sink endpoint
(``0000110b-0000-1000-8000-00805f9b34fb``) with SBC. host1 is the
peripheral, i.e. the device receiving audio.
diff --git a/test/functional/test_a2dp.py b/test/functional/test_a2dp.py
index f176e4296439..1ab712cd0da1 100644
--- a/test/functional/test_a2dp.py
+++ b/test/functional/test_a2dp.py
@@ -93,8 +93,8 @@ def a2dp_hosts(hosts):
"""
host0, host1 = hosts
- source = start_bluetoothctl(host0, "a2dp-source.bt")
- sink = start_bluetoothctl(host1, "a2dp-sink.bt")
+ source = start_bluetoothctl(host0, "a2dp-source-sbc.bt")
+ sink = start_bluetoothctl(host1, "a2dp-sink-sbc.bt")
pair(host0, source, host1, sink)
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 07/12] client: add BAP endpoint registration scripts
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (5 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 06/12] client: rename media endpoint scripts to include the codec Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 08/12] doc: bluetoothctl: document init script option and scripts Luiz Augusto von Dentz
` (5 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add scripts registering a local PAC Source and a local PAC Sink
endpoint with LC3, to set up the two sides of a BAP unicast stream:
bluetoothctl --init-script client/scripts/bap-source-lc3.bt
The locations and contexts match the ones bluetoothctl uses when
registering the endpoints itself, so that the metadata of a stream is
not rejected.
Assisted-by: opencode:claude-opus-5
---
client/scripts/bap-sink-lc3.bt | 32 ++++++++++++++++++++++++++++++
client/scripts/bap-source-lc3.bt | 34 ++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 client/scripts/bap-sink-lc3.bt
create mode 100644 client/scripts/bap-source-lc3.bt
diff --git a/client/scripts/bap-sink-lc3.bt b/client/scripts/bap-sink-lc3.bt
new file mode 100644
index 000000000000..4c3cdc51e785
--- /dev/null
+++ b/client/scripts/bap-sink-lc3.bt
@@ -0,0 +1,32 @@
+power on
+#
+#
+# Register a PAC Sink endpoint with LC3, to act as the acceptor
+# receiving audio, e.g. a headset.
+#
+endpoint.register 00002bc9-0000-1000-8000-00805f9b34fb 0x06
+# Auto Accept: accept the configuration chosen by the peer
+y
+# Max Transports: no limit
+a
+# Locations: Front Left and Front Right
+3
+# Supported Context: same as the contexts bluetoothctl registers with
+0x0fff
+# Context
+0x0fff
+# CIG: assigned automatically
+a
+# CIS: assigned automatically
+a
+#
+#
+# With narrower Supported Contexts the Enable is rejected with Invalid
+# Metadata, as the metadata of a stream carries the Unspecified
+# context.
+#
+# Advertise and wait for the initiator to connect and configure the
+# streams. The transports do not have to be acquired for the CIS to be
+# established, as bluetoothd sets up the ISO listener on its own:
+#
+# advertise on
diff --git a/client/scripts/bap-source-lc3.bt b/client/scripts/bap-source-lc3.bt
new file mode 100644
index 000000000000..8bd03044af6c
--- /dev/null
+++ b/client/scripts/bap-source-lc3.bt
@@ -0,0 +1,34 @@
+power on
+#
+#
+# Register a PAC Source endpoint with LC3, to act as the initiator
+# sending audio, e.g. a phone.
+#
+endpoint.register 00002bcb-0000-1000-8000-00805f9b34fb 0x06
+# Auto Accept: accept the configuration chosen by the peer
+y
+# Max Transports: no limit
+a
+# Locations: Front Left and Front Right
+3
+# Supported Context: same as the contexts bluetoothctl registers with
+0x000f
+# Context
+0x000f
+# CIG: assigned automatically
+a
+# CIS: assigned automatically
+a
+#
+#
+# After pairing, configure a remote PAC Sink endpoint with a preset:
+#
+# endpoint.config /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/pac_sink0 \
+# /local/endpoint/ep0 16_2_1
+#
+# A stream is created per audio location, each with its own CIS in the
+# same CIG, and all of the transports have to be acquired, as the CIS
+# are only created once every CIS of the CIG is ready:
+#
+# transport.acquire /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/pac_sink0/fd0 \
+# /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/pac_sink0/fd1
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 08/12] doc: bluetoothctl: document init script option and scripts
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (6 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 07/12] client: add BAP endpoint registration scripts Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 09/12] test: functional: add BAP unicast tests Luiz Augusto von Dentz
` (4 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
The --init-script option was not documented. Document it, along with
the scripts shipped in client/scripts and the roles they set up.
Assisted-by: opencode:claude-opus-5
---
doc/bluetoothctl.rst | 105 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/doc/bluetoothctl.rst b/doc/bluetoothctl.rst
index 0c55092b880f..ed4cd0703281 100644
--- a/doc/bluetoothctl.rst
+++ b/doc/bluetoothctl.rst
@@ -38,6 +38,7 @@ OPTIONS
-a capability, --agent capability Register agent handler: <capability>
-e, --endpoints Register Media endpoints
-m, --monitor Enable monitor output
+-s file, --init-script file Run the commands in the given script file
-t seconds, --timeout seconds Timeout in seconds for non-interactive mode
-v, --version Display version
-h, --help Display help
@@ -589,6 +590,110 @@ Using Here Docs to show information about the Bluetooth controller.
show
EOF
+Commands can also be read from a file with the **--init-script** option.
+The tool stays interactive after the script has been executed, which is
+useful to set up a role and then drive it by hand:
+
+.. code::
+
+ bluetoothctl --init-script client/scripts/power-on.bt
+
+Lines starting with **#** are comments, and lines are also used to answer
+the prompts of the commands, in the order the prompts appear.
+
+SCRIPTS
+=======
+
+The scripts shipped in **client/scripts** set up common roles. Scripts
+registering a media endpoint are named
+*<profile>-<role>-<codec>[-<preset>].bt*, where the preset is only part
+of the name if the script also configures the stream.
+
+Controller setup
+----------------
+
+``power-on.bt``, ``power-on-off.bt``
+ Power the controller on, or power it off and on again.
+
+``scan-on.bt``, ``scan-on-off.bt``, ``scan-le.bt``, ``scan-bredr.bt``
+ Start discovery, optionally restricted to a transport.
+
+``advertise-on.bt``, ``advertise-peripheral.bt``, ``advertise-broadcast.bt``, ``advertise-rsi.bt``
+ Start advertising with the given type.
+
+A2DP
+----
+
+``a2dp-source-sbc.bt``
+ Register a local A2DP Source endpoint (``0000110a-...``) with SBC,
+ i.e. act as the device sending audio, such as a phone.
+
+``a2dp-sink-sbc.bt``
+ Register a local A2DP Sink endpoint (``0000110b-...``) with SBC,
+ i.e. act as the device receiving audio, such as a speaker.
+
+Once connected, the stream is configured automatically and a transport
+is created, which can be acquired with **transport.acquire**.
+
+BAP unicast
+-----------
+
+``bap-source-lc3.bt``
+ Register a local PAC Source endpoint (``00002bcb-...``) with LC3,
+ i.e. act as the initiator sending audio.
+
+``bap-sink-lc3.bt``
+ Register a local PAC Sink endpoint (``00002bc9-...``) with LC3,
+ i.e. act as the acceptor receiving audio.
+
+The initiator configures a remote endpoint with **endpoint.config**,
+choosing a preset, which creates the transport:
+
+.. code::
+
+ endpoint.config /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX/pac_snk0 \
+ /local/endpoint/ep0 16_2_1
+
+``preset-custom.bt``
+ Add a custom LC3 preset, instead of using one of the presets
+ defined by the specification.
+
+BAP broadcast
+-------------
+
+``broadcast-source.bt``, ``broadcast-source-2bis.bt``, ``broadcast-source-pbp.bt``
+ Register a Broadcast Source endpoint (``00001852-...``) with LC3,
+ configure it with the 16_2_1 preset and acquire the transport,
+ which starts the broadcast. The variants set up two BISes and the
+ Public Broadcast Profile respectively.
+
+``broadcast-sink.bt``
+ Register a Broadcast Sink endpoint (``00001851-...``) with LC3 and
+ scan, to sync to a Broadcast Source without the help of a
+ Broadcast Assistant.
+
+``scan-delegator.bt``, ``broadcast-delegator.bt``
+ Register a Broadcast Sink endpoint and advertise, to be used as
+ Scan Delegator by a Broadcast Assistant. The stream is then synced
+ using PAST, and the transport moved to broadcasting with
+ **transport.select** before it is acquired.
+
+``broadcast-assistant.bt``
+ Scan, to discover a Scan Delegator to connect to and Broadcast
+ Sources to offer it with **assistant.push**.
+
+Channel Sounding
+----------------
+
+``cs-initiator.bt``, ``cs-reflector.bt``
+ Set up the two sides of a Channel Sounding procedure.
+
+GATT
+----
+
+``gatt-batt.bt``
+ Register a Battery Service with a notifiable Battery Level
+ characteristic.
RESOURCES
=========
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 09/12] test: functional: add BAP unicast tests
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (7 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 08/12] doc: bluetoothctl: document init script option and scripts Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 10/12] test: functional: add BAP broadcast tests Luiz Augusto von Dentz
` (3 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add tests covering BAP unicast via bluetoothctl: the initiator
registers a PAC Source endpoint, the acceptor a PAC Sink endpoint, and
after pairing the initiator configures the remote endpoint with a
preset.
test_bap_unicast_transport_created checks a MediaTransport1 object is
created on both sides with the expected endpoint, codec and state, and
test_bap_unicast_transport_acquire checks the initiator can acquire
them.
The endpoints are configured for stereo, so a stream is created per
location, each with its own CIS in the same CIG. All the transports
have to be acquired, as the CIS are only created once every CIS of the
CIG is ready.
Assisted-by: opencode:claude-opus-5
---
Makefile.am | 1 +
doc/functional-bap.rst | 109 +++++++++++++++++++++++
doc/functional-testing.rst | 1 +
test/functional/test_bap.py | 169 ++++++++++++++++++++++++++++++++++++
4 files changed, 280 insertions(+)
create mode 100644 doc/functional-bap.rst
create mode 100644 test/functional/test_bap.py
diff --git a/Makefile.am b/Makefile.am
index 2eef04edd760..efab756242e9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -500,6 +500,7 @@ EXTRA_DIST += doc/assigned-numbers.rst doc/supported-features.txt \
doc/functional-testing.rst \
doc/functional-a2dp.rst \
doc/functional-avrcp.rst \
+ doc/functional-bap.rst \
doc/functional-obex.rst \
doc/settings-storage.txt
diff --git a/doc/functional-bap.rst b/doc/functional-bap.rst
new file mode 100644
index 000000000000..6b67df541e97
--- /dev/null
+++ b/doc/functional-bap.rst
@@ -0,0 +1,109 @@
+==============
+functional-bap
+==============
+
+DESCRIPTION
+===========
+
+BAP (LE Audio) functional tests, `test/functional/test_bap.py`, driven
+through **bluetoothctl(1)**. See **functional-testing(7)** for the
+conventions used here, and **test-functional(1)** for how to run the
+suite.
+
+SETUP
+=====
+
+Two hosts, connected over LE:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | LE ACL | host1 |
+ | initiator (central) | --------------> | acceptor (peripheral) |
+ | bluetoothctl -a auto | | bluetoothctl -a auto |
+ | bap-source-lc3.bt | CIS 0x00 (LC3) | bap-sink-lc3.bt |
+ | PAC Source endpoint | ==============> | PAC Sink endpoint |
+ | | CIS 0x01 (LC3) | |
+ | | ==============> | |
+ +------------------------+ +------------------------+
+
+ one CIG holding one CIS per audio location
+
+ --> connection is initiated by ==> audio flows towards
+
+BAP requires the ISO socket support, so all hosts run `bluetoothd`
+with:
+
+.. code-block::
+
+ [General]
+ Experimental = true
+ KernelExperimental = true
+ ControllerMode = le
+
+`bluetoothctl` is started with ``-a auto``, so pairing and service
+authorization are accepted without prompting, and with an endpoint
+registration script:
+
+``client/scripts/bap-source-lc3.bt`` on host0
+ Registers a local PAC Source endpoint
+ (``00002bcb-0000-1000-8000-00805f9b34fb``) with LC3. host0 is the
+ initiator, i.e. the device sending audio.
+
+``client/scripts/bap-sink-lc3.bt`` on host1
+ Registers a local PAC Sink endpoint
+ (``00002bc9-0000-1000-8000-00805f9b34fb``) with LC3. host1 is the
+ acceptor, i.e. the device receiving audio.
+
+TEST CASES
+==========
+
+test_bap_unicast_transport_created
+----------------------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Start `bluetoothctl` with the scripts on both hosts.
+ 2. Pair over LE: ``scan on`` on the initiator, ``advertise on`` on
+ the acceptor, then ``pair``.
+ 3. Initiator: ``endpoint.config <remote endpoint>
+ /local/endpoint/ep0 16_2_1``, using the remote PAC Sink endpoint
+ exposed once the services are resolved.
+ 4. Initiator: ``transport.show <transport>`` for each transport.
+
+:Expected:
+ 1. ``Endpoint /local/endpoint/ep0 registered`` on both hosts.
+ 2. ``Pairing successful``.
+ 3. The remote endpoint appears as
+ ``Endpoint /org/bluez/hci0/dev_XX/pac_sinkN``, and configuring it
+ creates one transport per location on *both* hosts.
+ 4. Each transport of the initiator reports the local PAC Source
+ UUID (``00002bcb-...``), ``Codec: 0x06`` for LC3, ``Device:``
+ pointing at the acceptor device object, and ``State: idle``.
+
+:Notes: The endpoints are registered with the locations and contexts
+ `bluetoothctl` uses itself, ``0x0fff`` for the sink and ``0x000f``
+ for the source. With narrower Supported Contexts the acceptor
+ rejects the ``Enable`` with ``Invalid Metadata``, because the
+ metadata of the stream carries the ``Unspecified`` context.
+
+test_bap_unicast_transport_acquire
+----------------------------------
+
+:Setup: As above, with the transports already created.
+
+:Steps: Initiator: ``transport.acquire <transport> <transport>``, for
+ all the transports that were created.
+
+:Expected: ``Acquire successful: fd <fd> MTU <read>:<write>`` for each
+ transport, and each of them moves to ``State: active``.
+
+:Notes: *All* the transports have to be acquired: the controller only
+ creates the CIS once every CIS of the CIG is ready, so acquiring a
+ single transport leaves the stream waiting until it times out,
+ without ``LE Create CIS`` ever being sent.
+
+ The acceptor does not have to acquire its transports for the CIS to
+ be established, as `bluetoothd` sets up the ISO listener on its own
+ when the stream is enabled.
diff --git a/doc/functional-testing.rst b/doc/functional-testing.rst
index 03539dfea0a5..60c6762dee73 100644
--- a/doc/functional-testing.rst
+++ b/doc/functional-testing.rst
@@ -14,6 +14,7 @@ are documented separately:
- **functional-a2dp(7)**: `test/functional/test_a2dp.py`
- **functional-avrcp(7)**: `test/functional/test_avrcp.py`
+- **functional-bap(7)**: `test/functional/test_bap.py`
- **functional-obex(7)**: `test/functional/test_obex.py`
Each test case is described as:
diff --git a/test/functional/test_bap.py b/test/functional/test_bap.py
new file mode 100644
index 000000000000..69430d7a6699
--- /dev/null
+++ b/test/functional/test_bap.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8; mode: python; eval: (blacken-mode); -*-
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""
+Tests for BAP (LE Audio) using bluetoothctl in VM instances
+"""
+
+import warnings
+
+import pytest
+
+from pytest_bluezenv import Bluetoothd, Pexpect, find_exe, host_config
+from pytest_bluezenv.utils import bluez_src_dir
+
+pytestmark = [pytest.mark.vm]
+
+PAC_SINK_UUID = "00002bc9-0000-1000-8000-00805f9b34fb"
+PAC_SOURCE_UUID = "00002bcb-0000-1000-8000-00805f9b34fb"
+
+# BAP requires the ISO socket support, which is kernel experimental
+BAP_CONF = """[General]
+Experimental = true
+KernelExperimental = true
+ControllerMode = le
+"""
+
+PRESET = "16_2_1"
+
+TRANSPORT_RE = r"Transport (/org/bluez/\S+/fd\d+)"
+
+
+def dev_addr(host):
+ return host.bdaddr.upper().replace(":", "_")
+
+
+def expect_all(ctl, patterns):
+ """
+ Expect all the given patterns, in any order.
+ """
+ patterns = list(patterns)
+
+ while patterns:
+ idx, _ = ctl.expect(patterns)
+ patterns.pop(idx)
+
+
+def script(name):
+ src = bluez_src_dir()
+ if src is None:
+ pytest.skip("BlueZ source directory not known")
+
+ path = src / "client" / "scripts" / name
+ if not path.exists():
+ pytest.skip(f"{path} not found")
+
+ return str(path)
+
+
+def start_bluetoothctl(host, init_script):
+ exe = find_exe("client", "bluetoothctl")
+ # -a auto: accept pairing and authorize services without prompting
+ ctl = host.pexpect.spawn([exe, "-a", "auto", "--init-script", script(init_script)])
+ ctl.expect("Endpoint /local/endpoint/ep0 registered")
+ return ctl
+
+
+def pair_le(host0, ctl0, host1, ctl1):
+ ctl0.send("scan on\n")
+ ctl0.expect(f"Controller {host0.bdaddr.upper()} Discovering: yes")
+
+ ctl1.send("advertise on\n")
+ ctl1.expect("Advertising object registered")
+
+ ctl0.expect(f"Device {host1.bdaddr.upper()}")
+ ctl0.send(f"pair {host1.bdaddr.upper()}\n")
+
+ # See test_bluetoothctl_pair_le: passkey confirmation is handled by
+ # the auto agent, but legacy passkey entry still needs an answer
+ idx, m = ctl0.expect([r"\[agent\].*Passkey:.*m(\d+)", "Pairing successful"])
+
+ if idx == 0:
+ warnings.warn(
+ "BUG: we got passkey authentication, bluetoothd/kernel should be fixed"
+ )
+ key = m[0].decode("utf-8")
+ ctl1.expect(r"\[agent\] Enter passkey \(number in 0-999999\):")
+ ctl1.send(f"{key}\n")
+ ctl0.expect("Pairing successful")
+
+ ctl0.send("scan off\n")
+
+
+unicast_host_config = host_config(
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+)
+
+
+@pytest.fixture
+def unicast_hosts(hosts):
+ """
+ Initiator (host0) with a local PAC Source endpoint and acceptor
+ (host1) with a local PAC Sink endpoint, paired over LE, with the
+ remote PAC Sink endpoint configured.
+ """
+ host0, host1 = hosts
+
+ initiator = start_bluetoothctl(host0, "bap-source-lc3.bt")
+ acceptor = start_bluetoothctl(host1, "bap-sink-lc3.bt")
+
+ pair_le(host0, initiator, host1, acceptor)
+
+ # Remote PAC Sink endpoint is exposed once services are resolved
+ _, m = initiator.expect(r"Endpoint (/org/bluez/\S+/pac_sink\d+)")
+ remote = m[0].decode("utf-8")
+
+ initiator.send(f"endpoint.config {remote} /local/endpoint/ep0 {PRESET}\n")
+
+ yield host0, host1, initiator, acceptor
+
+
+def expect_transports(ctl):
+ """
+ The endpoint is configured for stereo, so one stream per location is
+ created, each with its own CIS in the same CIG.
+ """
+ _, m = ctl.expect(r"Transport (/org/bluez/\S+/fd0)")
+ left = m[0].decode("utf-8")
+ _, m = ctl.expect(r"Transport (/org/bluez/\S+/fd1)")
+ right = m[0].decode("utf-8")
+
+ return left, right
+
+
+@unicast_host_config
+def test_bap_unicast_transport_created(unicast_hosts):
+ host0, host1, initiator, acceptor = unicast_hosts
+
+ left, right = expect_transports(initiator)
+ expect_transports(acceptor)
+
+ for transport in (left, right):
+ initiator.send(f"transport.show {transport}\n")
+ initiator.expect(f"Transport {transport}")
+ initiator.expect(rf"UUID: .*\({PAC_SOURCE_UUID}\)")
+ initiator.expect(r"Codec: 0x06")
+ initiator.expect(f"Device: /org/bluez/hci0/dev_{dev_addr(host1)}")
+ initiator.expect("State: idle")
+
+
+@unicast_host_config
+def test_bap_unicast_transport_acquire(unicast_hosts):
+ host0, host1, initiator, acceptor = unicast_hosts
+
+ left, right = expect_transports(initiator)
+
+ # The CIS are only created once every CIS of the CIG is ready, so
+ # all the transports need to be acquired
+ initiator.send(f"transport.acquire {left} {right}\n")
+
+ acquired = r"Acquire successful: fd \d+ MTU \d+:\d+"
+ expect_all(
+ initiator,
+ [
+ acquired,
+ acquired,
+ f"Transport {left} State: active",
+ f"Transport {right} State: active",
+ ],
+ )
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 10/12] test: functional: add BAP broadcast tests
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (8 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 09/12] test: functional: add BAP unicast tests Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 11/12] test: functional: add BAP broadcast assistant test Luiz Augusto von Dentz
` (2 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add tests covering BAP broadcast via bluetoothctl: the Broadcast
Source registers a BCAA endpoint, configures it and starts the
broadcast, while the Broadcast Sink registers a BAA endpoint and scans,
syncing to the periodic advertising on its own, without a Broadcast
Assistant. Run them for a plain broadcast and for a Public Broadcast
Profile one.
test_bap_broadcast_transport_created checks a MediaTransport1 object is
created for each BIS of the BASE, and test_bap_broadcast_transport_acquire
checks selecting the transport with the broadcast code moves it to
broadcasting and acquiring it succeeds.
Assisted-by: opencode:claude-opus-5
---
doc/functional-bap.rst | 121 +++++++++++++++++++++++++++++++-----
test/functional/test_bap.py | 68 ++++++++++++++++++++
2 files changed, 172 insertions(+), 17 deletions(-)
diff --git a/doc/functional-bap.rst b/doc/functional-bap.rst
index 6b67df541e97..99a3d63eef68 100644
--- a/doc/functional-bap.rst
+++ b/doc/functional-bap.rst
@@ -13,6 +13,23 @@ suite.
SETUP
=====
+BAP requires the ISO socket support, so all hosts run `bluetoothd`
+with:
+
+.. code-block::
+
+ [General]
+ Experimental = true
+ KernelExperimental = true
+ ControllerMode = le
+
+`bluetoothctl` is started with ``-a auto``, so pairing and service
+authorization are accepted without prompting, and with an endpoint
+registration script from `client/scripts`.
+
+UNICAST
+=======
+
Two hosts, connected over LE:
.. code-block::
@@ -31,20 +48,6 @@ Two hosts, connected over LE:
--> connection is initiated by ==> audio flows towards
-BAP requires the ISO socket support, so all hosts run `bluetoothd`
-with:
-
-.. code-block::
-
- [General]
- Experimental = true
- KernelExperimental = true
- ControllerMode = le
-
-`bluetoothctl` is started with ``-a auto``, so pairing and service
-authorization are accepted without prompting, and with an endpoint
-registration script:
-
``client/scripts/bap-source-lc3.bt`` on host0
Registers a local PAC Source endpoint
(``00002bcb-0000-1000-8000-00805f9b34fb``) with LC3. host0 is the
@@ -55,9 +58,6 @@ registration script:
(``00002bc9-0000-1000-8000-00805f9b34fb``) with LC3. host1 is the
acceptor, i.e. the device receiving audio.
-TEST CASES
-==========
-
test_bap_unicast_transport_created
----------------------------------
@@ -107,3 +107,90 @@ test_bap_unicast_transport_acquire
The acceptor does not have to acquire its transports for the CIS to
be established, as `bluetoothd` sets up the ISO listener on its own
when the stream is enabled.
+
+BROADCAST
+=========
+
+Two hosts, with no connection between them:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | extended + | host1 |
+ | Broadcast Source | periodic | Broadcast Sink |
+ | bluetoothctl -a auto | advertising | bluetoothctl -a auto |
+ | broadcast-source.bt | --------------> | broadcast-sink.bt |
+ | or -pbp variant | | |
+ | BCAA endpoint (0x1852) | BIS 0 (LC3) | BAA endpoint (0x1851) |
+ | | ==============> | |
+ +------------------------+ +------------------------+
+
+ no ACL is established: the sink syncs to the periodic advertising,
+ reads the BASE from it and then syncs to the BIG
+
+ --> advertising is scanned by ==> audio flows towards
+
+``client/scripts/broadcast-source.bt`` on host0
+ Registers a Broadcast Source endpoint
+ (``00001852-0000-1000-8000-00805f9b34fb``) with LC3, configures it
+ with the 16_2_1 preset and acquires the transport, which starts
+ the broadcast. The stream is encrypted with the broadcast code
+ `bluetoothctl` uses by default.
+
+``client/scripts/broadcast-source-pbp.bt`` on host0
+ As above, but adds the Public Broadcast Announcement service
+ (``0x1856``) to the extended advertising first, so the broadcast
+ is a Public Broadcast Profile one.
+
+``client/scripts/broadcast-sink.bt`` on host1
+ Registers a Broadcast Sink endpoint
+ (``00001851-0000-1000-8000-00805f9b34fb``) with LC3 and scans.
+
+Both test cases run for each source, i.e. with the ``lc3`` parameter
+for a plain broadcast and with ``pbp`` for a Public Broadcast Profile
+one.
+
+test_bap_broadcast_transport_created[lc3|pbp]
+---------------------------------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Start `bluetoothctl` with the source script on host0.
+ 2. Start `bluetoothctl` with the sink script on host1.
+ 3. Sink: ``transport.show <transport>``.
+
+:Expected:
+ 1. ``Endpoint /local/endpoint/ep0 registered``, then
+ ``Acquire successful: fd <fd> MTU <read>:<write>`` on the
+ source, i.e. it is broadcasting.
+ 2. The sink syncs to the periodic advertising on its own and
+ creates a transport per BIS described by the BASE, under
+ ``/org/bluez/hci0/dev_XX/sidN/bisM/fdK``.
+ 3. The transport reports ``Codec: 0x06`` for LC3 and
+ ``State: idle``.
+
+:Notes: The sink does not need a Broadcast Assistant here: it scans,
+ finds the Broadcast Source and syncs by itself.
+
+test_bap_broadcast_transport_acquire[lc3|pbp]
+---------------------------------------------
+
+:Setup: As above, with the transport already created.
+
+:Steps:
+ 1. Sink: ``transport.select <transport>``.
+ 2. Answer ``Enter bcode[value/no]:`` with the broadcast code the
+ source used.
+
+:Expected:
+ 1. The transport moves to ``State: broadcasting``, i.e. the sink
+ synced to the BIG.
+ 2. ``Acquire successful: fd <fd> MTU <read>:<write>`` and the
+ transport moves to ``State: active``.
+
+:Notes: Selecting the transport is what moves it out of idle, and
+ `bluetoothctl` starts acquiring it right after, so the test does
+ not issue ``transport.acquire`` itself. The broadcast code has to
+ match the one the source encrypted the BIG with, otherwise the
+ sink cannot decrypt the stream.
diff --git a/test/functional/test_bap.py b/test/functional/test_bap.py
index 69430d7a6699..6b62338ac676 100644
--- a/test/functional/test_bap.py
+++ b/test/functional/test_bap.py
@@ -167,3 +167,71 @@ def test_bap_unicast_transport_acquire(unicast_hosts):
f"Transport {right} State: active",
],
)
+
+
+# Broadcast code used by the broadcast scripts, see BCAST_CODE in
+# client/player.c
+BCAST_CODE = (
+ "0x01 0x02 0x68 0x05 0x53 0xf1 0x41 0x5a " "0xa2 0x65 0xbb 0xaf 0xc6 0xea 0x03 0xb8"
+)
+
+BCAST_SOURCES = ["broadcast-source.bt", "broadcast-source-pbp.bt"]
+BCAST_IDS = ["lc3", "pbp"]
+
+broadcast_host_config = host_config(
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+)
+
+
+def start_broadcast(hosts, source_script):
+ """
+ Source broadcasting with the given script, and sink scanning for it.
+ """
+ source_host, sink_host = hosts
+
+ source = start_bluetoothctl(source_host, source_script)
+ source.expect(r"Acquire successful: fd \d+ MTU \d+:\d+")
+
+ sink = start_bluetoothctl(sink_host, "broadcast-sink.bt")
+
+ return source, sink
+
+
+@broadcast_host_config
+@pytest.mark.parametrize("source_script", BCAST_SOURCES, ids=BCAST_IDS)
+def test_bap_broadcast_transport_created(hosts, source_script):
+ source, sink = start_broadcast(hosts, source_script)
+
+ # Sink syncs to the periodic advertising and creates a transport
+ # for each BIS described by the BASE
+ _, m = sink.expect(TRANSPORT_RE)
+ transport = m[0].decode("utf-8")
+
+ sink.send(f"transport.show {transport}\n")
+ sink.expect(f"Transport {transport}")
+ sink.expect(r"Codec: 0x06")
+ sink.expect("State: idle")
+
+
+@broadcast_host_config
+@pytest.mark.parametrize("source_script", BCAST_SOURCES, ids=BCAST_IDS)
+def test_bap_broadcast_transport_acquire(hosts, source_script):
+ source, sink = start_broadcast(hosts, source_script)
+
+ _, m = sink.expect(TRANSPORT_RE)
+ transport = m[0].decode("utf-8")
+
+ # Selecting the transport syncs to the BIG and starts acquiring it
+ sink.send(f"transport.select {transport}\n")
+ sink.expect(r"Enter bcode\[value/no\]:")
+ sink.send(f"{BCAST_CODE}\n")
+
+ expect_all(
+ sink,
+ [
+ f"Transport {transport} State: broadcasting",
+ r"Acquire successful: fd \d+ MTU \d+:\d+",
+ f"Transport {transport} State: active",
+ ],
+ )
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 11/12] test: functional: add BAP broadcast assistant test
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (9 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 10/12] test: functional: add BAP broadcast tests Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 12/12] bap: reuse the PA sync established to discover a Broadcast Source Luiz Augusto von Dentz
2026-09-10 20:50 ` [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP patchwork-bot+bluetooth
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add a test covering the Broadcast Assistant sharing a broadcast with a
Scan Delegator, with the Broadcast Source and the Broadcast Assistant
colocated so the assistant shares its own broadcast. The delegator
receives the periodic advertising sync over the connection to the
assistant (PAST), instead of scanning the source itself.
The topology where the assistant relays a source it scanned, which
does not use PAST, needs the assistant on a third host and can be
added later.
Assisted-by: opencode:claude-opus-5
---
doc/functional-bap.rst | 94 +++++++++++++++++++++++++++++
test/functional/test_bap.py | 115 ++++++++++++++++++++++++++++++------
2 files changed, 192 insertions(+), 17 deletions(-)
diff --git a/doc/functional-bap.rst b/doc/functional-bap.rst
index 99a3d63eef68..f9392bd2dbce 100644
--- a/doc/functional-bap.rst
+++ b/doc/functional-bap.rst
@@ -194,3 +194,97 @@ test_bap_broadcast_transport_acquire[lc3|pbp]
not issue ``transport.acquire`` itself. The broadcast code has to
match the one the source encrypted the BIG with, otherwise the
sink cannot decrypt the stream.
+
+BROADCAST ASSISTANT
+===================
+
+Two hosts, with the Broadcast Source and the Broadcast Assistant
+colocated on host0, sharing its own broadcast with the Scan Delegator:
+
+.. code-block::
+
+ +------------------------+ +------------------------+
+ | host0 | | host1 |
+ | Broadcast Source | extended + | Scan Delegator |
+ | + Broadcast Assistant | periodic adv | (Broadcast Sink) |
+ | bluetoothctl -a auto | --------------> | bluetoothctl -a auto |
+ | broadcast-source.bt | | broadcast-delegator.bt |
+ | BCAA endpoint (0x1852) | BIG (BIS 1) | BAA endpoint (0x1851) |
+ | | ==============> | |
+ | | ACL, BASS, PAST | |
+ | | --------------> | |
+ +------------------------+ +------------------------+
+
+ --> advertising is scanned by ==> audio flows towards
+
+ The assistant connects to the delegator over ACL and shares the
+ local broadcast with it: the delegator receives the periodic
+ advertising sync over that connection (PAST), rather than scanning
+ the source itself.
+
+``client/scripts/broadcast-source.bt`` on host0
+ Registers a Broadcast Source endpoint
+ (``00001852-0000-1000-8000-00805f9b34fb``) with LC3, configures it
+ with the 16_2_1 preset and acquires the transport, which starts
+ the broadcast. The stream is encrypted with the broadcast code
+ `bluetoothctl` uses by default.
+
+``client/scripts/broadcast-delegator.bt`` on host1
+ Registers a Broadcast Sink endpoint
+ (``00001851-0000-1000-8000-00805f9b34fb``) with LC3, enables
+ automatic transport selection and acquisition, and advertises, so
+ the Broadcast Assistant can discover it and connect.
+
+The local broadcast of host0 is exposed as a MediaAssistant object in
+the ``local`` state, under the adapter path, e.g.
+``/org/bluez/hci0/sid0/bis1``. The push is driven through the commands
+of the assistant submenu, see **bluetoothctl-assistant(1)**.
+
+test_bass_past_transport_acquire
+--------------------------------
+
+:Setup: As above.
+
+:Steps:
+ 1. Start `bluetoothctl` with the source script on host0 and the
+ delegator script on host1.
+ 2. Assistant: ``scan on``, wait for the delegator device, then
+ ``connect`` it.
+ 3. Assistant: ``assistant.push <local assistant path>``, answering
+ the device prompt with the delegator device path and, if asked,
+ the broadcast code prompt with the code the stream is encrypted
+ with.
+
+:Expected:
+ 1. ``Acquire successful: fd <fd> MTU <read>:<write>`` on the
+ source, i.e. it is broadcasting, and the local stream is
+ exposed as ``[NEW] Assistant <adapter>/sid0/bis1``. On the
+ delegator, ``Advertising object registered``.
+ 2. ``Connection successful``, with the delegator authorizing the
+ assistant.
+ 3. ``Assistant <path> pushed``.
+ 4. On the delegator a transport is created for the BIS, and
+ selected and acquired automatically, reaching
+ ``State: broadcasting``, i.e. it synced to the BIG, and then
+ ``State: active``.
+
+:Notes: The delegator does not scan the source: it syncs to the
+ periodic advertising over the ACL to the assistant, as pushing a
+ local stream requests PAST. When instead the assistant scans and
+ relays a *remote* source, it shares the stream without PAST and
+ the delegator has to sync by scanning itself; that topology, with
+ the assistant on a third host, is left to be added later.
+
+ The broadcast code of the local stream is handed to the delegator
+ by the push, so its automatic transport selection does not have to
+ prompt for it, and the push itself is only asked for the device to
+ share the stream with.
+
+ The delegator is paired first: the Broadcast Receive State
+ characteristic requires an encrypted link, and without it the
+ assistant fails to read it and the push is rejected with
+ ``org.bluez.Error.InvalidArguments``.
+
+ The stream is verified on the delegator rather than through the
+ state of the MediaAssistant object, as an object created for a
+ local stream stays in the ``local`` state.
diff --git a/test/functional/test_bap.py b/test/functional/test_bap.py
index 6b62338ac676..bc9bcfb83ad8 100644
--- a/test/functional/test_bap.py
+++ b/test/functional/test_bap.py
@@ -34,13 +34,18 @@ def dev_addr(host):
def expect_all(ctl, patterns):
"""
- Expect all the given patterns, in any order.
+ Expect all the given patterns, in any order, returning the groups
+ each of them matched.
"""
- patterns = list(patterns)
+ pending = list(enumerate(patterns))
+ groups = [None] * len(patterns)
- while patterns:
- idx, _ = ctl.expect(patterns)
- patterns.pop(idx)
+ while pending:
+ idx, m = ctl.expect([pattern for _, pattern in pending])
+ groups[pending[idx][0]] = m
+ pending.pop(idx)
+
+ return groups
def script(name):
@@ -63,28 +68,39 @@ def start_bluetoothctl(host, init_script):
return ctl
-def pair_le(host0, ctl0, host1, ctl1):
+def pair_le(host0, ctl0, host1, ctl1, advertise=True, services=False):
ctl0.send("scan on\n")
ctl0.expect(f"Controller {host0.bdaddr.upper()} Discovering: yes")
- ctl1.send("advertise on\n")
- ctl1.expect("Advertising object registered")
+ if advertise:
+ ctl1.send("advertise on\n")
+ ctl1.expect("Advertising object registered")
ctl0.expect(f"Device {host1.bdaddr.upper()}")
ctl0.send(f"pair {host1.bdaddr.upper()}\n")
+ pending = ["Pairing successful"]
+ if services:
+ pending.append(f"Device {host1.bdaddr.upper()} ServicesResolved: yes")
+
# See test_bluetoothctl_pair_le: passkey confirmation is handled by
# the auto agent, but legacy passkey entry still needs an answer
- idx, m = ctl0.expect([r"\[agent\].*Passkey:.*m(\d+)", "Pairing successful"])
+ legacy = r"\[agent\].*Passkey:.*m(\d+)"
- if idx == 0:
- warnings.warn(
- "BUG: we got passkey authentication, bluetoothd/kernel should be fixed"
- )
- key = m[0].decode("utf-8")
- ctl1.expect(r"\[agent\] Enter passkey \(number in 0-999999\):")
- ctl1.send(f"{key}\n")
- ctl0.expect("Pairing successful")
+ while pending:
+ idx, m = ctl0.expect([legacy] + pending)
+
+ if idx == 0:
+ warnings.warn(
+ "BUG: we got passkey authentication, bluetoothd/kernel "
+ "should be fixed"
+ )
+ key = m[0].decode("utf-8")
+ ctl1.expect(r"\[agent\] Enter passkey \(number in 0-999999\):")
+ ctl1.send(f"{key}\n")
+ continue
+
+ pending.pop(idx - 1)
ctl0.send("scan off\n")
@@ -235,3 +251,68 @@ def test_bap_broadcast_transport_acquire(hosts, source_script):
f"Transport {transport} State: active",
],
)
+
+
+past_host_config = host_config(
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+ [Bluetoothd(conf=BAP_CONF), Pexpect()],
+)
+
+LOCAL_ASSISTANT_RE = r"Assistant (/org/bluez/\S+/sid\d+/bis\d+)"
+
+
+@past_host_config
+def test_bass_past_transport_acquire(hosts):
+ source_host, delegator_host = hosts
+
+ # Source broadcasting, and its own stream exposed as a local
+ # MediaAssistant object
+ source = start_bluetoothctl(source_host, "broadcast-source.bt")
+ groups = expect_all(
+ source,
+ [LOCAL_ASSISTANT_RE, r"Acquire successful: fd \d+ MTU \d+:\d+"],
+ )
+ assistant_path = groups[0][0].decode("utf-8")
+
+ # Delegator advertising, selecting and acquiring automatically
+ delegator = start_bluetoothctl(delegator_host, "broadcast-delegator.bt")
+ delegator.expect("Advertising object registered")
+
+ # Pair with the delegator: the Broadcast Receive State requires
+ # an encrypted link to be read
+ pair_le(
+ source_host,
+ source,
+ delegator_host,
+ delegator,
+ advertise=False,
+ services=True,
+ )
+
+ # Share the local broadcast: the delegator receives the periodic
+ # advertising sync over the connection (PAST)
+ source.send(f"assistant.push {assistant_path}\n")
+ source.expect(r"Enter Device \(path\):")
+ source.send(f"/org/bluez/hci0/dev_{dev_addr(delegator_host)}\n")
+
+ # The local stream may already know the broadcast code
+ idx, _ = source.expect(
+ [r"Enter Broadcast Code \(auto/value\):", r"Assistant \S+ pushed"]
+ )
+ if idx == 0:
+ source.send(f"{BCAST_CODE}\n")
+ source.expect(r"Assistant \S+ pushed")
+
+ # A transport is created on the delegator, selected and acquired
+ # automatically
+ _, m = delegator.expect(TRANSPORT_RE)
+ transport = m[0].decode("utf-8")
+
+ expect_all(
+ delegator,
+ [
+ r"Acquire successful: fd \d+ MTU \d+:\d+",
+ f"Transport {transport} State: broadcasting",
+ f"Transport {transport} State: active",
+ ],
+ )
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH BlueZ v1 12/12] bap: reuse the PA sync established to discover a Broadcast Source
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (10 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 11/12] test: functional: add BAP broadcast assistant test Luiz Augusto von Dentz
@ 2026-09-09 19:23 ` Luiz Augusto von Dentz
2026-09-10 20:50 ` [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP patchwork-bot+bluetooth
12 siblings, 0 replies; 15+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-09 19:23 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
A Broadcast Sink creates a short lived PA sync to read the BASE of a
Broadcast Source, closes it, and creates a new one once a stream is
enabled, as syncing to the BIG requires a PA sync. The second sync has
to be established again before the BIG sync can be requested, which
delays the start of the stream.
Keep the sync of the BIG Info report around instead, and reuse it, but
only while there is nothing else to discover, as a sync holds resources
that are needed to discover other Broadcast Sources: it is released as
soon as another source is probed, and after a grace timeout if no
stream is enabled in the meantime.
Assisted-by: opencode:claude-opus-5
---
profiles/audio/bap.c | 115 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 107 insertions(+), 8 deletions(-)
diff --git a/profiles/audio/bap.c b/profiles/audio/bap.c
index ff8f58a12091..5771c9ae0fdf 100644
--- a/profiles/audio/bap.c
+++ b/profiles/audio/bap.c
@@ -139,6 +139,8 @@ struct bap_data {
struct queue *bcast_snks;
struct queue *server_streams;
GIOChannel *listen_io;
+ bool pa_synced;
+ unsigned int pa_timer;
unsigned int io_id;
unsigned int cig_update_id;
bool services_ready;
@@ -176,6 +178,9 @@ static void bap_data_free(struct bap_data *data)
{
struct queue *bcast_snks = data->bcast_snks;
+ if (data->pa_timer)
+ g_source_remove(data->pa_timer);
+
if (data->listen_io) {
g_io_channel_shutdown(data->listen_io, TRUE, NULL);
g_io_channel_unref(data->listen_io);
@@ -1560,6 +1565,63 @@ static void bis_handler(uint8_t sid, uint8_t bis, uint8_t sgrp,
path);
}
+/* Time a PA sync is kept around waiting for a stream to be enabled,
+ * before it is released.
+ */
+#define PA_SYNC_GRACE_TIMEOUT 5
+
+static void pa_sync_release(struct bap_data *data)
+{
+ if (!data->pa_synced)
+ return;
+
+ DBG("Release PA sync");
+
+ if (data->pa_timer) {
+ g_source_remove(data->pa_timer);
+ data->pa_timer = 0;
+ }
+
+ if (data->listen_io) {
+ g_io_channel_shutdown(data->listen_io, TRUE, NULL);
+ g_io_channel_unref(data->listen_io);
+ data->listen_io = NULL;
+ }
+
+ data->pa_synced = false;
+}
+
+static gboolean pa_sync_timeout(gpointer user_data)
+{
+ struct bap_data *data = user_data;
+
+ data->pa_timer = 0;
+ pa_sync_release(data);
+
+ return FALSE;
+}
+
+static bool pa_sync_pending(const void *data, const void *match_data)
+{
+ const struct bap_data *bdata = data;
+
+ if (bdata == match_data)
+ return false;
+
+ /* A session with a listener that has not synced yet is still
+ * discovering a Broadcast Source.
+ */
+ return bdata->listen_io && !bdata->pa_synced;
+}
+
+static void pa_sync_release_session(void *data, void *user_data)
+{
+ struct bap_data *bdata = data;
+
+ if (bdata != user_data)
+ pa_sync_release(bdata);
+}
+
static gboolean big_info_report_cb(GIOChannel *io, GIOCondition cond,
gpointer user_data)
{
@@ -1591,10 +1653,21 @@ static gboolean big_info_report_cb(GIOChannel *io, GIOCondition cond,
g_io_channel_unref(data->listen_io);
data->listen_io = NULL;
- /* For short-lived PA, the sync is no longer needed at
- * this point, so the io can be closed.
- */
- g_io_channel_shutdown(io, TRUE, NULL);
+ if (queue_find(sessions, pa_sync_pending, data)) {
+ /* Other Broadcast Sources are still being discovered, so the
+ * sync is closed to not hold the resources needed for them.
+ */
+ g_io_channel_shutdown(io, TRUE, NULL);
+ } else {
+ /* Nothing else to discover: keep the sync for a while, as
+ * syncing to the BIG requires one, so it does not have to be
+ * established again if a stream is enabled.
+ */
+ data->listen_io = g_io_channel_ref(io);
+ data->pa_synced = true;
+ data->pa_timer = g_timeout_add_seconds(PA_SYNC_GRACE_TIMEOUT,
+ pa_sync_timeout, data);
+ }
/* Analyze received BASE data and create remote media endpoints for each
* BIS matching our capabilities
@@ -3592,6 +3665,11 @@ static int pa_sync(struct bap_data *data)
DBG("Create PA sync with this source");
+ /* Release any sync kept by another session, as it is not needed
+ * to discover this source.
+ */
+ queue_foreach(sessions, pa_sync_release_session, data);
+
data->listen_io = bt_io_listen(NULL, iso_pa_sync_confirm_cb, data,
NULL, &err,
BT_IO_OPT_SOURCE_BDADDR,
@@ -3662,10 +3740,12 @@ static gboolean iso_do_big_sync(GIOChannel *io, GIOCondition cond,
DBG("BIG info received, do BIG sync");
- g_io_channel_unref(data->listen_io);
- g_io_channel_shutdown(data->listen_io, TRUE, NULL);
- data->listen_io = io;
- g_io_channel_ref(data->listen_io);
+ if (data->listen_io != io) {
+ g_io_channel_unref(data->listen_io);
+ g_io_channel_shutdown(data->listen_io, TRUE, NULL);
+ data->listen_io = io;
+ g_io_channel_ref(data->listen_io);
+ }
/* Append each linked BIS to the BIG sync request */
append_setup(setup->stream, &iso_bc_addr);
@@ -3714,6 +3794,25 @@ static void pa_and_big_sync(struct bap_setup *setup)
{
GError *err = NULL;
struct bap_data *bap_data = setup->data;
+
+ if (bap_data->pa_synced) {
+ DBG("Reuse PA sync with this source");
+
+ /* The sync is in use from now on, so it is not released
+ * while the BIG sync is being set up.
+ */
+ bap_data->pa_synced = false;
+
+ if (bap_data->pa_timer) {
+ g_source_remove(bap_data->pa_timer);
+ bap_data->pa_timer = 0;
+ }
+
+ bap_data->io_id = g_io_add_watch(bap_data->listen_io, G_IO_OUT,
+ iso_do_big_sync, setup);
+ return;
+ }
+
DBG("Create PA sync with this source");
bap_data->listen_io = bt_io_listen(NULL, long_pa_sync_confirm_cb, setup,
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* RE: Add functional tests for A2DP and BAP
2026-09-09 19:22 ` [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST Luiz Augusto von Dentz
@ 2026-09-10 18:28 ` bluez.test.bot
0 siblings, 0 replies; 15+ messages in thread
From: bluez.test.bot @ 2026-09-10 18:28 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 1261 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1161557/
---Test result---
Test Summary:
CheckPatch PASS 4.46 seconds
GitLint FAIL 3.25 seconds
BuildEll PASS 19.50 seconds
BluezMake PASS 369.07 seconds
MakeCheck PASS 14.09 seconds
MakeDistcheck PASS 141.29 seconds
CheckValgrind PASS 230.79 seconds
CheckSmatch PASS 283.43 seconds
bluezmakeextell PASS 92.19 seconds
IncrementalBuild PASS 432.57 seconds
ScanBuild PASS 1019.97 seconds
Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v1,12/12] bap: reuse the PA sync established to discover a Broadcast Source
1: T1 Title exceeds max length (82>80): "[BlueZ,v1,12/12] bap: reuse the PA sync established to discover a Broadcast Source"
https://github.com/bluez/bluez/pull/2516
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
` (11 preceding siblings ...)
2026-09-09 19:23 ` [PATCH BlueZ v1 12/12] bap: reuse the PA sync established to discover a Broadcast Source Luiz Augusto von Dentz
@ 2026-09-10 20:50 ` patchwork-bot+bluetooth
12 siblings, 0 replies; 15+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-10 20:50 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Wed, 9 Sep 2026 15:22:56 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> This adds functional tests for A2DP and BAP, driven through
> bluetoothctl, together with the endpoint registration scripts they use
> and the documentation describing what each of them does.
>
> The test documentation is reworked to describe every case as setup,
> steps, expected outcome and notes, so a test can be reproduced and
> reviewed without reading its source, and the reason behind the way it
> is written is not lost. The setup of each case includes a topology
> diagram showing how many hosts are used and the role each of them
> takes. Tests of a profile need more context than the core ones, so they
> are documented separately, in doc/functional-<profile>.rst.
>
> [...]
Here is the summary with links:
- [BlueZ,v1,01/12] build: add doc/test-functional.rst to EXTRA_DIST
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=8df6386b97c8
- [BlueZ,v1,02/12] doc: describe the functional test cases
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=fb3457bcd1e5
- [BlueZ,v1,03/12] client: do not prompt for LE Audio settings on A2DP endpoints
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=45b123f3a9c2
- [BlueZ,v1,04/12] client: add A2DP endpoint registration scripts
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=23fac1a310c0
- [BlueZ,v1,05/12] test: functional: add A2DP tests
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a888c89ead5a
- [BlueZ,v1,06/12] client: rename media endpoint scripts to include the codec
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=d6d9a13775af
- [BlueZ,v1,07/12] client: add BAP endpoint registration scripts
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=304e388070f5
- [BlueZ,v1,08/12] doc: bluetoothctl: document init script option and scripts
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=2fccbd5040e9
- [BlueZ,v1,09/12] test: functional: add BAP unicast tests
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=a60d81439eab
- [BlueZ,v1,10/12] test: functional: add BAP broadcast tests
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=8c99eaa01d53
- [BlueZ,v1,11/12] test: functional: add BAP broadcast assistant test
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=4f77841df4ac
- [BlueZ,v1,12/12] bap: reuse the PA sync established to discover a Broadcast Source
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=61a2b3d93897
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-10 20:51 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09 19:22 [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 01/12] build: add doc/test-functional.rst to EXTRA_DIST Luiz Augusto von Dentz
2026-09-10 18:28 ` Add functional tests for A2DP and BAP bluez.test.bot
2026-09-09 19:22 ` [PATCH BlueZ v1 02/12] doc: describe the functional test cases Luiz Augusto von Dentz
2026-09-09 19:22 ` [PATCH BlueZ v1 03/12] client: do not prompt for LE Audio settings on A2DP endpoints Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 04/12] client: add A2DP endpoint registration scripts Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 05/12] test: functional: add A2DP tests Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 06/12] client: rename media endpoint scripts to include the codec Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 07/12] client: add BAP endpoint registration scripts Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 08/12] doc: bluetoothctl: document init script option and scripts Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 09/12] test: functional: add BAP unicast tests Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 10/12] test: functional: add BAP broadcast tests Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 11/12] test: functional: add BAP broadcast assistant test Luiz Augusto von Dentz
2026-09-09 19:23 ` [PATCH BlueZ v1 12/12] bap: reuse the PA sync established to discover a Broadcast Source Luiz Augusto von Dentz
2026-09-10 20:50 ` [PATCH BlueZ v1 00/12] Add functional tests for A2DP and BAP patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox