Bluetooth native dumpsys logging support (5/5) Added 'btsnooz' debug tool to interpret BTSnoop traces from a bugreport Bug: 18508263 Change-Id: I774c712300e805e7072c7a453e84b2a06eb67984
diff --git a/tools/btsnooz/Android.mk b/tools/btsnooz/Android.mk new file mode 100644 index 0000000..5364b0f --- /dev/null +++ b/tools/btsnooz/Android.mk
@@ -0,0 +1,37 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +supported_platforms := linux # For now... Needs to be tested on MacOS/Windows +cur_platform := $(filter $(HOST_OS),$(supported_platforms)) + +ifdef cur_platform + +# Host executable + +include $(CLEAR_VARS) +LOCAL_MODULE := btsnooz +LOCAL_SRC_FILES := btsnooz.cpp btsnooz_utils.cpp +LOCAL_C_INCLUDES := external/zlib system/bt +LOCAL_CFLAGS += -std=c++11 -W -Wall +LOCAL_STATIC_LIBRARIES := libz + +ifeq ($(HOST_OS),linux) + LOCAL_LDLIBS += -lresolv +endif + +include $(BUILD_HOST_EXECUTABLE) + +endif #cur_platform
diff --git a/tools/btsnooz/btsnooz.cpp b/tools/btsnooz/btsnooz.cpp new file mode 100644 index 0000000..567a0ea --- /dev/null +++ b/tools/btsnooz/btsnooz.cpp
@@ -0,0 +1,87 @@ +/****************************************************************************** + * + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include <iostream> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <string> +#include <vector> + +#include "btsnooz_utils.h" + +int main(int argc, char *argv[]) { + if (argc > 3) { + std::cerr << "Usage: " << argv[0] << " [input_file] [output_file]\n"; + return 1; + } + + std::vector<char> buffer; + + int read = 0; + if (argc < 3) { + std::cerr << "<Reading from stdin>\n"; + read = readLog(std::cin, buffer); + + } else { + std::cerr << "<Reading " << argv[1] << ">\n"; + std::ifstream ff(argv[1]); + read = readLog(ff, buffer); + ff.close(); + } + + if (read == 0) { + std::cerr << "File not found or not BTSNOOP data block....\n"; + return 2; + } + + std::cerr << std::setw(8) << read << " bytes of base64 data read\n"; + + read = base64Decode(buffer); + if (read <= 0) { + std::cerr << "Decoding base64 data failed...\n"; + return 3; + } + + std::cerr << std::setw(8) << read << " bytes of compressed data decoded\n"; + + std::vector<uint8_t> uncompressed; + read = inflate(buffer, uncompressed); + if (read <= 0) { + std::cerr << "Error inflating data...\n"; + return 4; + } + + std::cerr << std::setw(8) << read << " bytes of data inflated\n"; + + if (argc < 2) { + std::cerr << "<Writing to stdout>\n"; + read = writeBtSnoop(std::cout, uncompressed); + + } else { + const int arg = argc > 2 ? 2 : 1; + std::cerr << "<Writing " << argv[arg] << ">\n"; + std::ofstream ff(argv[arg]); + read = writeBtSnoop(ff, uncompressed); + ff.close(); + } + + std::cerr << std::setw(8) << read << " btsnoop packets written\n"; + + return 0; +}
diff --git a/tools/btsnooz/btsnooz_utils.cpp b/tools/btsnooz/btsnooz_utils.cpp new file mode 100644 index 0000000..b743997 --- /dev/null +++ b/tools/btsnooz/btsnooz_utils.cpp
@@ -0,0 +1,244 @@ +/****************************************************************************** + * + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#include <iostream> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <string> +#include <string.h> // for memcpy +#include <vector> +#include <resolv.h> +#include <zlib.h> + +extern "C" { +#include "btif/include/btif_debug_btsnoop.h" +#include "hci/include/bt_hci_bdroid.h" +#include "stack/include/bt_types.h" +#include "stack/include/hcidefs.h" +} + +// Epoch in microseconds since 01/01/0000. +#define BTSNOOP_EPOCH_DELTA 0x00dcddb30f2f8000ULL + +#define INITIAL_BUFFER_SIZE 131072 +#define INFLATE_BUFFER 16384 + +#define LOG_PREFIX "--- BEGIN:BTSNOOP_LOG_SUMMARY" +#define LOG_POSTFIX "--- END:BTSNOOP_LOG_SUMMARY" + +#define H4_DIRECTION_SENT 0 +#define H4_DIRECTION_RECEIVED 1 + +static uint8_t packetTypeToFlags(const uint8_t type) { + switch (type << 8) { + case MSG_HC_TO_STACK_HCI_ERR: + case MSG_HC_TO_STACK_HCI_ACL: + case MSG_HC_TO_STACK_HCI_SCO: + case MSG_HC_TO_STACK_HCI_EVT: + case MSG_HC_TO_STACK_L2C_SEG_XMIT: + return H4_DIRECTION_RECEIVED; + + case MSG_STACK_TO_HC_HCI_ACL: + case MSG_STACK_TO_HC_HCI_SCO: + case MSG_STACK_TO_HC_HCI_CMD: + return H4_DIRECTION_SENT; + + default: + break; + } + return 0; +} + +static uint8_t packetTypeToHciType(const uint8_t type) { + switch (type << 8 & 0xFF00) { + case MSG_STACK_TO_HC_HCI_CMD: + return HCIT_TYPE_COMMAND; + + case MSG_HC_TO_STACK_HCI_EVT: + return HCIT_TYPE_EVENT; + + case MSG_STACK_TO_HC_HCI_ACL: + case MSG_HC_TO_STACK_HCI_ACL: + return HCIT_TYPE_ACL_DATA; + + case MSG_STACK_TO_HC_HCI_SCO: + case MSG_HC_TO_STACK_HCI_SCO: + return HCIT_TYPE_SCO_DATA; + + default: + break; + } + return 0; +} + +size_t writeBtSnoop(std::ostream &out, std::vector<uint8_t> &in) { + if (in.size() < sizeof(btsnooz_preamble_t)) + return 0; + + // Get preamble + + uint8_t *p = in.data(); + btsnooz_preamble_t *preamble = reinterpret_cast<btsnooz_preamble_t*>(p); + if (preamble->version != BTSNOOZ_CURRENT_VERSION) + return 0; + + // Write header + + const uint8_t header[] = { + 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xea + }; + + out.write(reinterpret_cast<const char*>(header), sizeof(header)); + + // Calculate first timestamp + + uint64_t first_ts = preamble->last_timestamp_ms + BTSNOOP_EPOCH_DELTA; + size_t left = in.size() - sizeof(btsnooz_preamble_t); + p = in.data() + sizeof(btsnooz_preamble_t); + + while (left > sizeof(btsnooz_header_t)) { + btsnooz_header_t *p_hdr = reinterpret_cast<btsnooz_header_t*>(p); + p += sizeof(btsnooz_header_t) + (p_hdr->length - 1); + left -= sizeof(btsnooz_header_t) + (p_hdr->length - 1); + + first_ts -= p_hdr->delta_time_ms; + } + + // Process packets + + size_t packets = 0; + left = in.size() - sizeof(btsnooz_preamble_t); + p = in.data() + sizeof(btsnooz_preamble_t); + + while (left > sizeof(btsnooz_header_t)) { + btsnooz_header_t *p_hdr = reinterpret_cast<btsnooz_header_t*>(p); + p += sizeof(btsnooz_header_t); + left -= sizeof(btsnooz_header_t); + + const uint32_t h_length = htonl(p_hdr->length); + out.write(reinterpret_cast<const char*>(&h_length), 4); + out.write(reinterpret_cast<const char*>(&h_length), 4); + + const uint32_t h_flags = htonl(packetTypeToFlags(p_hdr->type)); + out.write(reinterpret_cast<const char*>(&h_flags), 4); + + const uint32_t h_dropped = 0; + out.write(reinterpret_cast<const char*>(&h_dropped), 4); + + first_ts += p_hdr->delta_time_ms; + const uint32_t h_time_hi = htonl(first_ts >> 32); + const uint32_t h_time_lo = htonl(first_ts & 0xFFFFFFFF); + out.write(reinterpret_cast<const char*>(&h_time_hi), 4); + out.write(reinterpret_cast<const char*>(&h_time_lo), 4); + + const uint8_t type = packetTypeToHciType(p_hdr->type); + out.write(reinterpret_cast<const char*>(&type), 1); + + out.write(reinterpret_cast<const char*>(p), p_hdr->length - 1); + + p += p_hdr->length - 1; + left -= p_hdr->length - 1; + + ++packets; + } + + return packets; +} + +int readLog(std::istream &in, std::vector<char> &buffer) { + buffer.reserve(INITIAL_BUFFER_SIZE); + + std::string line; + + const std::string log_prefix(LOG_PREFIX); + const std::string log_postfix(LOG_POSTFIX); + + bool in_block = false; + + while (std::getline(in, line)) { + // Ensure line endings aren't wonky... + + if (!line.empty() && line[line.size() - 1] == '\r') + line.erase(line.end() - 1); + + // Detect block + + if (!in_block) { + if (line.compare(0, log_prefix.length(), log_prefix) == 0) + in_block = true; + continue; + } + + if (line.compare(0, log_postfix.length(), log_postfix) == 0) + break; + + // Process data + + buffer.insert(buffer.end(), line.begin(), line.end()); + } + + if (buffer.size() != 0) + buffer.push_back(0); + + return buffer.size(); +} + +int base64Decode(std::vector<char> &buffer) { + char *p = buffer.data(); + return b64_pton(p, reinterpret_cast<uint8_t*>(p), buffer.size()); +} + +int inflate(std::vector<char> &in, std::vector<uint8_t> &out) { + out.reserve(in.size()); + + uint8_t buffer[INFLATE_BUFFER]; + z_stream zs; + + int ret = inflateInit(&zs); + if (Z_OK != ret) + return -1; + + // Copy preamble as-is + + for (size_t i = 0; i != sizeof(btsnooz_preamble_t); ++i) { + out.push_back(in[i]); + } + + // De-compress data + + zs.avail_in = in.size() - sizeof(btsnooz_preamble_t);; + zs.next_in = reinterpret_cast<uint8_t*>(in.data()) + sizeof(btsnooz_preamble_t); + + do { + zs.avail_out = INFLATE_BUFFER; + zs.next_out = buffer; + + ret = inflate(&zs, Z_NO_FLUSH); + + size_t read = INFLATE_BUFFER - zs.avail_out; + uint8_t *p = buffer; + while (read--) + out.push_back(*p++); + } while (zs.avail_out == 0); + + inflateEnd(&zs); + + return out.size(); +}
diff --git a/tools/btsnooz/btsnooz_utils.h b/tools/btsnooz/btsnooz_utils.h new file mode 100644 index 0000000..94ada83 --- /dev/null +++ b/tools/btsnooz/btsnooz_utils.h
@@ -0,0 +1,27 @@ +/****************************************************************************** + * + * Copyright (C) 2015 Google, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +#pragma once + +#include <iostream> +#include <vector> + +size_t writeBtSnoop(std::ostream &out, std::vector<uint8_t> &in); +int readLog(std::istream &in, std::vector<char> &buffer); +int base64Decode(std::vector<char> &buffer); +int inflate(std::vector<char> &in, std::vector<uint8_t> &out);