Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions include/zephyr/bluetooth/classic/sdp.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,24 @@ struct bt_sdp_record {
*/
int bt_sdp_register_service(struct bt_sdp_record *service);

/** @brief Unregister a Service Record.
*
* Remove a previously registered Service Record from the SDP database.
* The function checks for active SDP L2CAP channels and returns -EBUSY
* if any exist. A residual race window remains between the check and
* the actual removal; the caller is responsible for ensuring no new SDP
* connections are established during this call (e.g., by disconnecting
* BR/EDR links or disabling connectable mode beforehand).
*
* @param service Service record to unregister.
*
* @return 0 in case of success or negative value in case of error.
* @retval -EINVAL if @p service is NULL.
* @retval -EBUSY if an SDP L2CAP channel is currently active.
* @retval -ENOENT if @p service is not currently registered.
*/
int bt_sdp_unregister_service(struct bt_sdp_record *service);

/* Client API */

/** @brief Generic SDP Client Query Result data holder */
Expand Down
40 changes: 29 additions & 11 deletions subsys/bluetooth/host/classic/sdp.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ struct bt_sdp {
};

static sys_slist_t sdp_db = SYS_SLIST_STATIC_INIT(&sdp_db);
static uint8_t num_services;

static struct bt_sdp bt_sdp_pool[CONFIG_BT_MAX_CONN];

Expand Down Expand Up @@ -1518,15 +1517,13 @@ static uint16_t sdp_svc_search_att_req(struct bt_sdp *sdp, struct net_buf *buf,
if (state.pkt_full && !dry_run) {
LOG_DBG("Packet full, state.last_att %u", state.last_att);

if (state.current_svc < num_services) {
dry_run = true;
dry_run = true;

/* Add continuation state */
net_buf_add_u8(rsp_buf, SDP_SSA_CONT_STATE_SIZE);
net_buf_add_u8(rsp_buf, state.current_svc);
net_buf_add_u8(rsp_buf, state.last_att);
net_buf_add_be32(rsp_buf, state.last_att_index);
}
/* Add continuation state */
net_buf_add_u8(rsp_buf, SDP_SSA_CONT_STATE_SIZE);
net_buf_add_u8(rsp_buf, state.current_svc);
net_buf_add_u8(rsp_buf, state.last_att);
net_buf_add_be32(rsp_buf, state.last_att_index);

/* Break if it's not a partial response, else dry-run
* Dry run: Look for other services that match
Expand Down Expand Up @@ -1733,13 +1730,34 @@ int bt_sdp_register_service(struct bt_sdp_record *service)

sys_slist_append(&sdp_db, &service->node);

num_services++;

LOG_DBG("Service registered at %u", service->handle);

return 0;
}

int bt_sdp_unregister_service(struct bt_sdp_record *service)
{
if (service == NULL) {
return -EINVAL;
}

ARRAY_FOR_EACH(bt_sdp_pool, i) {
if (bt_sdp_pool[i].chan.chan.conn != NULL) {
LOG_WRN("Active SDP channel exists on conn %p",
(void *)bt_sdp_pool[i].chan.chan.conn);
return -EBUSY;
}
}

if (!sys_slist_find_and_remove(&sdp_db, &service->node)) {
return -ENOENT;
}

LOG_DBG("Service unregistered at %u", service->handle);

return 0;
}

static int sdp_client_discover(struct bt_sdp_client *session);

static void sdp_client_params_iterator(struct bt_sdp_client *session)
Expand Down
1 change: 1 addition & 0 deletions tests/bluetooth/classic/sdp_s/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ CONFIG_BT_CLASSIC=y
CONFIG_BT_SHELL=y
CONFIG_LOG=y
CONFIG_ZTEST=y
CONFIG_ZTEST_SHELL=y

CONFIG_BT_RFCOMM=y

Expand Down
76 changes: 76 additions & 0 deletions tests/bluetooth/classic/sdp_s/pytest/test_sdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,89 @@ async def sdp_discover_with_range(hci_port, shell, address) -> None:
assert len(search_result) == 0


async def sdp_unregister(hci_port, shell, address) -> None:
logger.info('<<< connect...')
async with await open_transport_or_link(hci_port) as hci_transport:
device = Device.with_hci(
'Bumble',
Address('F0:F1:F2:F3:F4:F5'),
hci_transport.source,
hci_transport.sink,
)

with open("bumble_hci_sdp_s_unregister.log", "wb") as snoop_file:
device.host.snooper = BtSnooper(snoop_file)
device.classic_enabled = True
device.le_enabled = False
await device_power_on(device)

target_address = address.split(" ")[0]
logger.info(f'=== Connecting to {target_address}...')
try:
connection = await device.connect(target_address, transport=BT_BR_EDR_TRANSPORT)
logger.info(f'=== Connected to {connection.peer_address}!')
except CommandTimeoutError as e:
logger.info('!!! Connection timed out')
raise e

# Register record 0
shell.exec_command("sdp_server register_sdp 0")

# Connect SDP and verify record is visible
sdp_client = SDP_Client(connection)
await sdp_client.connect()

logger.info("<<< 1 Verify record 0 is visible after register")
service_record_handles = await sdp_client.search_services([SDP_PUBLIC_BROWSE_ROOT])
logger.info(f'SERVICES: {service_record_handles}')
assert len(service_record_handles) == 1

# Disconnect SDP L2CAP channel so unregister can succeed
await sdp_client.disconnect()

# Unregister record 0
shell.exec_command("sdp_server unregister_sdp 0")

# Reconnect SDP and verify record is gone
await sdp_client.connect()

logger.info("<<< 2 Verify record 0 is gone after unregister")
service_record_handles = await sdp_client.search_services([SDP_PUBLIC_BROWSE_ROOT])
logger.info(f'SERVICES: {service_record_handles}')
assert len(service_record_handles) == 0

# Disconnect SDP again
await sdp_client.disconnect()

# Re-register record 0
shell.exec_command("sdp_server register_sdp 0")

# Reconnect SDP and verify record is back
await sdp_client.connect()

logger.info("<<< 3 Verify record 0 is visible after re-register")
service_record_handles = await sdp_client.search_services([SDP_PUBLIC_BROWSE_ROOT])
logger.info(f'SERVICES: {service_record_handles}')
assert len(service_record_handles) == 1

# Unregister to clean up for other tests
await sdp_client.disconnect()
shell.exec_command("sdp_server unregister_sdp 0")


class TestSdpServer:
def test_discovery_device(self, sdp_server_dut):
"""Test case to discover IUT"""
logger.info(f'test_discovery_device {sdp_server_dut}')
hci, iut_address = sdp_server_dut
asyncio.run(start_discovery(hci, iut_address))

def test_sdp_unregister(self, shell: Shell, dut: DeviceAdapter, sdp_server_dut):
"""Test case to unregister and re-register SDP records"""
logger.info(f'test_sdp_unregister {sdp_server_dut}')
hci, iut_address = sdp_server_dut
asyncio.run(sdp_unregister(hci, shell, iut_address))

def test_sdp_discover(self, shell: Shell, dut: DeviceAdapter, sdp_server_dut):
"""Test case to request SDP records"""
logger.info(f'test_sdp_discover {sdp_server_dut}')
Expand Down
26 changes: 26 additions & 0 deletions tests/bluetooth/classic/sdp_s/src/sdp_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,31 @@ static int cmd_register_sdp_all(const struct shell *sh, size_t argc, char *argv[
return 0;
}

static int cmd_unregister_sdp(const struct shell *sh, size_t argc, char *argv[])
{
int err = 0;
unsigned long index;

index = shell_strtoul(argv[1], 16, &err);
if (err || index >= MAX_SDP_RECORD_COUNT) {
shell_error(sh, "Invalid index %s", argv[1]);
return -EINVAL;
}

if (!sdp_rec_reg[index]) {
shell_error(sh, "The SDP record %lu is not registered", index);
return -ENOEXEC;
}

err = bt_sdp_unregister_service(&spp_rec[index]);
if (err != 0) {
shell_error(sh, "Unregister SDP record failed (err %d)", err);
} else {
sdp_rec_reg[index] = false;
}
return err;
}

static int cmd_register_sdp_large(const struct shell *sh, size_t argc, char *argv[])
{
int err;
Expand Down Expand Up @@ -372,6 +397,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sdp_server_cmds,
SHELL_CMD_ARG(register_sdp_large, NULL, "", cmd_register_sdp_large, 1, 0),
SHELL_CMD_ARG(register_sdp_large_valid, NULL, "", cmd_register_sdp_large_valid, 1, 0),
SHELL_CMD_ARG(register_sdp_uuid128, NULL, "", cmd_register_sdp_uuid128, 1, 0),
SHELL_CMD_ARG(unregister_sdp, NULL, "<SDP Record Index>", cmd_unregister_sdp, 2, 0),
SHELL_SUBCMD_SET_END
);

Expand Down
2 changes: 2 additions & 0 deletions tests/bluetooth/classic/sdp_s/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ tests:
harness_config:
pytest_dut_scope: session
fixture: usb_hci
extra_args:
- CONFIG_UART_NATIVE_PTY_0_ON_STDINOUT=y
timeout: 900
bluetooth.classic.sdp.server.no_blobs:
platform_allow:
Expand Down
Loading