/* * test-rfcomm.c * SPDX-FileCopyrightText: 2016-2025 BlueALSA developers * SPDX-License-Identifier: MIT */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include "a2dp.h" #include "ba-adapter.h" #include "ba-config.h" #include "ba-device.h" #include "ba-rfcomm.h" #include "ba-transport.h" #include "ba-transport-pcm.h" #include "bluealsa-dbus.h" #include "bluez.h" #include "hfp.h" #include "shared/log.h" #include "inc/check.inc" static struct ba_adapter *adapter = NULL; static struct ba_device *device1 = NULL; static struct ba_device *device2 = NULL; static pthread_mutex_t dbus_update_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t dbus_update_cond = PTHREAD_COND_INITIALIZER; static struct { unsigned int codec; unsigned int volume; unsigned int battery; } dbus_update_counters; static void dbus_update_counters_wait(unsigned int *counter, unsigned int value) { pthread_mutex_lock(&dbus_update_mtx); while (*counter != value) pthread_cond_wait(&dbus_update_cond, &dbus_update_mtx); pthread_mutex_unlock(&dbus_update_mtx); } int storage_device_load(const struct ba_device *d) { (void)d; return 0; } int storage_device_save(const struct ba_device *d) { (void)d; return 0; } int storage_pcm_data_sync(struct ba_transport_pcm *pcm) { (void)pcm; return 0; } int storage_pcm_data_update(const struct ba_transport_pcm *pcm) { (void)pcm; return 0; } int bluealsa_dbus_pcm_register(struct ba_transport_pcm *pcm) { debug("%s: %p", __func__, (void *)pcm); pcm->ba_dbus_exported = true; return 0; } void bluealsa_dbus_pcm_update(struct ba_transport_pcm *pcm, unsigned int mask) { debug("%s: %p %#x", __func__, (void *)pcm, mask); (void)pcm; pthread_mutex_lock(&dbus_update_mtx); dbus_update_counters.codec += !!(mask & BA_DBUS_PCM_UPDATE_CODEC); dbus_update_counters.volume += !!(mask & BA_DBUS_PCM_UPDATE_VOLUME); pthread_mutex_unlock(&dbus_update_mtx); pthread_cond_signal(&dbus_update_cond); } void bluealsa_dbus_pcm_unregister(struct ba_transport_pcm *pcm) { debug("%s: %p", __func__, (void *)pcm); (void)pcm; } int bluealsa_dbus_rfcomm_register(struct ba_rfcomm *r) { debug("%s: %p", __func__, (void *)r); (void)r; return 0; } void bluealsa_dbus_rfcomm_update(struct ba_rfcomm *r, unsigned int mask) { debug("%s: %p %#x", __func__, (void *)r, mask); (void)r; pthread_mutex_lock(&dbus_update_mtx); dbus_update_counters.battery += !!(mask & BA_DBUS_RFCOMM_UPDATE_BATTERY); pthread_mutex_unlock(&dbus_update_mtx); pthread_cond_signal(&dbus_update_cond); } void bluealsa_dbus_rfcomm_unregister(struct ba_rfcomm *r) { debug("%s: %p", __func__, (void *)r); (void)r; } bool bluez_a2dp_set_configuration(const char *current_dbus_sep_path, const struct a2dp_sep_config *sep, const void *configuration, GError **error) { debug("%s: %s: %p", __func__, current_dbus_sep_path, sep); (void)current_dbus_sep_path; (void)sep; (void)configuration; (void)error; return false; } void bluez_battery_provider_update(struct ba_device *device) { debug("%s: %p", __func__, device); (void)device; } #define ck_assert_rfcomm_recv(fd, command) { \ char buffer[sizeof(command)] = { 0 }; \ const ssize_t len = strlen(command); \ ck_assert_int_eq(read(fd, buffer, sizeof(buffer) - 1), len); \ ck_assert_str_eq(buffer, command); } #define ck_assert_rfcomm_send(fd, command) { \ const ssize_t len = strlen(command); \ ck_assert_int_eq(write(fd, command, len), len); } CK_START_TEST(test_rfcomm_hsp_ag) { int fds[2]; ck_assert_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); struct ba_transport *sco = ba_transport_new_sco(device1, BA_TRANSPORT_PROFILE_HSP_AG, ":test", "/sco", fds[0]); const int fd = fds[1]; /* check support for setting microphone gain */ ck_assert_rfcomm_send(fd, "AT+VGM=10\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 1); /* check support for setting speaker gain */ ck_assert_rfcomm_send(fd, "AT+VGS=13\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 2); /* check support for button press */ ck_assert_rfcomm_send(fd, "AT+CKPD=200\r"); ck_assert_rfcomm_recv(fd, "\r\nERROR\r\n"); /* check vendor-specific command for battery level notification */ ck_assert_rfcomm_send(fd, "AT+IPHONEACCEV=2,1,8,2,1\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.battery, 1); ba_transport_destroy(sco); close(fd); } CK_END_TEST CK_START_TEST(test_rfcomm_hsp_hs) { int fds[2]; ck_assert_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); struct ba_transport *sco = ba_transport_new_sco(device1, BA_TRANSPORT_PROFILE_HSP_HS, ":test", "/sco", fds[0]); const int fd = fds[1]; /* wait for initial microphone gain */ ck_assert_rfcomm_recv(fd, "AT+VGM=15\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* wait for initial speaker gain */ ck_assert_rfcomm_recv(fd, "AT+VGS=15\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* wait for initial vendor-specific battery level */ ck_assert_rfcomm_recv(fd, "AT+XAPL=DEAD-C0DE-1234,6\r"); ck_assert_rfcomm_send(fd, "\r\n+XAPL:TEST,6\r\n"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); ck_assert_rfcomm_recv(fd, "AT+IPHONEACCEV=2,1,8,2,0\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* check support for setting speaker gain */ ck_assert_rfcomm_send(fd, "\r\n+VGS=13\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 1); /* check support for setting microphone gain */ ck_assert_rfcomm_send(fd, "\r\n+VGM=10\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 2); /* check support for RING command */ ck_assert_rfcomm_send(fd, "\r\nRING\r\n"); usleep(5000); ba_transport_destroy(sco); close(fd); } CK_END_TEST #if ENABLE_HFP_CODEC_SELECTION static void *test_rfcomm_hfp_ag_switch_codecs(void *userdata) { struct ba_transport *sco = userdata; pthread_mutex_lock(&sco->codec_select_client_mtx); ck_assert_int_eq(ba_transport_select_codec_sco(sco, HFP_CODEC_CVSD), 0); /* The test code rejects first codec selection request for mSBC. */ ck_assert_int_eq(ba_transport_select_codec_sco(sco, HFP_CODEC_MSBC), -1); ck_assert_int_eq(ba_transport_select_codec_sco(sco, HFP_CODEC_MSBC), 0); pthread_mutex_unlock(&sco->codec_select_client_mtx); return NULL; } #endif CK_START_TEST(test_rfcomm_hfp_ag) { int fds[2]; ck_assert_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); struct ba_transport *sco = ba_transport_new_sco(device1, BA_TRANSPORT_PROFILE_HFP_AG, ":test", "/sco", fds[0]); const int fd = fds[1]; /* SLC initialization shall be started by the HF */ /* supported features exchange: volume, codec, eSCO */ ck_assert_int_eq(HFP_HF_FEAT_VOLUME | HFP_HF_FEAT_CODEC | HFP_HF_FEAT_ESCO, 656); ck_assert_rfcomm_send(fd, "AT+BRSF=656\r"); #if ENABLE_HFP_CODEC_SELECTION ck_assert_rfcomm_recv(fd, "\r\n+BRSF:2784\r\n"); #else ck_assert_rfcomm_recv(fd, "\r\n+BRSF:2272\r\n"); #endif ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); #if ENABLE_HFP_CODEC_SELECTION /* verify that AG supports codec negotiation and eSCO */ ck_assert_int_ne(2784 & (HFP_AG_FEAT_CODEC | HFP_AG_FEAT_ESCO), 0); /* codec negotiation */ #if ENABLE_MSBC && ENABLE_LC3_SWB ck_assert_rfcomm_send(fd, "AT+BAC=1,2,3\r"); #elif ENABLE_MSBC ck_assert_rfcomm_send(fd, "AT+BAC=1,2\r"); #elif ENABLE_LC3_SWB ck_assert_rfcomm_send(fd, "AT+BAC=1,3\r"); #endif ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); #endif /* AG indicators: retrieve supported indicators and their ordering */ ck_assert_rfcomm_send(fd, "AT+CIND=?\r"); ck_assert_rfcomm_recv(fd, "\r\n+CIND:(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))," "(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0,1)),(\"battchg\",(0-5))\r\n"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* AG indicators: retrieve the current status of the indicators */ ck_assert_rfcomm_send(fd, "AT+CIND?\r"); ck_assert_rfcomm_recv(fd, "\r\n+CIND:0,0,0,0,0,0,4\r\n"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* enable indicator status update */ ck_assert_rfcomm_send(fd, "AT+CMER=3,0,0,1\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* SLC has been established */ /* check support for setting microphone gain */ ck_assert_rfcomm_send(fd, "AT+VGM=10\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 1); /* check vendor-specific command for microphone mute */ ck_assert_rfcomm_send(fd, "AT+ANDROID=XHSMICMUTE,1\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 2); /* check vendor-specific command for ... */ ck_assert_rfcomm_send(fd, "AT+ANDROID=BOOM\r"); ck_assert_rfcomm_recv(fd, "\r\nERROR\r\n"); /* check support for setting speaker gain */ ck_assert_rfcomm_send(fd, "AT+VGS=13\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.volume, 3); #if ENABLE_HFP_CODEC_SELECTION /* request codec connection setup */ ck_assert_rfcomm_send(fd, "AT+BCC\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* wait for codec selection */ # if ENABLE_LC3_SWB ck_assert_rfcomm_recv(fd, "\r\n+BCS:3\r\n"); ck_assert_rfcomm_send(fd, "AT+BCS=3\r"); # elif ENABLE_MSBC ck_assert_rfcomm_recv(fd, "\r\n+BCS:2\r\n"); ck_assert_rfcomm_send(fd, "AT+BCS=2\r"); # else # error "Invalid codec configuration" # endif ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.codec, 2); #endif #if ENABLE_MSBC /* use internal API to select the codec */ GThread *th = g_thread_new(NULL, test_rfcomm_hfp_ag_switch_codecs, sco); ck_assert_rfcomm_recv(fd, "\r\n+BCS:1\r\n"); ck_assert_rfcomm_send(fd, "AT+BCS=1\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* reject codec selection request */ ck_assert_rfcomm_recv(fd, "\r\n+BCS:2\r\n"); ck_assert_rfcomm_send(fd, "AT+BAC=1,2\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); ck_assert_rfcomm_recv(fd, "\r\n+BCS:2\r\n"); ck_assert_rfcomm_send(fd, "AT+BCS=2\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); g_thread_join(th); #endif config.battery.level = 100; /* use internal API to update battery level */ ba_rfcomm_send_signal(sco->sco.rfcomm, BA_RFCOMM_SIGNAL_UPDATE_BATTERY); ck_assert_rfcomm_recv(fd, "\r\n+CIEV:7,5\r\n"); /* disable all indicators */ ck_assert_rfcomm_send(fd, "AT+BIA=0,0,0,0,0,0,0,0,0,0\r"); ck_assert_rfcomm_recv(fd, "\r\nOK\r\n"); /* battery level indicator shall not be reported */ ba_rfcomm_send_signal(sco->sco.rfcomm, BA_RFCOMM_SIGNAL_UPDATE_BATTERY); ck_assert_rfcomm_recv(fd, ""); const int level = -1000; struct ba_transport_pcm *pcm = &sco->sco.pcm_spk; pthread_mutex_lock(&pcm->mutex); ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL); pthread_mutex_unlock(&pcm->mutex); /* use internal API to update volume */ ba_transport_pcm_volume_sync(pcm, BA_DBUS_PCM_UPDATE_VOLUME); ck_assert_rfcomm_recv(fd, "\r\n+VGS:7\r\n"); ba_transport_destroy(sco); close(fd); } CK_END_TEST CK_START_TEST(test_rfcomm_hfp_hf) { int fds[2]; ck_assert_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); struct ba_transport *sco = ba_transport_new_sco(device1, BA_TRANSPORT_PROFILE_HFP_HF, ":test", "/sco", fds[0]); const int fd = fds[1]; /* SLC initialization shall be started by the HF */ /* supported features exchange */ #if ENABLE_HFP_CODEC_SELECTION ck_assert_rfcomm_recv(fd, "AT+BRSF=756\r"); #else ck_assert_rfcomm_recv(fd, "AT+BRSF=628\r"); #endif ck_assert_int_eq(HFP_AG_FEAT_CODEC | HFP_AG_FEAT_ESCO, 2560); ck_assert_rfcomm_send(fd, "\r\n+BRSF:2560\r\n"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); #if ENABLE_HFP_CODEC_SELECTION /* verify that HF supports codec negotiation and eSCO */ ck_assert_int_ne(756 & (HFP_HF_FEAT_CODEC | HFP_HF_FEAT_ESCO), 0); /* codec negotiation */ # if ENABLE_MSBC && ENABLE_LC3_SWB ck_assert_rfcomm_recv(fd, "AT+BAC=1,2,3\r"); # elif ENABLE_MSBC ck_assert_rfcomm_recv(fd, "AT+BAC=1,2\r"); # elif ENABLE_LC3_SWB ck_assert_rfcomm_recv(fd, "AT+BAC=1,3\r"); # endif ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); #endif /* AG indicators: retrieve supported indicators and their ordering */ ck_assert_rfcomm_recv(fd, "AT+CIND=?\r"); ck_assert_rfcomm_send(fd, "\r\n+CIND:(\"service\",(0,1)),(\"call\",(0,1)),(\"callsetup\",(0-3))," "(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0,1)),(\"battchg\",(0-5))\r\n"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* AG indicators: retrieve the current status of the indicators */ ck_assert_rfcomm_recv(fd, "AT+CIND?\r"); ck_assert_rfcomm_send(fd, "\r\n+CIND:0,0,0,0,0,0,4\r\n"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.battery, 1); /* enable indicator status update */ ck_assert_rfcomm_recv(fd, "AT+CMER=3,0,0,1,0\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* SLC has been established */ /* report service, current signal strength and battery level */ ck_assert_rfcomm_send(fd, "\r\n+CIEV:1,1\r\n"); ck_assert_rfcomm_send(fd, "\r\n+CIEV:5,4\r\n"); ck_assert_rfcomm_send(fd, "\r\n+CIEV:7,5\r\n"); dbus_update_counters_wait(&dbus_update_counters.battery, 2); /* wait for initial microphone gain */ ck_assert_rfcomm_recv(fd, "AT+VGM=15\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* wait for initial speaker gain */ ck_assert_rfcomm_recv(fd, "AT+VGS=15\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* wait for initial vendor-specific battery level */ ck_assert_rfcomm_recv(fd, "AT+XAPL=DEAD-C0DE-1234,6\r"); ck_assert_rfcomm_send(fd, "\r\n+XAPL:TEST,6\r\n"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); ck_assert_rfcomm_recv(fd, "AT+IPHONEACCEV=2,1,8,2,0\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); #if ENABLE_HFP_CODEC_SELECTION /* wait for codec selection request */ ck_assert_rfcomm_recv(fd, "AT+BCC\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); /* codec selection */ ck_assert_rfcomm_send(fd, "\r\n+BCS:1\r\n"); ck_assert_rfcomm_recv(fd, "AT+BCS=1\r"); ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.codec, 2); /* codec selection: initial codec */ ck_assert_rfcomm_send(fd, "\r\n+BCS:42\r\n"); # if ENABLE_MSBC && ENABLE_LC3_SWB ck_assert_rfcomm_recv(fd, "AT+BAC=1,2,3\r"); # elif ENABLE_MSBC ck_assert_rfcomm_recv(fd, "AT+BAC=1,2\r"); # elif ENABLE_LC3_SWB ck_assert_rfcomm_recv(fd, "AT+BAC=1,3\r"); # endif ck_assert_rfcomm_send(fd, "\r\nOK\r\n"); dbus_update_counters_wait(&dbus_update_counters.codec, 2); #endif /* check support for RING command */ ck_assert_rfcomm_send(fd, "\r\nRING\r\n"); usleep(5000); ba_transport_destroy(sco); close(fd); } CK_END_TEST CK_START_TEST(test_rfcomm_self_hfp_slc) { /* disable eSCO, so that codec negotiation is not performed */ adapter->hci.features[2] &= ~LMP_TRSP_SCO; adapter->hci.features[3] &= ~LMP_ESCO; int fds[2]; ck_assert_int_eq(socketpair(AF_UNIX, SOCK_STREAM, 0, fds), 0); struct ba_transport *ag = ba_transport_new_sco(device1, BA_TRANSPORT_PROFILE_HFP_AG, ":test", "/sco/ag", fds[0]); struct ba_transport *hf = ba_transport_new_sco(device2, BA_TRANSPORT_PROFILE_HFP_HF, ":test", "/sco/hf", fds[1]); ck_assert_int_eq(ba_transport_get_codec(ag), HFP_CODEC_CVSD); ck_assert_int_eq(ba_transport_get_codec(hf), HFP_CODEC_CVSD); /* wait for codec selection (SLC established) signals */ dbus_update_counters_wait(&dbus_update_counters.codec, 0 + (2 + 2)); pthread_mutex_lock(&adapter->devices_mutex); ck_assert_int_eq(device1->_rc.count, 1 + 1); ck_assert_int_eq(device2->_rc.count, 1 + 1); pthread_mutex_unlock(&adapter->devices_mutex); ck_assert_int_eq(ba_transport_get_codec(ag), HFP_CODEC_CVSD); ck_assert_int_eq(ba_transport_get_codec(hf), HFP_CODEC_CVSD); debug("Audio gateway destroying"); ba_transport_destroy(ag); /* The hf transport shall be destroyed by the "link lost" quirk. However, * we have to wait "some" time before reference counter check, because * this action is asynchronous from our point of view. */ debug("Hands Free unreferencing"); ba_transport_unref(hf); debug("Wait for asynchronous free"); usleep(100000); pthread_mutex_lock(&adapter->devices_mutex); ck_assert_int_eq(device1->_rc.count, 1); ck_assert_int_eq(device2->_rc.count, 1); pthread_mutex_unlock(&adapter->devices_mutex); } CK_END_TEST static void tc_setup(void) { config.battery.available = true; config.battery.level = 80; config.hfp.xapl_vendor_id = 0xDEAD, config.hfp.xapl_product_id = 0xC0DE, config.hfp.xapl_sw_version = 0x1234, memset(adapter->hci.features, 0, sizeof(adapter->hci.features)); adapter->hci.features[2] = LMP_TRSP_SCO; adapter->hci.features[3] = LMP_ESCO; memset(&dbus_update_counters, 0, sizeof(dbus_update_counters)); } int main(void) { struct sigaction sigact = { .sa_handler = SIG_IGN }; sigaction(SIGPIPE, &sigact, NULL); adapter = ba_adapter_new(0); bdaddr_t addr1 = {{ 1, 1, 1, 1, 1, 1 }}; device1 = ba_device_new(adapter, &addr1); bdaddr_t addr2 = {{ 2, 2, 2, 2, 2, 2 }}; device2 = ba_device_new(adapter, &addr2); Suite *s = suite_create(__FILE__); TCase *tc = tcase_create(__FILE__); SRunner *sr = srunner_create(s); suite_add_tcase(s, tc); tcase_add_checked_fixture(tc, tc_setup, NULL); tcase_set_timeout(tc, 10); tcase_add_test(tc, test_rfcomm_hsp_ag); tcase_add_test(tc, test_rfcomm_hsp_hs); tcase_add_test(tc, test_rfcomm_hfp_ag); tcase_add_test(tc, test_rfcomm_hfp_hf); tcase_add_test(tc, test_rfcomm_self_hfp_slc); srunner_run_all(sr, CK_ENV); int nf = srunner_ntests_failed(sr); srunner_free(sr); ba_device_unref(device1); ba_device_unref(device2); ba_adapter_unref(adapter); return nf == 0 ? 0 : 1; }