blob: 00db8ddaa7949df90bb12e77e9ad03bfbd3350c9 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2011-2014 Intel Corporation
* Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include <errno.h>
#include <linux/limits.h>
#include "lib/bluetooth.h"
#include "lib/uuid.h"
#include "lib/hci.h"
#include "lib/hci_lib.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/textfile.h"
#include "src/settings.h"
#include "bt.h"
#include "packet.h"
#include "display.h"
#include "l2cap.h"
#include "att.h"
static void print_uuid(const char *label, const void *data, uint16_t size)
{
const char *str;
char uuidstr[MAX_LEN_UUID_STR];
switch (size) {
case 2:
str = bt_uuid16_to_str(get_le16(data));
print_field("%s: %s (0x%4.4x)", label, str, get_le16(data));
break;
case 4:
str = bt_uuid32_to_str(get_le32(data));
print_field("%s: %s (0x%8.8x)", label, str, get_le32(data));
break;
case 16:
sprintf(uuidstr, "%8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x",
get_le32(data + 12), get_le16(data + 10),
get_le16(data + 8), get_le16(data + 6),
get_le32(data + 2), get_le16(data + 0));
str = bt_uuidstr_to_str(uuidstr);
print_field("%s: %s (%s)", label, str, uuidstr);
break;
default:
packet_hexdump(data, size);
break;
}
}
static void print_handle_range(const char *label, const void *data)
{
print_field("%s: 0x%4.4x-0x%4.4x", label,
get_le16(data), get_le16(data + 2));
}
static void print_data_list(const char *label, uint8_t length,
const void *data, uint16_t size)
{
uint8_t count;
if (length == 0)
return;
count = size / length;
print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies");
while (size >= length) {
print_field("Handle: 0x%4.4x", get_le16(data));
print_hex_field("Value", data + 2, length - 2);
data += length;
size -= length;
}
packet_hexdump(data, size);
}
static void print_attribute_info(uint16_t type, const void *data, uint16_t len)
{
const char *str = bt_uuid16_to_str(type);
print_field("%s: %s (0x%4.4x)", "Attribute type", str, type);
switch (type) {
case 0x2800: /* Primary Service */
case 0x2801: /* Secondary Service */
print_uuid(" UUID", data, len);
break;
case 0x2802: /* Include */
if (len < 4) {
print_hex_field(" Value", data, len);
break;
}
print_handle_range(" Handle range", data);
print_uuid(" UUID", data + 4, len - 4);
break;
case 0x2803: /* Characteristic */
if (len < 3) {
print_hex_field(" Value", data, len);
break;
}
print_field(" Properties: 0x%2.2x", *((uint8_t *) data));
print_field(" Handle: 0x%2.2x", get_le16(data + 1));
print_uuid(" UUID", data + 3, len - 3);
break;
default:
print_hex_field("Value", data, len);
break;
}
}
static const char *att_opcode_to_str(uint8_t opcode);
static void att_error_response(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_error_response *pdu = frame->data;
const char *str;
switch (pdu->error) {
case 0x01:
str = "Invalid Handle";
break;
case 0x02:
str = "Read Not Permitted";
break;
case 0x03:
str = "Write Not Permitted";
break;
case 0x04:
str = "Invalid PDU";
break;
case 0x05:
str = "Insufficient Authentication";
break;
case 0x06:
str = "Request Not Supported";
break;
case 0x07:
str = "Invalid Offset";
break;
case 0x08:
str = "Insufficient Authorization";
break;
case 0x09:
str = "Prepare Queue Full";
break;
case 0x0a:
str = "Attribute Not Found";
break;
case 0x0b:
str = "Attribute Not Long";
break;
case 0x0c:
str = "Insufficient Encryption Key Size";
break;
case 0x0d:
str = "Invalid Attribute Value Length";
break;
case 0x0e:
str = "Unlikely Error";
break;
case 0x0f:
str = "Insufficient Encryption";
break;
case 0x10:
str = "Unsupported Group Type";
break;
case 0x11:
str = "Insufficient Resources";
break;
case 0x12:
str = "Database Out of Sync";
break;
case 0x13:
str = "Value Not Allowed";
break;
case 0xfd:
str = "CCC Improperly Configured";
break;
case 0xfe:
str = "Procedure Already in Progress";
break;
case 0xff:
str = "Out of Range";
break;
default:
str = "Reserved";
break;
}
print_field("%s (0x%2.2x)", att_opcode_to_str(pdu->request),
pdu->request);
print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle));
print_field("Error: %s (0x%2.2x)", str, pdu->error);
}
static const struct bitfield_data ccc_value_table[] = {
{ 0, "Notification (0x01)" },
{ 1, "Indication (0x02)" },
{ }
};
static void print_ccc_value(const struct l2cap_frame *frame)
{
uint8_t value;
uint8_t mask;
if (!l2cap_frame_get_u8((void *)frame, &value)) {
print_text(COLOR_ERROR, "invalid size");
return;
}
mask = print_bitfield(4, value, ccc_value_table);
if (mask)
print_text(COLOR_WHITE_BG, " Unknown fields (0x%2.2x)",
mask);
}
static void ccc_read(const struct l2cap_frame *frame)
{
print_ccc_value(frame);
}
static void ccc_write(const struct l2cap_frame *frame)
{
print_ccc_value(frame);
}
static bool print_ase_codec(const struct l2cap_frame *frame)
{
uint8_t codec_id;
uint16_t codec_cid, codec_vid;
if (!l2cap_frame_get_u8((void *)frame, &codec_id)) {
print_text(COLOR_ERROR, "Codec: invalid size");
return false;
}
packet_print_codec_id(" Codec", codec_id);
if (!l2cap_frame_get_le16((void *)frame, &codec_cid)) {
print_text(COLOR_ERROR, "Codec Company ID: invalid size");
return false;
}
if (!l2cap_frame_get_le16((void *)frame, &codec_vid)) {
print_text(COLOR_ERROR, "Codec Vendor ID: invalid size");
return false;
}
if (codec_id == 0xff) {
print_field(" Codec Company ID: %s (0x%04x)",
bt_compidtostr(codec_cid),
codec_cid);
print_field(" Codec Vendor ID: 0x%04x", codec_vid);
}
return true;
}
static bool print_ase_lv(const struct l2cap_frame *frame, const char *label)
{
struct bt_hci_lv_data *lv;
lv = l2cap_frame_pull((void *)frame, frame, sizeof(*lv));
if (!lv) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
if (!l2cap_frame_pull((void *)frame, frame, lv->len)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
packet_print_ltv(label, lv->data, lv->len);
return true;
}
static bool print_ase_cc(const struct l2cap_frame *frame)
{
return print_ase_lv(frame, " Codec Specific Configuration");
}
static bool print_ase_metadata(const struct l2cap_frame *frame)
{
return print_ase_lv(frame, " Metadata");
}
static void print_pac(const struct l2cap_frame *frame)
{
uint8_t num = 0, i;
if (!l2cap_frame_get_u8((void *)frame, &num)) {
print_text(COLOR_ERROR, "Number of PAC(s): invalid size");
goto done;
}
print_field(" Number of PAC(s): %u", num);
for (i = 0; i < num; i++) {
print_field(" PAC #%u:", i);
if (!print_ase_codec(frame))
goto done;
if (!print_ase_cc(frame))
break;
if (!print_ase_metadata(frame))
break;
}
done:
if (frame->size)
print_hex_field(" Data", frame->data, frame->size);
}
static void pac_read(const struct l2cap_frame *frame)
{
print_pac(frame);
}
static void pac_notify(const struct l2cap_frame *frame)
{
print_pac(frame);
}
static bool print_prefer_framing(const struct l2cap_frame *frame)
{
uint8_t framing;
if (!l2cap_frame_get_u8((void *)frame, &framing)) {
print_text(COLOR_ERROR, " Framing: invalid size");
return false;
}
switch (framing) {
case 0x00:
print_field(" Framing: Unframed PDUs supported (0x00)");
break;
case 0x01:
print_field(" Framing: Unframed PDUs not supported (0x01)");
break;
default:
print_field(" Framing: Reserved (0x%2.2x)", framing);
break;
}
return true;
}
static const struct bitfield_data prefer_phy_table[] = {
{ 0, "LE 1M PHY preffered (0x01)" },
{ 1, "LE 2M PHY preffered (0x02)" },
{ 2, "LE Codec PHY preffered (0x04)" },
{ }
};
static bool print_prefer_phy(const struct l2cap_frame *frame)
{
uint8_t phy, mask;
if (!l2cap_frame_get_u8((void *)frame, &phy)) {
print_text(COLOR_ERROR, "PHY: invalid size");
return false;
}
print_field(" PHY: 0x%2.2x", phy);
mask = print_bitfield(4, phy, prefer_phy_table);
if (mask)
print_text(COLOR_WHITE_BG, " Unknown fields (0x%2.2x)",
mask);
return true;
}
static bool print_ase_rtn(const struct l2cap_frame *frame, const char *label)
{
uint8_t rtn;
if (!l2cap_frame_get_u8((void *)frame, &rtn)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: %u", label, rtn);
return true;
}
static bool print_ase_latency(const struct l2cap_frame *frame,
const char *label)
{
uint16_t latency;
if (!l2cap_frame_get_le16((void *)frame, &latency)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: %u", label, latency);
return true;
}
static bool print_ase_pd(const struct l2cap_frame *frame, const char *label)
{
uint32_t pd;
if (!l2cap_frame_get_le24((void *)frame, &pd)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: %u us", label, pd);
return true;
}
static void print_ase_config(const struct l2cap_frame *frame)
{
if (!print_prefer_framing(frame))
return;
if (!print_prefer_phy(frame))
return;
if (!print_ase_rtn(frame, " RTN"))
return;
if (!print_ase_latency(frame, " Max Transport Latency"))
return;
if (!print_ase_pd(frame, " Presentation Delay Min"))
return;
if (!print_ase_pd(frame, " Presentation Delay Max"))
return;
if (!print_ase_pd(frame, " Preferred Presentation Delay Min"))
return;
if (!print_ase_pd(frame, " Preferred Presentation Delay Max"))
return;
if (!print_ase_codec(frame))
return;
print_ase_cc(frame);
}
static bool print_ase_framing(const struct l2cap_frame *frame,
const char *label)
{
uint8_t framing;
if (!l2cap_frame_get_u8((void *)frame, &framing)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
switch (framing) {
case 0x00:
print_field("%s: Unframed (0x00)", label);
break;
case 0x01:
print_field("%s: Framed (0x01)", label);
break;
default:
print_field("%s: Reserved (0x%2.2x)", label, framing);
}
return true;
}
static const struct bitfield_data phy_table[] = {
{ 0, "LE 1M PHY (0x01)" },
{ 1, "LE 2M PHY (0x02)" },
{ 2, "LE Codec PHY (0x04)" },
{ }
};
static bool print_ase_phy(const struct l2cap_frame *frame, const char *label)
{
uint8_t phy, mask;
if (!l2cap_frame_get_u8((void *)frame, &phy)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: 0x%2.2x", label, phy);
mask = print_bitfield(4, phy, phy_table);
if (mask)
print_text(COLOR_WHITE_BG, " Unknown fields (0x%2.2x)",
mask);
return true;
}
static bool print_ase_interval(const struct l2cap_frame *frame,
const char *label)
{
uint32_t interval;
if (!l2cap_frame_get_le24((void *)frame, &interval)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: %u usec", label, interval);
return true;
}
static bool print_ase_sdu(const struct l2cap_frame *frame, const char *label)
{
uint16_t sdu;
if (!l2cap_frame_get_le16((void *)frame, &sdu)) {
print_text(COLOR_ERROR, "%s: invalid size", label);
return false;
}
print_field("%s: %u", label, sdu);
return true;
}
static void print_ase_qos(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " CIG ID"))
return;
if (!l2cap_frame_print_u8((void *)frame, " CIS ID"))
return;
if (!print_ase_interval(frame, " SDU Interval"))
return;
if (!print_ase_framing(frame, " Framing"))
return;
if (!print_ase_phy(frame, " PHY"))
return;
if (!print_ase_sdu(frame, " Max SDU"))
return;
if (!print_ase_rtn(frame, " RTN"))
return;
if (!print_ase_latency(frame, " Max Transport Latency"))
return;
print_ase_pd(frame, " Presentation Delay");
}
static void print_ase_metadata_status(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " CIG ID"))
return;
if (!l2cap_frame_print_u8((void *)frame, " CIS ID"))
return;
print_ase_metadata(frame);
}
static void print_ase_status(const struct l2cap_frame *frame)
{
uint8_t id, state;
if (!l2cap_frame_get_u8((void *)frame, &id)) {
print_text(COLOR_ERROR, "ASE ID: invalid size");
goto done;
}
print_field(" ASE ID: %u", id);
if (!l2cap_frame_get_u8((void *)frame, &state)) {
print_text(COLOR_ERROR, "ASE State: invalid size");
goto done;
}
switch (state) {
/* ASE_State = 0x00 (Idle) */
case 0x00:
print_field(" State: Idle (0x00)");
break;
/* ASE_State = 0x01 (Codec Configured) */
case 0x01:
print_field(" State: Codec Configured (0x01)");
print_ase_config(frame);
break;
/* ASE_State = 0x02 (QoS Configured) */
case 0x02:
print_field(" State: QoS Configured (0x02)");
print_ase_qos(frame);
break;
/* ASE_Status = 0x03 (Enabling) */
case 0x03:
print_field(" State: Enabling (0x03)");
print_ase_metadata_status(frame);
break;
/* ASE_Status = 0x04 (Streaming) */
case 0x04:
print_field(" State: Streaming (0x04)");
print_ase_metadata_status(frame);
break;
/* ASE_Status = 0x05 (Disabling) */
case 0x05:
print_field(" State: Disabling (0x05)");
print_ase_metadata_status(frame);
break;
/* ASE_Status = 0x06 (Releasing) */
case 0x06:
print_field(" State: Releasing (0x06)");
break;
default:
print_field(" State: Reserved (0x%2.2x)", state);
break;
}
done:
if (frame->size)
print_hex_field(" Data", frame->data, frame->size);
}
static void ase_read(const struct l2cap_frame *frame)
{
print_ase_status(frame);
}
static void ase_notify(const struct l2cap_frame *frame)
{
print_ase_status(frame);
}
static bool print_ase_target_latency(const struct l2cap_frame *frame)
{
uint8_t latency;
if (!l2cap_frame_get_u8((void *)frame, &latency)) {
print_text(COLOR_ERROR, " Target Latency: invalid size");
return false;
}
switch (latency) {
case 0x01:
print_field(" Target Latency: Low Latency (0x01)");
break;
case 0x02:
print_field(" Target Latency: Balance Latency/Reliability "
"(0x02)");
break;
case 0x03:
print_field(" Target Latency: High Reliability (0x03)");
break;
default:
print_field(" Target Latency: Reserved (0x%2.2x)", latency);
break;
}
return true;
}
static bool ase_config_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
if (!print_ase_target_latency(frame))
return false;
if (!print_ase_phy(frame, " PHY"))
return false;
if (!print_ase_codec(frame))
return false;
if (!print_ase_cc(frame))
return false;
return true;
}
static bool ase_qos_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
if (!l2cap_frame_print_u8((void *)frame, " CIG ID"))
return false;
if (!l2cap_frame_print_u8((void *)frame, " CIS ID"))
return false;
if (!print_ase_interval(frame, " SDU Interval"))
return false;
if (!print_ase_framing(frame, " Framing"))
return false;
if (!print_ase_phy(frame, " PHY"))
return false;
if (!print_ase_sdu(frame, " Max SDU"))
return false;
if (!print_ase_rtn(frame, " RTN"))
return false;
if (!print_ase_latency(frame, " Max Transport Latency"))
return false;
if (!print_ase_pd(frame, " Presentation Delay"))
return false;
return true;
}
static bool ase_enable_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
if (!print_ase_metadata(frame))
return false;
return true;
}
static bool ase_start_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
return true;
}
static bool ase_disable_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
return true;
}
static bool ase_stop_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
return true;
}
static bool ase_metadata_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
if (!print_ase_metadata(frame))
return false;
return true;
}
static bool ase_release_cmd(const struct l2cap_frame *frame)
{
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
return false;
return true;
}
#define ASE_CMD(_op, _desc, _func) \
[_op] = { \
.desc = _desc, \
.func = _func, \
}
struct ase_cmd {
const char *desc;
bool (*func)(const struct l2cap_frame *frame);
} ase_cmd_table[] = {
/* Opcode = 0x01 (Codec Configuration) */
ASE_CMD(0x01, "Codec Configuration", ase_config_cmd),
/* Opcode = 0x02 (QoS Configuration) */
ASE_CMD(0x02, "QoS Configuration", ase_qos_cmd),
/* Opcode = 0x03 (Enable) */
ASE_CMD(0x03, "Enable", ase_enable_cmd),
/* Opcode = 0x04 (Receiver Start Ready) */
ASE_CMD(0x04, "Receiver Start Ready", ase_start_cmd),
/* Opcode = 0x05 (Disable) */
ASE_CMD(0x05, "Disable", ase_disable_cmd),
/* Opcode = 0x06 (Receiver Stop Ready) */
ASE_CMD(0x06, "Receiver Stop Ready", ase_stop_cmd),
/* Opcode = 0x07 (Update Metadata) */
ASE_CMD(0x07, "Update Metadata", ase_metadata_cmd),
/* Opcode = 0x08 (Release) */
ASE_CMD(0x08, "Release", ase_release_cmd),
};
static struct ase_cmd *ase_get_cmd(uint8_t op)
{
if (op > ARRAY_SIZE(ase_cmd_table))
return NULL;
return &ase_cmd_table[op];
}
static void print_ase_cmd(const struct l2cap_frame *frame)
{
uint8_t op, num, i;
struct ase_cmd *cmd;
if (!l2cap_frame_get_u8((void *)frame, &op)) {
print_text(COLOR_ERROR, "opcode: invalid size");
goto done;
}
if (!l2cap_frame_get_u8((void *)frame, &num)) {
print_text(COLOR_ERROR, "num: invalid size");
goto done;
}
cmd = ase_get_cmd(op);
if (!cmd) {
print_field(" Opcode: Reserved (0x%2.2x)", op);
goto done;
}
print_field(" Opcode: %s (0x%2.2x)", cmd->desc, op);
print_field(" Number of ASE(s): %u", num);
for (i = 0; i < num && frame->size; i++) {
print_field(" ASE: #%u", i);
if (!cmd->func(frame))
break;
}
done:
if (frame->size)
print_hex_field(" Data", frame->data, frame->size);
}
static void ase_cp_write(const struct l2cap_frame *frame)
{
print_ase_cmd(frame);
}
static bool print_ase_cp_rsp_code(const struct l2cap_frame *frame)
{
uint8_t code;
if (!l2cap_frame_get_u8((void *)frame, &code)) {
print_text(COLOR_ERROR, " ASE Response Code: invalid size");
return false;
}
switch (code) {
case 0x00:
print_field(" ASE Response Code: Success (0x00)");
break;
case 0x01:
print_field(" ASE Response Code: Unsupported Opcode (0x01)");
break;
case 0x02:
print_field(" ASE Response Code: Invalid Length (0x02)");
break;
case 0x03:
print_field(" ASE Response Code: Invalid ASE ID (0x03)");
break;
case 0x04:
print_field(" ASE Response Code: Invalid ASE State (0x04)");
break;
case 0x05:
print_field(" ASE Response Code: Invalid ASE Direction "
"(0x05)");
break;
case 0x06:
print_field(" ASE Response Code: Unsupported Audio "
"Capabilities (0x06)");
break;
case 0x07:
print_field(" ASE Response Code: Unsupported Configuration "
"(0x07)");
break;
case 0x08:
print_field(" ASE Response Code: Rejected Configuration "
"(0x08)");
break;
case 0x09:
print_field(" ASE Response Code: Invalid Configuration "
"(0x09)");
break;
case 0x0a:
print_field(" ASE Response Code: Unsupported Metadata "
"(0x0a)");
break;
case 0x0b:
print_field(" ASE Response Code: Rejected Metadata (0x0b)");
break;
case 0x0c:
print_field(" ASE Response Code: Invalid Metadata (0x0c)");
break;
case 0x0d:
print_field(" ASE Response Code: Insufficient Resources "
"(0x0d)");
break;
case 0x0e:
print_field(" ASE Response Code: Unspecified Error (0x0e)");
break;
default:
print_field(" ASE Response Code: Reserved (0x%2.2x)", code);
break;
}
return true;
}
static bool print_ase_cp_rsp_reason(const struct l2cap_frame *frame)
{
uint8_t reason;
if (!l2cap_frame_get_u8((void *)frame, &reason)) {
print_text(COLOR_ERROR,
" ASE Response Reason: invalid size");
return false;
}
switch (reason) {
case 0x00:
print_field(" ASE Response Reason: None (0x00)");
break;
case 0x01:
print_field(" ASE Response Reason: ASE ID (0x01)");
break;
case 0x02:
print_field(" ASE Response Reason: Codec Specific "
"Configuration (0x02)");
break;
case 0x03:
print_field(" ASE Response Reason: SDU Interval (0x03)");
break;
case 0x04:
print_field(" ASE Response Reason: Framing (0x04)");
break;
case 0x05:
print_field(" ASE Response Reason: PHY (0x05)");
break;
case 0x06:
print_field(" ASE Response Reason: Max SDU (0x06)");
break;
case 0x07:
print_field(" ASE Response Reason: RTN (0x07)");
break;
case 0x08:
print_field(" ASE Response Reason: Max Transport Latency "
"(0x08)");
break;
case 0x09:
print_field(" ASE Response Reason: Presentation Delay "
"(0x09)");
break;
case 0x0a:
print_field(" ASE Response Reason: Invalid ASE/CIS Mapping "
"(0x0a)");
break;
default:
print_field(" ASE Response Reason: Reserved (0x%2.2x)",
reason);
break;
}
return true;
}
static void print_ase_cp_rsp(const struct l2cap_frame *frame)
{
uint8_t op, num, i;
struct ase_cmd *cmd;
if (!l2cap_frame_get_u8((void *)frame, &op)) {
print_text(COLOR_ERROR, " opcode: invalid size");
goto done;
}
if (!l2cap_frame_get_u8((void *)frame, &num)) {
print_text(COLOR_ERROR, " Number of ASE(s): invalid size");
goto done;
}
cmd = ase_get_cmd(op);
if (!cmd) {
print_field(" Opcode: Reserved (0x%2.2x)", op);
goto done;
}
print_field(" Opcode: %s (0x%2.2x)", cmd->desc, op);
print_field(" Number of ASE(s): %u", num);
for (i = 0; i < num && frame->size; i++) {
print_field(" ASE: #%u", i);
if (!l2cap_frame_print_u8((void *)frame, " ASE ID"))
break;
if (!print_ase_cp_rsp_code(frame))
break;
if (!print_ase_cp_rsp_reason(frame))
break;
}
done:
if (frame->size)
print_hex_field(" Data", frame->data, frame->size);
}
static void ase_cp_notify(const struct l2cap_frame *frame)
{
print_ase_cp_rsp(frame);
}
#define GATT_HANDLER(_uuid, _read, _write, _notify) \
{ \
.uuid = { \
.type = BT_UUID16, \
.value.u16 = _uuid, \
}, \
.read = _read, \
.write = _write, \
.notify = _notify \
}
struct gatt_handler {
bt_uuid_t uuid;
void (*read)(const struct l2cap_frame *frame);
void (*write)(const struct l2cap_frame *frame);
void (*notify)(const struct l2cap_frame *frame);
} gatt_handlers[] = {
GATT_HANDLER(0x2902, ccc_read, ccc_write, NULL),
GATT_HANDLER(0x2bc4, ase_read, NULL, ase_notify),
GATT_HANDLER(0x2bc5, ase_read, NULL, ase_notify),
GATT_HANDLER(0x2bc6, NULL, ase_cp_write, ase_cp_notify),
GATT_HANDLER(0x2bc9, pac_read, NULL, pac_notify),
GATT_HANDLER(0x2bcb, pac_read, NULL, pac_notify),
};
static struct gatt_handler *get_handler(struct gatt_db_attribute *attr)
{
const bt_uuid_t *uuid = gatt_db_attribute_get_type(attr);
size_t i;
for (i = 0; i < ARRAY_SIZE(gatt_handlers); i++) {
struct gatt_handler *handler = &gatt_handlers[i];
if (!bt_uuid_cmp(&handler->uuid, uuid))
return handler;
}
return NULL;
}
static void att_exchange_mtu_req(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_exchange_mtu_req *pdu = frame->data;
print_field("Client RX MTU: %d", le16_to_cpu(pdu->mtu));
}
static void att_exchange_mtu_rsp(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_exchange_mtu_rsp *pdu = frame->data;
print_field("Server RX MTU: %d", le16_to_cpu(pdu->mtu));
}
static void att_find_info_req(const struct l2cap_frame *frame)
{
print_handle_range("Handle range", frame->data);
}
static const char *att_format_str(uint8_t format)
{
switch (format) {
case 0x01:
return "UUID-16";
case 0x02:
return "UUID-128";
default:
return "unknown";
}
}
static uint16_t print_info_data_16(const void *data, uint16_t len)
{
while (len >= 4) {
print_field("Handle: 0x%4.4x", get_le16(data));
print_uuid("UUID", data + 2, 2);
data += 4;
len -= 4;
}
return len;
}
static uint16_t print_info_data_128(const void *data, uint16_t len)
{
while (len >= 18) {
print_field("Handle: 0x%4.4x", get_le16(data));
print_uuid("UUID", data + 2, 16);
data += 18;
len -= 18;
}
return len;
}
static void att_find_info_rsp(const struct l2cap_frame *frame)
{
const uint8_t *format = frame->data;
uint16_t len;
print_field("Format: %s (0x%2.2x)", att_format_str(*format), *format);
if (*format == 0x01)
len = print_info_data_16(frame->data + 1, frame->size - 1);
else if (*format == 0x02)
len = print_info_data_128(frame->data + 1, frame->size - 1);
else
len = frame->size - 1;
packet_hexdump(frame->data + (frame->size - len), len);
}
static void att_find_by_type_val_req(const struct l2cap_frame *frame)
{
uint16_t type;
print_handle_range("Handle range", frame->data);
type = get_le16(frame->data + 4);
print_attribute_info(type, frame->data + 6, frame->size - 6);
}
static void att_find_by_type_val_rsp(const struct l2cap_frame *frame)
{
const uint8_t *ptr = frame->data;
uint16_t len = frame->size;
while (len >= 4) {
print_handle_range("Handle range", ptr);
ptr += 4;
len -= 4;
}
packet_hexdump(ptr, len);
}
static void att_read_type_req(const struct l2cap_frame *frame)
{
print_handle_range("Handle range", frame->data);
print_uuid("Attribute type", frame->data + 4, frame->size - 4);
}
static void att_read_type_rsp(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_read_group_type_rsp *pdu = frame->data;
print_field("Attribute data length: %d", pdu->length);
print_data_list("Attribute data list", pdu->length,
frame->data + 1, frame->size - 1);
}
struct att_read {
struct gatt_db_attribute *attr;
bool in;
uint16_t chan;
void (*func)(const struct l2cap_frame *frame);
};
struct att_conn_data {
struct gatt_db *ldb;
struct gatt_db *rdb;
struct queue *reads;
};
static void att_conn_data_free(void *data)
{
struct att_conn_data *att_data = data;
gatt_db_unref(att_data->rdb);
gatt_db_unref(att_data->ldb);
queue_destroy(att_data->reads, free);
free(att_data);
}
static void load_gatt_db(struct packet_conn_data *conn)
{
struct att_conn_data *data = conn->data;
char filename[PATH_MAX];
char local[18];
char peer[18];
if (!data) {
data = new0(struct att_conn_data, 1);
data->rdb = gatt_db_new();
data->ldb = gatt_db_new();
conn->data = data;
conn->destroy = att_conn_data_free;
}
if (!gatt_db_isempty(data->ldb) && !gatt_db_isempty(data->rdb))
return;
ba2str((bdaddr_t *)conn->src, local);
ba2str((bdaddr_t *)conn->dst, peer);
if (gatt_db_isempty(data->ldb)) {
create_filename(filename, PATH_MAX, "/%s/attributes", local);
btd_settings_gatt_db_load(data->ldb, filename);
}
if (gatt_db_isempty(data->rdb)) {
create_filename(filename, PATH_MAX, "/%s/cache/%s", local,
peer);
btd_settings_gatt_db_load(data->rdb, filename);
}
}
static struct gatt_db_attribute *get_attribute(const struct l2cap_frame *frame,
uint16_t handle, bool rsp)
{
struct packet_conn_data *conn;
struct att_conn_data *data;
struct gatt_db *db;
conn = packet_get_conn_data(frame->handle);
if (!conn)
return NULL;
/* Try loading local and remote gatt_db if not loaded yet */
load_gatt_db(conn);
data = conn->data;
if (!data)
return NULL;
if (frame->in) {
if (rsp)
db = data->rdb;
else
db = data->ldb;
} else {
if (rsp)
db = data->ldb;
else
db = data->rdb;
}
return gatt_db_get_attribute(db, handle);
}
static void print_handle(const struct l2cap_frame *frame, uint16_t handle,
bool rsp)
{
struct gatt_db_attribute *attr;
const bt_uuid_t *uuid;
char label[21];
attr = get_attribute(frame, handle, rsp);
if (!attr)
goto done;
uuid = gatt_db_attribute_get_type(attr);
if (!uuid)
goto done;
switch (uuid->type) {
case BT_UUID16:
sprintf(label, "Handle: 0x%4.4x Type", handle);
print_uuid(label, &cpu_to_le16(uuid->value.u16), 2);
return;
case BT_UUID128:
sprintf(label, "Handle: 0x%4.4x Type", handle);
print_uuid(label, &uuid->value.u128, 16);
return;
case BT_UUID_UNSPEC:
case BT_UUID32:
break;
}
done:
print_field("Handle: 0x%4.4x", handle);
}
static void att_read_req(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_read_req *pdu = frame->data;
uint16_t handle;
struct packet_conn_data *conn;
struct att_conn_data *data;
struct att_read *read;
struct gatt_db_attribute *attr;
struct gatt_handler *handler;
l2cap_frame_pull((void *)frame, frame, sizeof(*pdu));
handle = le16_to_cpu(pdu->handle);
print_handle(frame, handle, false);
attr = get_attribute(frame, handle, false);
if (!attr)
return;
handler = get_handler(attr);
if (!handler || !handler->read)
return;
conn = packet_get_conn_data(frame->handle);
data = conn->data;
if (!data->reads)
data->reads = queue_new();
read = new0(struct att_read, 1);
read->attr = attr;
read->in = frame->in;
read->chan = frame->chan;
read->func = handler->read;
queue_push_tail(data->reads, read);
}
static bool match_read_frame(const void *data, const void *match_data)
{
const struct att_read *read = data;
const struct l2cap_frame *frame = match_data;
/* Read frame and response frame shall be in the opposite direction to
* match.
*/
if (read->in == frame->in)
return false;
return read->chan == frame->chan;
}
static void att_read_rsp(const struct l2cap_frame *frame)
{
struct packet_conn_data *conn;
struct att_conn_data *data;
struct att_read *read;
print_hex_field("Value", frame->data, frame->size);
conn = packet_get_conn_data(frame->handle);
if (!conn)
return;
data = conn->data;
read = queue_remove_if(data->reads, match_read_frame, (void *)frame);
if (!read)
return;
read->func(frame);
free(read);
}
static void att_read_blob_req(const struct l2cap_frame *frame)
{
print_handle(frame, get_le16(frame->data), false);
print_field("Offset: 0x%4.4x", get_le16(frame->data + 2));
}
static void att_read_blob_rsp(const struct l2cap_frame *frame)
{
packet_hexdump(frame->data, frame->size);
}
static void att_read_multiple_req(const struct l2cap_frame *frame)
{
int i, count;
count = frame->size / 2;
for (i = 0; i < count; i++)
print_handle(frame, get_le16(frame->data + (i * 2)), false);
}
static void att_read_group_type_req(const struct l2cap_frame *frame)
{
print_handle_range("Handle range", frame->data);
print_uuid("Attribute group type", frame->data + 4, frame->size - 4);
}
static void print_group_list(const char *label, uint8_t length,
const void *data, uint16_t size)
{
uint8_t count;
if (length == 0)
return;
count = size / length;
print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies");
while (size >= length) {
print_handle_range("Handle range", data);
print_uuid("UUID", data + 4, length - 4);
data += length;
size -= length;
}
packet_hexdump(data, size);
}
static void att_read_group_type_rsp(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_read_group_type_rsp *pdu = frame->data;
print_field("Attribute data length: %d", pdu->length);
print_group_list("Attribute group list", pdu->length,
frame->data + 1, frame->size - 1);
}
static void print_write(const struct l2cap_frame *frame, uint16_t handle,
size_t len)
{
struct gatt_db_attribute *attr;
struct gatt_handler *handler;
print_handle(frame, handle, false);
print_hex_field(" Data", frame->data, frame->size);
if (len > frame->size) {
print_text(COLOR_ERROR, "invalid size");
return;
}
attr = get_attribute(frame, handle, false);
if (!attr)
return;
handler = get_handler(attr);
if (!handler)
return;
handler->write(frame);
}
static void att_write_req(const struct l2cap_frame *frame)
{
uint16_t handle;
if (!l2cap_frame_get_le16((void *)frame, &handle)) {
print_text(COLOR_ERROR, "invalid size");
return;
}
print_write(frame, handle, frame->size);
}
static void att_write_rsp(const struct l2cap_frame *frame)
{
}
static void att_prepare_write_req(const struct l2cap_frame *frame)
{
print_handle(frame, get_le16(frame->data), false);
print_field("Offset: 0x%4.4x", get_le16(frame->data + 2));
print_hex_field(" Data", frame->data + 4, frame->size - 4);
}
static void att_prepare_write_rsp(const struct l2cap_frame *frame)
{
print_handle(frame, get_le16(frame->data), true);
print_field("Offset: 0x%4.4x", get_le16(frame->data + 2));
print_hex_field(" Data", frame->data + 4, frame->size - 4);
}
static void att_execute_write_req(const struct l2cap_frame *frame)
{
uint8_t flags = *(uint8_t *) frame->data;
const char *flags_str;
switch (flags) {
case 0x00:
flags_str = "Cancel all prepared writes";
break;
case 0x01:
flags_str = "Immediately write all pending values";
break;
default:
flags_str = "Unknown";
break;
}
print_field("Flags: %s (0x%02x)", flags_str, flags);
}
static void print_notify(const struct l2cap_frame *frame, uint16_t handle,
size_t len)
{
struct gatt_db_attribute *attr;
struct gatt_handler *handler;
struct l2cap_frame clone;
print_handle(frame, handle, false);
print_hex_field(" Data", frame->data, len);
if (len > frame->size) {
print_text(COLOR_ERROR, "invalid size");
return;
}
attr = get_attribute(frame, handle, false);
if (!attr)
return;
handler = get_handler(attr);
if (!handler)
return;
/* Use a clone if the callback is not expected to parse the whole
* frame.
*/
if (len != frame->size) {
l2cap_frame_clone(&clone, frame);
clone.size = len;
frame = &clone;
}
handler->notify(frame);
}
static void att_handle_value_notify(const struct l2cap_frame *frame)
{
uint16_t handle;
const struct bt_l2cap_att_handle_value_notify *pdu = frame->data;
l2cap_frame_pull((void *)frame, frame, sizeof(*pdu));
handle = le16_to_cpu(pdu->handle);
print_notify(frame, handle, frame->size);
}
static void att_handle_value_ind(const struct l2cap_frame *frame)
{
const struct bt_l2cap_att_handle_value_ind *pdu = frame->data;
l2cap_frame_pull((void *)frame, frame, sizeof(*pdu));
print_notify(frame, le16_to_cpu(pdu->handle), frame->size);
}
static void att_handle_value_conf(const struct l2cap_frame *frame)
{
}
static void att_multiple_vl_rsp(const struct l2cap_frame *frame)
{
struct l2cap_frame *f = (void *) frame;
while (frame->size) {
uint16_t handle;
uint16_t len;
if (!l2cap_frame_get_le16(f, &handle))
return;
if (!l2cap_frame_get_le16(f, &len))
return;
print_field("Length: 0x%4.4x", len);
print_notify(frame, handle, len);
l2cap_frame_pull(f, f, len);
}
}
static void att_write_command(const struct l2cap_frame *frame)
{
uint16_t handle;
if (!l2cap_frame_get_le16((void *)frame, &handle)) {
print_text(COLOR_ERROR, "invalid size");
return;
}
print_write(frame, handle, frame->size);
}
static void att_signed_write_command(const struct l2cap_frame *frame)
{
uint16_t handle;
if (!l2cap_frame_get_le16((void *)frame, &handle)) {
print_text(COLOR_ERROR, "invalid size");
return;
}
print_write(frame, handle, frame->size - 12);
print_hex_field(" Data", frame->data, frame->size - 12);
print_hex_field(" Signature", frame->data + frame->size - 12, 12);
}
struct att_opcode_data {
uint8_t opcode;
const char *str;
void (*func) (const struct l2cap_frame *frame);
uint8_t size;
bool fixed;
};
static const struct att_opcode_data att_opcode_table[] = {
{ 0x01, "Error Response",
att_error_response, 4, true },
{ 0x02, "Exchange MTU Request",
att_exchange_mtu_req, 2, true },
{ 0x03, "Exchange MTU Response",
att_exchange_mtu_rsp, 2, true },
{ 0x04, "Find Information Request",
att_find_info_req, 4, true },
{ 0x05, "Find Information Response",
att_find_info_rsp, 5, false },
{ 0x06, "Find By Type Value Request",
att_find_by_type_val_req, 6, false },
{ 0x07, "Find By Type Value Response",
att_find_by_type_val_rsp, 4, false },
{ 0x08, "Read By Type Request",
att_read_type_req, 6, false },
{ 0x09, "Read By Type Response",
att_read_type_rsp, 3, false },
{ 0x0a, "Read Request",
att_read_req, 2, true },
{ 0x0b, "Read Response",
att_read_rsp, 0, false },
{ 0x0c, "Read Blob Request",
att_read_blob_req, 4, true },
{ 0x0d, "Read Blob Response",
att_read_blob_rsp, 0, false },
{ 0x0e, "Read Multiple Request",
att_read_multiple_req, 4, false },
{ 0x0f, "Read Multiple Response" },
{ 0x10, "Read By Group Type Request",
att_read_group_type_req, 6, false },
{ 0x11, "Read By Group Type Response",
att_read_group_type_rsp, 4, false },
{ 0x12, "Write Request" ,
att_write_req, 2, false },
{ 0x13, "Write Response",
att_write_rsp, 0, true },
{ 0x16, "Prepare Write Request",
att_prepare_write_req, 4, false },
{ 0x17, "Prepare Write Response",
att_prepare_write_rsp, 4, false },
{ 0x18, "Execute Write Request",
att_execute_write_req, 1, true },
{ 0x19, "Execute Write Response" },
{ 0x1b, "Handle Value Notification",
att_handle_value_notify, 2, false },
{ 0x1d, "Handle Value Indication",
att_handle_value_ind, 2, false },
{ 0x1e, "Handle Value Confirmation",
att_handle_value_conf, 0, true },
{ 0x20, "Read Multiple Request Variable Length",
att_read_multiple_req, 4, false },
{ 0x21, "Read Multiple Response Variable Length",
att_multiple_vl_rsp, 4, false },
{ 0x23, "Handle Multiple Value Notification",
att_multiple_vl_rsp, 4, false },
{ 0x52, "Write Command",
att_write_command, 2, false },
{ 0xd2, "Signed Write Command", att_signed_write_command, 14, false },
{ }
};
static const char *att_opcode_to_str(uint8_t opcode)
{
int i;
for (i = 0; att_opcode_table[i].str; i++) {
if (att_opcode_table[i].opcode == opcode)
return att_opcode_table[i].str;
}
return "Unknown";
}
void att_packet(uint16_t index, bool in, uint16_t handle, uint16_t cid,
const void *data, uint16_t size)
{
struct l2cap_frame frame;
uint8_t opcode = *((const uint8_t *) data);
const struct att_opcode_data *opcode_data = NULL;
const char *opcode_color, *opcode_str;
int i;
if (size < 1) {
print_text(COLOR_ERROR, "malformed attribute packet");
packet_hexdump(data, size);
return;
}
for (i = 0; att_opcode_table[i].str; i++) {
if (att_opcode_table[i].opcode == opcode) {
opcode_data = &att_opcode_table[i];
break;
}
}
if (opcode_data) {
if (opcode_data->func) {
if (in)
opcode_color = COLOR_MAGENTA;
else
opcode_color = COLOR_BLUE;
} else
opcode_color = COLOR_WHITE_BG;
opcode_str = opcode_data->str;
} else {
opcode_color = COLOR_WHITE_BG;
opcode_str = "Unknown";
}
print_indent(6, opcode_color, "ATT: ", opcode_str, COLOR_OFF,
" (0x%2.2x) len %d", opcode, size - 1);
if (!opcode_data || !opcode_data->func) {
packet_hexdump(data + 1, size - 1);
return;
}
if (opcode_data->fixed) {
if (size - 1 != opcode_data->size) {
print_text(COLOR_ERROR, "invalid size");
packet_hexdump(data + 1, size - 1);
return;
}
} else {
if (size - 1 < opcode_data->size) {
print_text(COLOR_ERROR, "too short packet");
packet_hexdump(data + 1, size - 1);
return;
}
}
l2cap_frame_init(&frame, index, in, handle, 0, cid, 0,
data + 1, size - 1);
opcode_data->func(&frame);
}