From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: Marcel Holtmann <marcel@holtmann.org>,
Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Subject: [RFC PATCH v2] l2cap-tester: Check compatible interval preferences
Date: Sat, 31 Oct 2026 19:37:45 +0000 [thread overview]
Message-ID: <20261031193734.189558-1-cochcoder@proton.me> (raw)
In-Reply-To: <20261031181244.123461-1-cochcoder@proton.me>
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
Assisted-by: Codex:GPT-6
---
Changes in v2:
- Use the tester I/O wrapper introduced since the original test was written.
- Wrap the long note reported by GitLint.
Companion Linux RFC v3:
https://lore.kernel.org/r/20261015181041.42873-1-cochcoder@proton.me
The overlap test requires the Linux soft-preference RFC. Its acceptance is
not established. With the earlier test source, the saved guest controls
passed 4/4 on that RFC, 3/4 on the unpatched kernel, and 2/4 with a strict
intersection control. The full guest L2CAP suite passed 115/115 with the
soft-preference kernel. These results predate the tester I/O conversion.
On BlueZ master 14bd6ab65f4a, this revision builds with bundled and external
ELL, passes all 35 ordinary unit tests, and passes checkpatch. A socketpair
control verifies that the tester wrapper keeps the socket until queue teardown.
The revised native interval tests have not been rerun: the saved guest panics
at init without KVM in this environment. No real-controller testing is claimed.
tools/l2cap-tester.c | 159 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 159 insertions(+)
diff --git a/tools/l2cap-tester.c b/tools/l2cap-tester.c
index 4002ca5..758aeba 100644
--- a/tools/l2cap-tester.c
+++ b/tools/l2cap-tester.c
@@ -56,6 +56,7 @@ struct test_data {
int step;
struct queue *io_channels;
struct tx_tstamp_data tx_ts;
+ unsigned int interval_events;
};
struct l2cap_conn_subrate {
@@ -134,6 +135,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)
@@ -3522,6 +3524,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);
+ struct tester_io *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 = tester_io_new(sk);
+ tester_io_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();
@@ -3533,6 +3683,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,
--
2.56.0
next prev parent reply other threads:[~2026-10-31 19:37 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 18:13 [RESEND RFC PATCH] l2cap-tester: Check compatible interval preferences Kyle Pepper
2026-10-31 18:58 ` [RESEND,RFC] " bluez.test.bot
2026-10-31 19:37 ` Kyle Pepper [this message]
2026-10-31 22:56 ` [RFC,v2] " bluez.test.bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261031193734.189558-1-cochcoder@proton.me \
--to=cochcoder@proton.me \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.