[PATCH v1] PTS: Handle A2DP and AVRCP pts testing.
Raghavender Reddy Bujala <[email protected]> Tue, 4 Mar 2025 11:11:26 +0530
| Newsgroups | org.kernel.vger.selinux-refpolicy |
|---|---|
| Message-ID | <[email protected]> |
Implemented pts_test tool to help in certification. handles few of the below usecases and gives flexibility to user to implement further to solve newly added or existing pts cases without rely on other audio modules like pulseaudio or pipewire. kindly review the change and let us know whether we can proceed further with this approach or not. - Handle error codes based on user input in setconf A2DP/SNK/AVP/BI-03-C A2DP/SNK/AVP/BI-10-C A2DP/SNK/AVP/BI-11-C A2DP/SNK/AVP/BI-12-C A2DP/SNK/AVP/BI-13-C A2DP/SNK/AVP/BI-14-C A2DP/SNK/AVP/BI-15-C A2DP/SNK/AVP/BI-16-C A2DP/SNK/AVP/BI-20-C AVDTP/SNK/ACP/TRA/BTR/BI-01-C AVDTP/SRC/ACP/TRA/BTR/BI-01-C - Handle userdefined a2dp profile connection to pts. AVRCP/CT/VLH/BI-03-C AVRCP/CT/VLH/BI-04-C AVRCP/CT/VLH/BV-01-C AVRCP/CT/VLH/BV-03-C AVRCP/CT/VLH/BV-04-C AVRCP/CT/VLH/BV-05-C AVRCP/CT/VLH/BV-06-C AVRCP/CT/VLH/BV-07-C AVRCP/TG/VLH/BI-01-C AVRCP/TG/VLH/BI-02-C AVRCP/TG/VLH/BV-01-C AVRCP/TG/VLH/BV-02-C AVRCP/TG/VLH/BV-03-C AVRCP/TG/VLH/BV-04-C - added avrcp abort continuation respone. AVRCP/CT/RCR/BV-03-C - send getconf & abort to pts. Signed-off-by: Raghavender Reddy Bujala <[email protected]> --- Makefile.tools | 9 +- profiles/audio/a2dp.c | 91 ++++++++++- profiles/audio/avdtp.c | 15 ++ profiles/audio/avrcp.c | 30 ++++ profiles/audio/avrcp.h | 2 + src/device.c | 12 ++ tools/pts_test.c | 353 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 510 insertions(+), 2 deletions(-) create mode 100644 tools/pts_test.c diff --git a/Makefile.tools b/Makefile.tools index e60c31b1d..46982cdac 100644 --- a/Makefile.tools +++ b/Makefile.tools @@ -225,7 +225,8 @@ endif if TOOLS bin_PROGRAMS += tools/rctest tools/l2test tools/l2ping tools/bluemoon \ - tools/hex2hcd tools/mpris-proxy tools/btattach tools/isotest + tools/hex2hcd tools/mpris-proxy tools/btattach tools/isotest \ + tools/pts_test noinst_PROGRAMS += tools/bdaddr tools/avinfo tools/avtest \ tools/scotest tools/hwdb \ @@ -330,6 +331,12 @@ tools_hex2hcd_SOURCES = tools/hex2hcd.c tools/missing.h tools_mpris_proxy_SOURCES = tools/mpris-proxy.c tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) + +tools_pts_test_SOURCES = tools/pts_test.c +tools_pts_test_LDADD = gdbus/libgdbus-internal.la \ + src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + if SYSTEMD systemduserunit_DATA += tools/mpris-proxy.service endif diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c index 81dbbfae3..7c6cea23a 100644 --- a/profiles/audio/a2dp.c +++ b/profiles/audio/a2dp.c @@ -54,6 +54,7 @@ #include "a2dp.h" #include "a2dp-codecs.h" #include "media.h" +#include "avrcp.h" /* The duration that streams without users are allowed to stay in * STREAMING state. */ @@ -62,6 +63,7 @@ #define AVDTP_PSM 25 #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" +#define BLUEZ_MISC_INTERFACE "org.bluez.Misc1" struct a2dp_sep { struct a2dp_server *server; @@ -152,6 +154,84 @@ struct a2dp_channel { static GSList *servers = NULL; static GSList *setups = NULL; static unsigned int cb_id = 0; +//PTS +extern struct avdtp_stream *conn_stream; +extern struct avdtp *conn_session; +int error_code = 0; +extern uint16_t pts_profile; + +static void set_error(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + void *data) +{ + uint16_t arg; + int16_t val; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + goto error; + + dbus_message_iter_get_basic(iter, &arg); + if (arg > UINT16_MAX) + goto error; + + g_dbus_pending_property_success(id); + + error_code = arg; + if (arg <= 2) + pts_profile = arg; + + DBG("value set for error_code %d pts_profile %d", error_code, pts_profile); + return; + + error: + g_dbus_pending_property_error(id, ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusMessage *getconf(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("in getconf"); + + avdtp_get_configuration(conn_session, conn_stream); + return dbus_message_new_method_return(msg); +} + +static DBusMessage *avrcp_abort(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("in avrcp_abort continuation response"); + + pts_abort_continuation_resposne(); + return dbus_message_new_method_return(msg); +} + + +static DBusMessage *send_abort(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("in send_abort"); + avdtp_abort(conn_session, conn_stream); + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable misc_methods[] = { + { GDBUS_ASYNC_METHOD("getconf", NULL, NULL, getconf) }, + { GDBUS_ASYNC_METHOD("abort", NULL, NULL, send_abort) }, + { GDBUS_ASYNC_METHOD("avrcp_abort", NULL, NULL, avrcp_abort) }, + { }, +}; + +static const GDBusPropertyTable misc_properties[] = { + { "error", "q", NULL, set_error, NULL }, + { } +}; + +static void misc_destroy(void *data) { + DBG("in misc_destroy"); +} + +//pts static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) { @@ -1687,7 +1767,16 @@ static sdp_record_t *a2dp_record(uint8_t type) sdp_list_free(aproto, 0); sdp_list_free(root, 0); sdp_list_free(svclass_id, 0); - +//pts + if (g_dbus_register_interface(btd_get_dbus_connection(), + "/org/bluez/misc", BLUEZ_MISC_INTERFACE, + misc_methods, NULL, misc_properties, + NULL, misc_destroy) == FALSE) { + error("Could not register misc"); + } else { + DBG("misc interface got created"); + } +//pts return record; } diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c index 80fbe847e..3d40270ed 100644 --- a/profiles/audio/avdtp.c +++ b/profiles/audio/avdtp.c @@ -415,6 +415,11 @@ struct avdtp { gboolean stream_setup; }; +//PTS +struct avdtp_stream *conn_stream; +struct avdtp *conn_session; +extern int error_code; +//PTS static GSList *state_callbacks = NULL; static int send_request(struct avdtp *session, gboolean priority, @@ -1554,6 +1559,11 @@ static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction, &stream->codec, &stream->delay_reporting); + if (error_code) { + err = error_code; + goto failed_stream; + } + if (!stream->caps || !stream->codec) { err = AVDTP_UNSUPPORTED_CONFIGURATION; category = 0x00; @@ -3600,6 +3610,11 @@ int avdtp_set_configuration(struct avdtp *session, if (stream) *stream = new_stream; session->dc_timeout = DISCONNECT_TIMEOUT; + + //PTS save session & stream + conn_stream = new_stream; + conn_session = session; + } g_free(req); diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index 7e9a383b0..19125a6f3 100644 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -576,6 +576,7 @@ static void populate_default_features(void) /* supports GetTotalNumberOfItems browsing command */ default_features[8] |= (1 << 3); + } static unsigned int attr_get_max_val(uint8_t attr) @@ -1794,6 +1795,9 @@ static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session, } volume = pdu->params[0] & 0x7F; + //RFA value is getting set by PTS & expecting to remove it from IUT. + if (pdu->params[0] > 0x7F) + pdu->params[0] = volume; media_transport_update_device_volume(session->dev, volume); @@ -2502,12 +2506,38 @@ static gboolean avrcp_get_element_attributes_rsp(struct avctp *conn, return FALSE; } +void pts_abort_continuation_resposne() { + DBG("abort continuation resposne in CT role for get element attributes"); + uint8_t buf[AVRCP_HEADER_LENGTH + 1]; + struct avrcp_header *pdu = (void *) buf; + uint16_t length; + + struct avrcp *session = last_session; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = 0x41; + pdu->params_len = cpu_to_be16(1); + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params[0] = 0x20; + + length = AVRCP_HEADER_LENGTH + be16_to_cpu(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, buf, length, + NULL, + session); +} + static void avrcp_get_element_attributes(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH + 9]; struct avrcp_header *pdu = (void *) buf; uint16_t length; + last_session = session; + memset(buf, 0, sizeof(buf)); set_company_id(pdu->company_id, IEEEID_BTSIG); diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h index 887753ddf..70d466f05 100644 --- a/profiles/audio/avrcp.h +++ b/profiles/audio/avrcp.h @@ -107,3 +107,5 @@ size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands); size_t avrcp_browsing_general_reject(uint8_t *operands); struct avrcp_player *avrcp_get_target_player_by_device(struct btd_device *dev); + +void pts_abort_continuation_resposne(); diff --git a/src/device.c b/src/device.c index ec97fc889..c2fa79cea 100644 --- a/src/device.c +++ b/src/device.c @@ -301,6 +301,7 @@ static const uint16_t uuid_list[] = { 0 }; +uint16_t pts_profile = 0; static int device_browse_gatt(struct btd_device *device, DBusMessage *msg); static int device_browse_sdp(struct btd_device *device, DBusMessage *msg); @@ -7474,6 +7475,17 @@ struct btd_service *btd_device_get_service(struct btd_device *dev, for (l = dev->services; l != NULL; l = g_slist_next(l)) { struct btd_service *service = l->data; struct btd_profile *p = btd_service_get_profile(service); + //pts specific + //during volume test case PTS sends both SRC and SNK Uuids + //due to this conflict of connection happens. + if (g_str_equal(p->remote_uuid, A2DP_SINK_UUID) && pts_profile == 1) { + continue; + } + + if (g_str_equal(p->remote_uuid, A2DP_SOURCE_UUID) && pts_profile == 2) { + continue; + } + //pts if (g_str_equal(p->remote_uuid, remote_uuid)) return service; diff --git a/tools/pts_test.c b/tools/pts_test.c new file mode 100644 index 000000000..9864c3b5f --- /dev/null +++ b/tools/pts_test.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <sys/uio.h> + +#include <glib.h> + +#include "gdbus/gdbus.h" + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/shell.h" + +/* String display constants */ +#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF +#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF +#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF + +#define PROMPT_ON COLOR_BLUE "[pts_test]" COLOR_OFF "# " +#define PROMPT_OFF "[pts_test]# " +#define BLUEZ_MISC_INTERFACE "org.bluez.Misc1" +static GList *miscs = NULL; + +static DBusConnection *dbus_conn; +static void print_misc(void *data, void *user_data); + +static void connect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_attach(fileno(stdin)); + bt_shell_set_prompt(PROMPT_ON); +} + +static void disconnect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_detach(); + bt_shell_set_prompt(PROMPT_OFF); +} + +static void misc_added(GDBusProxy *proxy) +{ + miscs = g_list_append(miscs, proxy); + + print_misc(proxy, COLORED_NEW); +} + +static void misc_removed(GDBusProxy *proxy) +{ + miscs = g_list_remove(miscs, proxy); + + print_misc(proxy, COLORED_DEL); +} + +static char *generic_generator(const char *text, int state, GList *source) +{ + static int index = 0; + + if (!state) { + index = 0; + } + + return g_dbus_proxy_path_lookup(source, &index, text); +} + + +static char *misc_generator(const char *text, int state) +{ + return generic_generator(text, state, miscs); +} + +static void error_callback(const DBusError *error, void *user_data) +{ + if (dbus_error_is_set(error)) { + bt_shell_printf("Failed to set error code : %s\n", error->name); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Changing error code succeeded\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_error_misc(int argc, char *argv[]) +{ + GDBusProxy *proxy; + char *endptr = NULL; + int error; + + proxy = g_dbus_proxy_lookup(miscs, NULL, argv[1], + BLUEZ_MISC_INTERFACE); + if (!proxy) { + bt_shell_printf("misc %s not found\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + + if (argc == 2) { + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + error = strtol(argv[2], &endptr, 0); + if (!endptr || *endptr != '\0' || error > UINT16_MAX) { + bt_shell_printf("Invalid argument: %s\n", argv[2]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (!g_dbus_proxy_set_property_basic(proxy, "error", DBUS_TYPE_UINT16, + &error, error_callback, + NULL, NULL)) { + bt_shell_printf("Failed release transport\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void getconf_callback(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to getconf: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("getconf successful\n"); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_getconf_misc(int argc, char *argv[]) +{ + GDBusProxy *proxy; + int i; + + proxy = g_dbus_proxy_lookup(miscs, NULL, argv[1], + BLUEZ_MISC_INTERFACE); + if (!proxy) { + bt_shell_printf("misc %s not found\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + if (!g_dbus_proxy_method_call(proxy, "getconf", NULL, + getconf_callback, NULL, NULL)) { + bt_shell_printf("Failed getconf misc\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void abort_callback(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to abort: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("abort successful\n"); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_abort_misc(int argc, char *argv[]) +{ + GDBusProxy *proxy; + int i; + + proxy = g_dbus_proxy_lookup(miscs, NULL, argv[1], + BLUEZ_MISC_INTERFACE); + if (!proxy) { + bt_shell_printf("misc %s not found\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + if (!g_dbus_proxy_method_call(proxy, "abort", NULL, + abort_callback, NULL, NULL)) { + bt_shell_printf("Failed abort misc\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_avrcp_misc(int argc, char *argv[]) +{ + GDBusProxy *proxy; + int i; + + proxy = g_dbus_proxy_lookup(miscs, NULL, argv[1], + BLUEZ_MISC_INTERFACE); + if (!proxy) { + bt_shell_printf("misc %s not found\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + if (!g_dbus_proxy_method_call(proxy, "avrcp_abort", NULL, + abort_callback, NULL, NULL)) { + bt_shell_printf("Failed abort avrcp misc\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_list_misc(int argc, char *argv[]) +{ + GList *l; + + for (l = miscs; l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + print_misc(proxy, NULL); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static char *proxy_description(GDBusProxy *proxy, const char *title, + const char *description) +{ + const char *path; + + path = g_dbus_proxy_get_path(proxy); + + return g_strdup_printf("%s%s%s%s %s ", + description ? "[" : "", + description ? : "", + description ? "] " : "", + title, path); +} + +static void print_misc(void *data, void *user_data) +{ + GDBusProxy *proxy = data; + const char *description = user_data; + char *str; + + str = proxy_description(proxy, "Misc", description); + + bt_shell_printf("%s\n", str); + + g_free(str); +} + +static void proxy_added(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, BLUEZ_MISC_INTERFACE)) + misc_added(proxy); +} + +static void proxy_removed(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, BLUEZ_MISC_INTERFACE)) + misc_removed(proxy); +} + +static const struct bt_shell_menu misc_menu = { + .name = "misc", + .desc = "Misc Submenu", + .entries = { + { "list", NULL, cmd_list_misc, + "List available miscs" }, + { "error", "<misc> [value]", cmd_error_misc, + "Set misc error\n \ + 0 --> to reset\n \ + 1 --> enable only sink profile for connection\n \ + 2 --> enable only source profile for connection\n \ + 35 --> invalid media transport\n \ + 127 --> delay reporting enable\n \ + 193 --> invalid codec typ\n \ + 194 --> not supported codec type\n \ + 195 --> invalid sampling freq\n \ + 197 --> invalid channel mode\n \ + 199 --> invalid sub bands\n \ + 201 --> invalid allocation method\n \ + 203 --> invalid min bit pool\n \ + 205 --> invalid max bit pool\n \ + 221 --> invalid block length", + misc_generator }, + { "getconf", "<misc>", cmd_getconf_misc, + "send getconf", + misc_generator }, + { "abort", "<misc>", cmd_abort_misc, + "send abort", + misc_generator }, + { "avrcp_abort", "<misc>", cmd_avrcp_misc, + "send abort continuation response", + misc_generator }, + {} }, +}; + +int main(int argc, char *argv[]) +{ + GDBusClient *client; + int status; + + bt_shell_init(argc, argv, NULL); + bt_shell_set_menu(&misc_menu); + bt_shell_set_prompt(PROMPT_OFF); + + dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + + bt_shell_set_env("DBUS_CONNECTION", dbus_conn); + + + client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + + g_dbus_client_set_connect_watch(client, connect_handler, NULL); + g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, + NULL, NULL); + g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); + + status = bt_shell_run(); + + + g_dbus_client_unref(client); + + dbus_connection_unref(dbus_conn); + + return status; +} -- 2.17.1