| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2011-2014 Intel Corporation |
| * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "lib/bluetooth.h" |
| |
| #include "src/shared/util.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/btsnoop.h" |
| #include "monitor/bt.h" |
| #include "analyze.h" |
| |
| struct hci_dev { |
| uint16_t index; |
| uint8_t type; |
| uint8_t bdaddr[6]; |
| struct timeval time_added; |
| struct timeval time_removed; |
| unsigned long num_cmd; |
| unsigned long num_evt; |
| unsigned long num_acl; |
| unsigned long num_sco; |
| unsigned long vendor_diag; |
| unsigned long system_note; |
| unsigned long user_log; |
| unsigned long unknown; |
| uint16_t manufacturer; |
| }; |
| |
| static struct queue *dev_list; |
| |
| static void dev_destroy(void *data) |
| { |
| struct hci_dev *dev = data; |
| const char *str; |
| |
| switch (dev->type) { |
| case 0x00: |
| str = "BR/EDR"; |
| break; |
| case 0x01: |
| str = "AMP"; |
| break; |
| default: |
| str = "unknown"; |
| break; |
| } |
| |
| printf("Found %s controller with index %u\n", str, dev->index); |
| printf(" BD_ADDR %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", |
| dev->bdaddr[5], dev->bdaddr[4], dev->bdaddr[3], |
| dev->bdaddr[2], dev->bdaddr[1], dev->bdaddr[0]); |
| if (dev->manufacturer != 0xffff) |
| printf(" (%s)", bt_compidtostr(dev->manufacturer)); |
| printf("\n"); |
| |
| |
| printf(" %lu commands\n", dev->num_cmd); |
| printf(" %lu events\n", dev->num_evt); |
| printf(" %lu ACL packets\n", dev->num_acl); |
| printf(" %lu SCO packets\n", dev->num_sco); |
| printf(" %lu vendor diagnostics\n", dev->vendor_diag); |
| printf(" %lu system notes\n", dev->system_note); |
| printf(" %lu user logs\n", dev->user_log); |
| printf(" %lu unknown opcodes\n", dev->unknown); |
| printf("\n"); |
| |
| free(dev); |
| } |
| |
| static struct hci_dev *dev_alloc(uint16_t index) |
| { |
| struct hci_dev *dev; |
| |
| dev = new0(struct hci_dev, 1); |
| |
| dev->index = index; |
| dev->manufacturer = 0xffff; |
| |
| return dev; |
| } |
| |
| static bool dev_match_index(const void *a, const void *b) |
| { |
| const struct hci_dev *dev = a; |
| uint16_t index = PTR_TO_UINT(b); |
| |
| return dev->index == index; |
| } |
| |
| static struct hci_dev *dev_lookup(uint16_t index) |
| { |
| struct hci_dev *dev; |
| |
| dev = queue_find(dev_list, dev_match_index, UINT_TO_PTR(index)); |
| if (!dev) { |
| fprintf(stderr, "Creating new device for unknown index\n"); |
| |
| dev = dev_alloc(index); |
| |
| queue_push_tail(dev_list, dev); |
| } |
| |
| return dev; |
| } |
| |
| static void new_index(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct btsnoop_opcode_new_index *ni = data; |
| struct hci_dev *dev; |
| |
| dev = dev_alloc(index); |
| |
| dev->type = ni->type; |
| memcpy(dev->bdaddr, ni->bdaddr, 6); |
| |
| queue_push_tail(dev_list, dev); |
| } |
| |
| static void del_index(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| struct hci_dev *dev; |
| |
| dev = queue_remove_if(dev_list, dev_match_index, UINT_TO_PTR(index)); |
| if (!dev) { |
| fprintf(stderr, "Remove for an unexisting device\n"); |
| return; |
| } |
| |
| dev_destroy(dev); |
| } |
| |
| static void command_pkt(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_cmd_hdr *hdr = data; |
| struct hci_dev *dev; |
| |
| data += sizeof(*hdr); |
| size -= sizeof(*hdr); |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->num_cmd++; |
| } |
| |
| static void rsp_read_bd_addr(struct hci_dev *dev, struct timeval *tv, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_rsp_read_bd_addr *rsp = data; |
| |
| printf("Read BD Addr event with status 0x%2.2x\n", rsp->status); |
| |
| if (rsp->status) |
| return; |
| |
| memcpy(dev->bdaddr, rsp->bdaddr, 6); |
| } |
| |
| static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_evt_cmd_complete *evt = data; |
| uint16_t opcode; |
| |
| data += sizeof(*evt); |
| size -= sizeof(*evt); |
| |
| opcode = le16_to_cpu(evt->opcode); |
| |
| switch (opcode) { |
| case BT_HCI_CMD_READ_BD_ADDR: |
| rsp_read_bd_addr(dev, tv, data, size); |
| break; |
| } |
| } |
| |
| static void event_pkt(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_evt_hdr *hdr = data; |
| struct hci_dev *dev; |
| |
| data += sizeof(*hdr); |
| size -= sizeof(*hdr); |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->num_evt++; |
| |
| switch (hdr->evt) { |
| case BT_HCI_EVT_CMD_COMPLETE: |
| evt_cmd_complete(dev, tv, data, size); |
| break; |
| } |
| } |
| |
| static void acl_pkt(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_acl_hdr *hdr = data; |
| struct hci_dev *dev; |
| |
| data += sizeof(*hdr); |
| size -= sizeof(*hdr); |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->num_acl++; |
| } |
| |
| static void sco_pkt(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct bt_hci_sco_hdr *hdr = data; |
| struct hci_dev *dev; |
| |
| data += sizeof(*hdr); |
| size -= sizeof(*hdr); |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->num_sco++; |
| } |
| |
| static void info_index(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| const struct btsnoop_opcode_index_info *hdr = data; |
| struct hci_dev *dev; |
| |
| data += sizeof(*hdr); |
| size -= sizeof(*hdr); |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->manufacturer = hdr->manufacturer; |
| } |
| |
| static void vendor_diag(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| struct hci_dev *dev; |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->vendor_diag++; |
| } |
| |
| static void system_note(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| struct hci_dev *dev; |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->system_note++; |
| } |
| |
| static void user_log(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| struct hci_dev *dev; |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->user_log++; |
| } |
| |
| static void unknown_opcode(struct timeval *tv, uint16_t index, |
| const void *data, uint16_t size) |
| { |
| struct hci_dev *dev; |
| |
| dev = dev_lookup(index); |
| if (!dev) |
| return; |
| |
| dev->unknown++; |
| } |
| |
| void analyze_trace(const char *path) |
| { |
| struct btsnoop *btsnoop_file; |
| unsigned long num_packets = 0; |
| uint32_t format; |
| |
| btsnoop_file = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT); |
| if (!btsnoop_file) |
| return; |
| |
| format = btsnoop_get_format(btsnoop_file); |
| |
| switch (format) { |
| case BTSNOOP_FORMAT_HCI: |
| case BTSNOOP_FORMAT_UART: |
| case BTSNOOP_FORMAT_MONITOR: |
| break; |
| default: |
| fprintf(stderr, "Unsupported packet format\n"); |
| goto done; |
| } |
| |
| dev_list = queue_new(); |
| |
| while (1) { |
| unsigned char buf[BTSNOOP_MAX_PACKET_SIZE]; |
| struct timeval tv; |
| uint16_t index, opcode, pktlen; |
| |
| if (!btsnoop_read_hci(btsnoop_file, &tv, &index, &opcode, |
| buf, &pktlen)) |
| break; |
| |
| switch (opcode) { |
| case BTSNOOP_OPCODE_NEW_INDEX: |
| new_index(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_DEL_INDEX: |
| del_index(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_COMMAND_PKT: |
| command_pkt(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_EVENT_PKT: |
| event_pkt(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_ACL_TX_PKT: |
| case BTSNOOP_OPCODE_ACL_RX_PKT: |
| acl_pkt(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_SCO_TX_PKT: |
| case BTSNOOP_OPCODE_SCO_RX_PKT: |
| sco_pkt(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_OPEN_INDEX: |
| case BTSNOOP_OPCODE_CLOSE_INDEX: |
| break; |
| case BTSNOOP_OPCODE_INDEX_INFO: |
| info_index(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_VENDOR_DIAG: |
| vendor_diag(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_SYSTEM_NOTE: |
| system_note(&tv, index, buf, pktlen); |
| break; |
| case BTSNOOP_OPCODE_USER_LOGGING: |
| user_log(&tv, index, buf, pktlen); |
| break; |
| default: |
| fprintf(stderr, "Unknown opcode %u\n", opcode); |
| unknown_opcode(&tv, index, buf, pktlen); |
| break; |
| } |
| |
| num_packets++; |
| } |
| |
| printf("Trace contains %lu packets\n\n", num_packets); |
| |
| queue_destroy(dev_list, dev_destroy); |
| |
| done: |
| btsnoop_unref(btsnoop_file); |
| } |