532 lines
17 KiB
C
532 lines
17 KiB
C
/*
|
|||
|
|
* test-rfcomm.c
|
||
|
|
* SPDX-FileCopyrightText: 2016-2025 BlueALSA developers
|
||
|
|
* SPDX-License-Identifier: MIT
|
||
|
|
*/
|
||
|
|
|
||
|
|
#if HAVE_CONFIG_H
|
||
|
|
# include <config.h>
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <sys/socket.h>
|
||
|
|
#include <sys/types.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
|
||
|
|
#include <bluetooth/bluetooth.h>
|
||
|
|
#include <bluetooth/hci.h>
|
||
|
|
#include <check.h>
|
||
|
|
#include <glib.h>
|
||
|
|
|
||
|
|
#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;
|
||
|
|
}
|