diff mbox series

[BlueZ,v3,4/4] monitor/att: Add decoding support for GMCS

Message ID 20221018043831.342821-5-abhay.maheshbhai.maheta@intel.com
State New
Headers show
Series Media Control Profile Client | expand

Commit Message

Abhay Maheta Oct. 18, 2022, 4:38 a.m. UTC
This adds decoding support for GMCS attributes.

< ACL Data TX: Handle 3585 flags 0x00 dlen 7
      ATT: Read Request (0x0a) len 2
        Handle: 0x0056 Type: Media Control Point Opcodes Supported (0x2ba5)
> ACL Data RX: Handle 3585 flags 0x02 dlen 9
      ATT: Read Response (0x0b) len 4
        Value: 33180000
        Handle: 0x0056 Type: Media Control Point Opcodes Supported (0x2ba5)
              Supported Opcodes: 0x00001833
                Play (0x00000001)
                Pause (0x00000002)
                Stop (0x00000010)
                Move Relative (0x00000020)
                Previous Track (0x00000800)
                Next Track (0x00001000)
---
 monitor/att.c | 513 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 513 insertions(+)
diff mbox series

Patch

diff --git a/monitor/att.c b/monitor/att.c
index f5fc32cb0..491f196bf 100644
--- a/monitor/att.c
+++ b/monitor/att.c
@@ -14,6 +14,7 @@ 
 #endif
 
 #define _GNU_SOURCE
+#include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -22,6 +23,8 @@ 
 #include <errno.h>
 #include <linux/limits.h>
 
+#include <glib.h>
+
 #include "lib/bluetooth.h"
 #include "lib/uuid.h"
 #include "lib/hci.h"
@@ -1746,6 +1749,499 @@  static void vol_flag_notify(const struct l2cap_frame *frame)
 	print_vcs_flag(frame);
 }
 
+static char *name2utf8(const uint8_t *name, uint16_t len)
+{
+	char utf8_name[HCI_MAX_NAME_LENGTH + 2];
+	int i;
+
+	if (g_utf8_validate((const char *) name, len, NULL))
+		return g_strndup((char *) name, len);
+
+	len = MIN(len, sizeof(utf8_name) - 1);
+
+	memset(utf8_name, 0, sizeof(utf8_name));
+	strncpy(utf8_name, (char *) name, len);
+
+	/* Assume ASCII, and replace all non-ASCII with spaces */
+	for (i = 0; utf8_name[i] != '\0'; i++) {
+		if (!isascii(utf8_name[i]))
+			utf8_name[i] = ' ';
+	}
+
+	/* Remove leading and trailing whitespace characters */
+	g_strstrip(utf8_name);
+
+	return g_strdup(utf8_name);
+}
+
+static void print_mp_name(const struct l2cap_frame *frame)
+{
+	char *name;
+
+	name = name2utf8((uint8_t *)frame->data, frame->size);
+
+	print_field("  Media Player Name: %s", name);
+}
+
+static void mp_name_read(const struct l2cap_frame *frame)
+{
+	print_mp_name(frame);
+}
+
+static void mp_name_notify(const struct l2cap_frame *frame)
+{
+	print_mp_name(frame);
+}
+
+static void print_track_changed(const struct l2cap_frame *frame)
+{
+	print_field("  Track Changed");
+}
+
+static void track_changed_notify(const struct l2cap_frame *frame)
+{
+	print_track_changed(frame);
+}
+
+static void print_track_title(const struct l2cap_frame *frame)
+{
+	char *name;
+
+	name = name2utf8((uint8_t *)frame->data, frame->size);
+
+	print_field("  Track Title: %s", name);
+}
+
+static void track_title_read(const struct l2cap_frame *frame)
+{
+	print_track_title(frame);
+}
+
+static void track_title_notify(const struct l2cap_frame *frame)
+{
+	print_track_title(frame);
+}
+
+static void print_track_duration(const struct l2cap_frame *frame)
+{
+	int32_t duration;
+
+	if (!l2cap_frame_get_le32((void *)frame, (uint32_t *)&duration)) {
+		print_text(COLOR_ERROR, "  Track Duration: invalid size");
+		goto done;
+	}
+
+	print_field("  Track Duration: %u", duration);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void track_duration_read(const struct l2cap_frame *frame)
+{
+	print_track_duration(frame);
+}
+
+static void track_duration_notify(const struct l2cap_frame *frame)
+{
+	print_track_duration(frame);
+}
+
+static void print_track_position(const struct l2cap_frame *frame)
+{
+	int32_t position;
+
+	if (!l2cap_frame_get_le32((void *)frame, (uint32_t *)&position)) {
+		print_text(COLOR_ERROR, "  Track Position: invalid size");
+		goto done;
+	}
+
+	print_field("  Track Position: %u", position);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void track_position_read(const struct l2cap_frame *frame)
+{
+	print_track_position(frame);
+}
+
+static void track_position_write(const struct l2cap_frame *frame)
+{
+	print_track_position(frame);
+}
+
+static void track_position_notify(const struct l2cap_frame *frame)
+{
+	print_track_position(frame);
+}
+
+static void print_playback_speed(const struct l2cap_frame *frame)
+{
+	int8_t playback_speed;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&playback_speed)) {
+		print_text(COLOR_ERROR, "  Playback Speed: invalid size");
+		goto done;
+	}
+
+	print_field("  Playback Speed: %u", playback_speed);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void playback_speed_read(const struct l2cap_frame *frame)
+{
+	print_playback_speed(frame);
+}
+
+static void playback_speed_write(const struct l2cap_frame *frame)
+{
+	print_playback_speed(frame);
+}
+
+static void playback_speed_notify(const struct l2cap_frame *frame)
+{
+	print_playback_speed(frame);
+}
+
+static void print_seeking_speed(const struct l2cap_frame *frame)
+{
+	int8_t seeking_speed;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&seeking_speed)) {
+		print_text(COLOR_ERROR, "  Seeking Speed: invalid size");
+		goto done;
+	}
+
+	print_field("  Seeking Speed: %u", seeking_speed);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void seeking_speed_read(const struct l2cap_frame *frame)
+{
+	print_seeking_speed(frame);
+}
+
+static void seeking_speed_notify(const struct l2cap_frame *frame)
+{
+	print_seeking_speed(frame);
+}
+
+static const char *play_order_str(uint8_t order)
+{
+	switch (order) {
+	case 0x01:
+		return "Single once";
+	case 0x02:
+		return "Single repeat";
+	case 0x03:
+		return "In order once";
+	case 0x04:
+		return "In order repeat";
+	case 0x05:
+		return "Oldest once";
+	case 0x06:
+		return "Oldest repeat";
+	case 0x07:
+		return "Newest once";
+	case 0x08:
+		return "Newest repeat";
+	case 0x09:
+		return "Shuffle once";
+	case 0x0A:
+		return "Shuffle repeat";
+	default:
+		return "RFU";
+	}
+}
+
+static void print_playing_order(const struct l2cap_frame *frame)
+{
+	int8_t playing_order;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&playing_order)) {
+		print_text(COLOR_ERROR, "  Playing Order: invalid size");
+		goto done;
+	}
+
+	print_field("  Playing Order: %s", play_order_str(playing_order));
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void playing_order_read(const struct l2cap_frame *frame)
+{
+	print_playing_order(frame);
+}
+
+static void playing_order_write(const struct l2cap_frame *frame)
+{
+	print_playing_order(frame);
+}
+
+static void playing_order_notify(const struct l2cap_frame *frame)
+{
+	print_playing_order(frame);
+}
+
+static const struct bitfield_data playing_orders_table[] = {
+	{  0, "Single once (0x0001)"	    },
+	{  1, "Single repeat (0x0002)"		},
+	{  2, "In order once (0x0004)"		},
+	{  3, "In Order Repeat (0x0008)"	},
+	{  4, "Oldest once (0x0010)"		},
+	{  5, "Oldest repeat (0x0020)"		},
+	{  6, "Newest once (0x0040)"		},
+	{  7, "Newest repeat (0x0080)"	    },
+	{  8, "Shuffle once (0x0100)"		},
+	{  9, "Shuffle repeat (0x0200)"		},
+	{  10, "RFU (0x0400)"			    },
+	{  11, "RFU (0x0800)"		        },
+	{  12, "RFU (0x1000)"				},
+	{  13, "RFU (0x2000)"				},
+	{  14, "RFU (0x4000)"				},
+	{  15, "RFU (0x8000)"				},
+	{ }
+};
+
+static void print_playing_orders_supported(const struct l2cap_frame *frame)
+{
+	uint16_t supported_orders;
+	uint16_t mask;
+
+	if (!l2cap_frame_get_le16((void *)frame, &supported_orders)) {
+		print_text(COLOR_ERROR,
+				"    Supported Playing Orders: invalid size");
+		goto done;
+	}
+
+	print_field("      Supported Playing Orders: 0x%4.4x",
+				supported_orders);
+
+	mask = print_bitfield(8, supported_orders, playing_orders_table);
+	if (mask)
+		print_text(COLOR_WHITE_BG, "    Unknown fields (0x%4.4x)",
+								mask);
+
+done:
+	if (frame->size)
+		print_hex_field("    Data", frame->data, frame->size);
+}
+
+static void playing_orders_supported_read(const struct l2cap_frame *frame)
+{
+	print_playing_orders_supported(frame);
+}
+
+static const char *media_state_str(uint8_t state)
+{
+	switch (state) {
+	case 0x00:
+		return "Inactive";
+	case 0x01:
+		return "Playing";
+	case 0x02:
+		return "Paused";
+	case 0x03:
+		return "Seeking";
+	default:
+		return "RFU";
+	}
+}
+
+static void print_media_state(const struct l2cap_frame *frame)
+{
+	int8_t state;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&state)) {
+		print_text(COLOR_ERROR, "  Media State: invalid size");
+		goto done;
+	}
+
+	print_field("  Media State: %s", media_state_str(state));
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void media_state_read(const struct l2cap_frame *frame)
+{
+	print_media_state(frame);
+}
+
+static void media_state_notify(const struct l2cap_frame *frame)
+{
+	print_media_state(frame);
+}
+
+struct media_cp_opcode {
+	uint8_t opcode;
+	const char *opcode_str;
+} media_cp_opcode_table[] = {
+	{0x01,	"Play"},
+	{0x02,	"Pause"},
+	{0x03,	"Fast Rewind"},
+	{0x04,	"Fast Forward"},
+	{0x05,	"Stop"},
+	{0x10,	"Move Relative"},
+	{0x20,	"Previous Segment"},
+	{0x21,	"Next Segment"},
+	{0x22,	"First Segment"},
+	{0x23,	"Last Segment"},
+	{0x24,	"Goto Segment"},
+	{0x30,	"Previous Track"},
+	{0x31,	"Next Track"},
+	{0x32,	"First Track"},
+	{0x33,	"Last Track"},
+	{0x34,	"Goto Track"},
+	{0x40,	"Previous Group"},
+	{0x41,	"Next Group"},
+	{0x42,	"First Group"},
+	{0x43,	"Last Group"},
+	{0x44,	"Goto Group"},
+};
+
+static const char *cp_opcode_str(uint8_t opcode)
+{
+	size_t i;
+
+	for (i = 0; i < ARRAY_SIZE(media_cp_opcode_table); i++) {
+		const char *str = media_cp_opcode_table[i].opcode_str;
+
+		if (opcode == media_cp_opcode_table[i].opcode)
+			return str;
+	}
+
+	return "RFU";
+}
+
+static void print_media_cp(const struct l2cap_frame *frame)
+{
+	int8_t opcode;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&opcode)) {
+		print_text(COLOR_ERROR, "  Media Control Point: invalid size");
+		goto done;
+	}
+
+	print_field("  Media Control Point: %s", cp_opcode_str(opcode));
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void media_cp_write(const struct l2cap_frame *frame)
+{
+	print_media_cp(frame);
+}
+
+static void media_cp_notify(const struct l2cap_frame *frame)
+{
+	print_media_cp(frame);
+}
+
+static const struct bitfield_data supported_opcodes_table[] = {
+	{0, "Play (0x00000001)"				},
+	{1, "Pause (0x00000002)"			},
+	{2, "Fast Rewind	(0x00000004)"	},
+	{3, "Fast Forward (0x00000008)"		},
+	{4, "Stop (0x00000010)"				},
+	{5, "Move Relative (0x00000020)"	},
+	{6, "Previous Segment (0x00000040)"	},
+	{7, "Next Segment (0x00000080)"		},
+	{8, "First Segment (0x00000100)"	},
+	{9, "Last Segment (0x00000200)"		},
+	{10, "Goto Segment (0x00000400)"	},
+	{11, "Previous Track (0x00000800)"	},
+	{12, "Next Track (0x00001000)"		},
+	{13, "First Track (0x00002000)"		},
+	{14, "Last Track (0x00004000)"		},
+	{15, "Goto Track (0x00008000)"		},
+	{16, "Previous Group (0x00010000)"	},
+	{17, "Next Group (0x00020000)"		},
+	{18, "First Group (0x00040000)"		},
+	{19, "Last Group (0x00080000)"		},
+	{20, "Goto Group (0x00100000)"		},
+	{21, "RFU (0x00200000)"				},
+	{22, "RFU (0x00400000)"				},
+	{23, "RFU (0x00800000)"				},
+	{24, "RFU (0x01000000)"				},
+	{25, "RFU (0x02000000)"				},
+	{26, "RFU (0x04000000)"				},
+	{27, "RFU (0x08000000)"				},
+	{28, "RFU (0x10000000)"				},
+	{29, "RFU (0x20000000)"				},
+	{30, "RFU (0x40000000)"				},
+	{31, "RFU (0x80000000)"				},
+	{ }
+};
+
+static void print_media_cp_op_supported(const struct l2cap_frame *frame)
+{
+	uint32_t supported_opcodes;
+	uint32_t mask;
+
+	if (!l2cap_frame_get_le32((void *)frame, &supported_opcodes)) {
+		print_text(COLOR_ERROR, "    value: invalid size");
+		goto done;
+	}
+
+	print_field("      Supported Opcodes: 0x%8.8x", supported_opcodes);
+
+	mask = print_bitfield(8, supported_opcodes, supported_opcodes_table);
+	if (mask)
+		print_text(COLOR_WHITE_BG, "    Unknown fields (0x%4.4x)",
+								mask);
+
+done:
+	if (frame->size)
+		print_hex_field("    Data", frame->data, frame->size);
+}
+
+static void media_cp_op_supported_read(const struct l2cap_frame *frame)
+{
+	print_media_cp_op_supported(frame);
+}
+
+static void media_cp_op_supported_notify(const struct l2cap_frame *frame)
+{
+	print_media_cp_op_supported(frame);
+}
+
+static void print_content_control_id(const struct l2cap_frame *frame)
+{
+	int8_t ccid;
+
+	if (!l2cap_frame_get_u8((void *)frame, (uint8_t *)&ccid)) {
+		print_text(COLOR_ERROR, "  Content Control ID: invalid size");
+		goto done;
+	}
+
+	print_field("  Content Control ID: 0x%2.2x", ccid);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void content_control_id_read(const struct l2cap_frame *frame)
+{
+	print_content_control_id(frame);
+}
+
 #define GATT_HANDLER(_uuid, _read, _write, _notify) \
 { \
 	.uuid = { \
@@ -1776,6 +2272,23 @@  struct gatt_handler {
 	GATT_HANDLER(0x2b7d, vol_state_read, NULL, vol_state_notify),
 	GATT_HANDLER(0x2b7e, NULL, vol_cp_write, NULL),
 	GATT_HANDLER(0x2b7f, vol_flag_read, NULL, vol_flag_notify),
+	GATT_HANDLER(0x2b93, mp_name_read, NULL, mp_name_notify),
+	GATT_HANDLER(0x2b96, NULL, NULL, track_changed_notify),
+	GATT_HANDLER(0x2b97, track_title_read, NULL, track_title_notify),
+	GATT_HANDLER(0x2b98, track_duration_read, NULL, track_duration_notify),
+	GATT_HANDLER(0x2b99, track_position_read, track_position_write,
+					track_position_notify),
+	GATT_HANDLER(0x2b9a, playback_speed_read, playback_speed_write,
+					playback_speed_notify),
+	GATT_HANDLER(0x2b9b, seeking_speed_read, NULL, seeking_speed_notify),
+	GATT_HANDLER(0x2ba1, playing_order_read, playing_order_write,
+					playing_order_notify),
+	GATT_HANDLER(0x2ba2, playing_orders_supported_read, NULL, NULL),
+	GATT_HANDLER(0x2ba3, media_state_read, NULL, media_state_notify),
+	GATT_HANDLER(0x2ba4, NULL, media_cp_write, media_cp_notify),
+	GATT_HANDLER(0x2ba5, media_cp_op_supported_read, NULL,
+					media_cp_op_supported_notify),
+	GATT_HANDLER(0x2bba, content_control_id_read, NULL, NULL),
 };
 
 static struct gatt_handler *get_handler(struct gatt_db_attribute *attr)