* [RFC PATCH] l2cap-tester: Check compatible interval preferences
@ 2026-10-15 4:42 Kyle Pepper
2026-10-15 7:34 ` [RFC] " bluez.test.bot
0 siblings, 1 reply; 2+ messages in thread
From: Kyle Pepper @ 2026-10-15 4:42 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Marcel Holtmann, Luiz Augusto von Dentz
Load local connection preferences and check both the peer's L2CAP
response and the resulting HCI Connection Update range. Cover overlap,
fast HID and slow idle fallback, and an exact-range control.
Assisted-by: OpenCode:gpt-6.1-sol
---
Companion Linux RFC v2:
https://lore.kernel.org/linux-bluetooth/20261015043336.47734-1-cochcoder@proton.me/
This standalone BlueZ test patch requires that Linux soft-preference change
for the overlap case to pass. It is independent of ASHA STATUS, STOP, the
stream gate and credit changes. It does not add a BlueZ runtime fix.
Each case loads local preferences through MGMT, sends a peer L2CAP parameter
request, and checks both an accepted peer response and the selected HCI
Connection Update range. The cases cover overlap preference, fast HID and
slow idle outside the local preference, and an exact match.
Saved native controls:
- Unpatched kernel: 3/4, failing only the expected narrower-overlap selection.
- Strict-bound negative control: 2/4, rejecting the HID and idle requests.
- Soft-preference revision: 4/4; full reference-guest L2CAP suite 115/115,
without skips or captured KASAN/lockdep alerts.
This standalone source also passes 35/35 ordinary unit binaries. The newer
combined Arch package port passes all four interval controls on the saved
soft-preference guest, but that package port is not this submission's source.
No real-controller, firmware, acoustic, paired-device or remote-CI claim is
made for this standalone test patch. Archive rendering of the Linux link has
not been independently verified; confirm the thread before sending.
tools/l2cap-tester.c | 159 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index f2d3e56b87..a4701071e7 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -54,6 +54,7 @@ struct test_data {
int step;
struct queue *io_channels;
struct tx_tstamp_data tx_ts;
+ unsigned int interval_events;
};
struct l2cap_conn_subrate {
@@ -132,6 +133,7 @@ struct l2cap_data {
/* Change security level to this after connecting */
int change_sec_level;
+ const struct interval_test *interval;
};
static void print_debug(const char *str, void *user_data)
@@ -3497,6 +3499,154 @@ done:
close(sk);
}
+struct interval_test {
+ uint16_t local_min;
+ uint16_t local_max;
+ uint16_t peer_min;
+ uint16_t peer_max;
+ uint16_t expect_min;
+ uint16_t expect_max;
+};
+
+static const struct interval_test interval_cases[] = {
+ { 16, 16, 16, 24, 16, 16 },
+ { 24, 40, 6, 6, 6, 6 },
+ { 16, 24, 80, 160, 80, 160 },
+ { 16, 16, 16, 16, 16, 16 },
+};
+
+static const struct l2cap_data interval_data[] = {
+ { .interval = &interval_cases[0] },
+ { .interval = &interval_cases[1] },
+ { .interval = &interval_cases[2] },
+ { .interval = &interval_cases[3] },
+};
+
+static void interval_complete(struct test_data *data, unsigned int event)
+{
+ data->interval_events |= event;
+
+ if (data->interval_events == 3)
+ tester_test_passed();
+}
+
+static void interval_command(uint16_t opcode, const void *param, uint8_t len,
+ void *user_data)
+{
+ struct test_data *data = user_data;
+ const struct l2cap_data *l2data = data->test_data;
+ const struct interval_test *test = l2data->interval;
+ const struct bt_hci_cmd_le_conn_update *cp = param;
+
+ if (opcode != BT_HCI_CMD_LE_CONN_UPDATE)
+ return;
+
+ if (len != sizeof(*cp) ||
+ get_le16(&cp->min_interval) != test->expect_min ||
+ get_le16(&cp->max_interval) != test->expect_max ||
+ get_le16(&cp->latency) ||
+ get_le16(&cp->supv_timeout) != 600) {
+ tester_warn("Unexpected LE Connection Update parameters");
+ tester_test_failed();
+ return;
+ }
+
+ interval_complete(data, 1);
+}
+
+static void interval_response(uint8_t code, const void *param, uint16_t len,
+ void *user_data)
+{
+ struct test_data *data = user_data;
+
+ if (code != 0x13 || len != 2 || get_le16(param)) {
+ tester_warn("Connection Parameter Update was not accepted");
+ tester_test_failed();
+ return;
+ }
+
+ interval_complete(data, 2);
+}
+
+static void interval_connected(uint16_t handle, void *user_data)
+{
+ struct test_data *data = user_data;
+ const struct l2cap_data *l2data = data->test_data;
+ const struct interval_test *test = l2data->interval;
+ struct bthost *host = hciemu_client_get_host(data->hciemu);
+ uint8_t request[8];
+
+ put_le16(test->peer_min, request);
+ put_le16(test->peer_max, request + 2);
+ put_le16(0, request + 4);
+ put_le16(600, request + 6);
+
+ if (!bthost_l2cap_req(host, handle, 0x12, request, sizeof(request),
+ interval_response, data))
+ tester_test_failed();
+}
+
+static void interval_loaded(uint8_t status, uint16_t len, const void *param,
+ void *user_data)
+{
+ struct test_data *data = user_data;
+ struct bthost *host = hciemu_client_get_host(data->hciemu);
+ GIOChannel *io;
+ int sk;
+
+ if (status) {
+ tester_warn("Could not load connection preferences");
+ tester_test_failed();
+ return;
+ }
+
+ bthost_set_connect_cb(host, interval_connected, data);
+ bthost_add_l2cap_server(host, 0x0080, NULL, NULL, NULL);
+ hciemu_add_central_post_command_hook(data->hciemu,
+ interval_command, data);
+ sk = create_l2cap_sock(data, 0, 0, 0, BT_MODE_LE_FLOWCTL);
+
+ if (sk < 0) {
+ tester_test_failed();
+ return;
+ }
+
+ if (connect_l2cap_sock(data, sk, 0x0080, 0) < 0) {
+ close(sk);
+ tester_test_failed();
+ return;
+ }
+
+ io = g_io_channel_unix_new(sk);
+ g_io_channel_set_close_on_unref(io, TRUE);
+ data->io_channels = queue_new();
+ queue_push_tail(data->io_channels, io);
+}
+
+static void test_interval_preferences(const void *test_data)
+{
+ struct test_data *data = tester_get_data();
+ const struct l2cap_data *l2data = data->test_data;
+ const struct interval_test *test = l2data->interval;
+ uint8_t buf[sizeof(struct mgmt_cp_load_conn_param) +
+ sizeof(struct mgmt_conn_param)];
+ struct mgmt_cp_load_conn_param *cp = (void *) buf;
+ struct mgmt_conn_param *param = cp->params;
+
+ memset(buf, 0, sizeof(buf));
+ cp->param_count = cpu_to_le16(1);
+ memcpy(¶m->addr.bdaddr,
+ hciemu_get_client_bdaddr(data->hciemu), 6);
+ param->addr.type = BDADDR_LE_PUBLIC;
+ param->min_interval = cpu_to_le16(test->local_min);
+ param->max_interval = cpu_to_le16(test->local_max);
+ param->timeout = cpu_to_le16(600);
+
+ if (!mgmt_send(data->mgmt, MGMT_OP_LOAD_CONN_PARAM, data->mgmt_index,
+ sizeof(buf), buf, interval_loaded, data, NULL))
+ tester_test_failed();
+}
+
static void test_l2cap_ethtool_get_ts_info(const void *test_data)
{
struct test_data *data = tester_get_data();
@@ -3508,6 +3658,15 @@ int main(int argc, char *argv[])
{
tester_init(&argc, &argv);
+ test_l2cap_le("L2CAP Interval - Prefer overlap", &interval_data[0],
+ setup_powered_client, test_interval_preferences);
+ test_l2cap_le("L2CAP Interval - Fast HID fallback", &interval_data[1],
+ setup_powered_client, test_interval_preferences);
+ test_l2cap_le("L2CAP Interval - Slow idle fallback", &interval_data[2],
+ setup_powered_client, test_interval_preferences);
+ test_l2cap_le("L2CAP Interval - Exact match", &interval_data[3],
+ setup_powered_client, test_interval_preferences);
+
test_l2cap_bredr("Basic L2CAP Socket - Success", NULL,
setup_powered_client, test_basic);
test_l2cap_bredr("Non-connected getpeername - Failure", NULL,
base-commit: dd9d5ba1fa4f682b082b1275a8c38523d95a58fd
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-15 7:34 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 4:42 [RFC PATCH] l2cap-tester: Check compatible interval preferences Kyle Pepper
2026-10-15 7:34 ` [RFC] " bluez.test.bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox