Files
2026-08-24 10:03:24 +08:00

242 lines
6.7 KiB
C

/*
* mock.c
* SPDX-FileCopyrightText: 2016-2025 BlueALSA developers
* SPDX-License-Identifier: MIT
*
* This program might be used to debug or check the functionality of ALSA
* plug-ins. It should work exactly the same as the BlueALSA server.
*
*/
#include "mock.h"
#include "service.h"
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
#include <gio/gio.h>
#include <glib-unix.h>
#include <glib.h>
#include "a2dp.h"
#include "ba-config.h"
#include "bluealsa-iface.h"
#include "dbus.h"
#include "ofono.h"
#include "storage.h"
#include "shared/a2dp-codecs.h"
#include "shared/defs.h"
#include "shared/log.h"
/* Keep persistent storage in the current directory. */
#define TEST_BLUEALSA_STORAGE_DIR "storage-mock"
static void * main_loop_run(void * userdata) {
g_main_loop_run(userdata);
return NULL;
}
static int queue_push_callback(void * userdata) {
g_async_queue_push(userdata, GINT_TO_POINTER(1));
return G_SOURCE_REMOVE;
}
int main(int argc, char *argv[]) {
int opt;
const char *opts = "hB:p:c:t:";
struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "dbus", required_argument, NULL, 'B' },
{ "profile", required_argument, NULL, 'p' },
{ "codec", required_argument, NULL, 'c' },
{ "timeout", required_argument, NULL, 't' },
{ "device-name", required_argument, NULL, 2 },
{ "fuzzing", required_argument, NULL, 7 },
{ 0, 0, 0, 0 },
};
char ba_service_name[32] = BLUEALSA_SERVICE;
int timeout_ms = 5000;
int fuzzing_ms = 0;
while ((opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1)
switch (opt) {
case 'h':
printf("Usage:\n"
" %s [OPTION]...\n"
"\nOptions:\n"
" -h, --help\t\t\tprint this help and exit\n"
" -B, --dbus=NAME\t\tBlueALSA service name suffix\n"
" -p, --profile=NAME\t\tset enabled BT profiles\n"
" -c, --codec=NAME\t\tset enabled BT audio codecs\n"
" -t, --timeout=MSEC\t\tmock server exit timeout\n"
" --device-name=MAC:NAME\tmock BT device name\n"
" --fuzzing=MSEC\t\tmock human actions with timings\n",
argv[0]);
return EXIT_SUCCESS;
case 'B' /* --dbus=NAME */ :
snprintf(ba_service_name, sizeof(ba_service_name),
BLUEALSA_SERVICE ".%s", optarg);
break;
case 'p' /* --profile=NAME */ : {
static const struct {
const char * name;
bool * ptr;
} map[] = {
{ "a2dp-source", &config.profile.a2dp_source },
{ "a2dp-sink", &config.profile.a2dp_sink },
#if ENABLE_ASHA
{ "asha-source", &config.profile.asha_source },
{ "asha-sink", &config.profile.asha_sink },
#endif
#if ENABLE_OFONO
{ "hfp-ofono", &config.profile.hfp_ofono },
#endif
{ "hfp-ag", &config.profile.hfp_ag },
{ "hfp-hf", &config.profile.hfp_hf },
{ "hsp-ag", &config.profile.hsp_ag },
{ "hsp-hs", &config.profile.hsp_hs },
#if ENABLE_MIDI
{ "midi", &config.profile.midi },
#endif
};
bool matched = false;
for (size_t i = 0; i < ARRAYSIZE(map); i++)
if (strcasecmp(optarg, map[i].name) == 0) {
*map[i].ptr = true;
matched = true;
break;
}
if (!matched) {
error("Invalid BT profile name: %s", optarg);
return EXIT_FAILURE;
}
break;
}
case 'c' /* --codec=NAME */ : {
uint32_t codec_id = a2dp_codecs_codec_id_from_string(optarg);
bool matched = false;
struct a2dp_sep * const * seps = a2dp_seps;
for (struct a2dp_sep *sep = *seps; sep != NULL; sep = *++seps)
if (sep->config.codec_id == codec_id) {
sep->enabled = true;
matched = true;
}
if (!matched) {
error("Invalid BT codec name: %s", optarg);
return EXIT_FAILURE;
}
break;
}
case 't' /* --timeout=MSEC */ :
timeout_ms = atoi(optarg);
break;
case 2 /* --device-name=MAC:NAME */ :
mock_bluez_service_add_device_name_mapping(optarg);
break;
case 7 /* --fuzzing=MSEC */ :
fuzzing_ms = atoi(optarg);
break;
default:
fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
return EXIT_FAILURE;
}
log_open(basename(argv[0]), false);
assert(ba_config_init() == 0);
/* Add BT address to the HCI filter to test filtering logic. */
const char *filter = MOCK_ADAPTER_ADDRESS;
g_array_append_val(config.hci_filter, filter);
assert(mkdir(TEST_BLUEALSA_STORAGE_DIR, 0755) == 0 || errno == EEXIST);
assert(storage_init(TEST_BLUEALSA_STORAGE_DIR) == 0);
atexit(storage_destroy);
g_autoptr(GTestDBus) dbus = g_test_dbus_new(G_TEST_DBUS_NONE);
g_test_dbus_up(dbus);
const char * dbus_address = g_test_dbus_get_bus_address(dbus);
fprintf(stderr, "DBUS_SYSTEM_BUS_ADDRESS=%s\n", dbus_address);
/* receive EPIPE error code */
struct sigaction sigact = { .sa_handler = SIG_IGN };
sigaction(SIGPIPE, &sigact, NULL);
/* Timeout queue. */
g_autoptr(GAsyncQueue) queue = g_async_queue_new();
/* Set up main loop with graceful termination handlers. */
g_autoptr(GMainLoop) loop = g_main_loop_new(NULL, FALSE);
GThread * loop_th = g_thread_new(NULL, main_loop_run, loop);
g_unix_signal_add(SIGINT, queue_push_callback, queue);
g_unix_signal_add(SIGTERM, queue_push_callback, queue);
g_autoptr(BlueZMockService) bluez = mock_bluez_service_new();
bluez->media_transport_update_ms = fuzzing_ms;
g_autoptr(GDBusConnection) conn1 = g_dbus_connection_new_for_address_simple_sync(
dbus_address, NULL);
mock_service_start(bluez, conn1);
g_autoptr(OFonoMockService) ofono = mock_ofono_service_new();
g_autoptr(GDBusConnection) conn2 = g_dbus_connection_new_for_address_simple_sync(
dbus_address, NULL);
mock_service_start(ofono, conn2);
g_autoptr(UPowerMockService) upower = mock_upower_service_new();
g_autoptr(GDBusConnection) conn3 = g_dbus_connection_new_for_address_simple_sync(
dbus_address, NULL);
mock_service_start(upower, conn3);
/* Start BlueALSA as the last service. */
g_autoptr(BlueALSAMockService) ba = mock_bluealsa_service_new(
ba_service_name, bluez, ofono, upower);
ba->fuzzing_ms = fuzzing_ms;
g_autoptr(GDBusConnection) conn4 = g_dbus_connection_new_for_address_simple_sync(
dbus_address, NULL);
mock_service_start(ba, conn4);
#if ENABLE_OFONO
assert(ofono_detect_service() == true);
#endif
/* Start the termination timer after all services are up and running. */
g_timeout_add(timeout_ms, queue_push_callback, queue);
/* Run mock until timeout or SIGINT/SIGTERM signal. */
mock_bluealsa_service_run(ba, queue);
mock_service_stop(ofono);
mock_service_stop(upower);
/* Simulate BlueZ termination while BlueALSA is still running. */
mock_service_stop(bluez);
mock_service_stop(ba);
g_main_loop_quit(loop);
g_thread_join(loop_th);
return EXIT_SUCCESS;
}