術語
在閱讀源碼的過程中發現許多函數名稱帶有意義不明的縮寫,下面是筆者整理的一些縮寫及其對應含義:
- BTIF: Bluetooth Interface
- BTU : Bluetooth Upper Layer
- BTM: Bluetooth Manager
- BTE: Bluetooth embedded system
- BTA :Blueetooth application layer
- CO: call out
- CI: call in
- HF : Handsfree Profile
- HH: HID Host Profile
- HL: Health Device Profile
- av: audio/vidio
- ag: audio gateway
- ar: audio/video registration
- gattc: GATT client
Android Bluetooth Stack
安卓中藍牙協議棧主要分為三個時期,上古時期使用的是BlueZ,后來在4.2之后自己獨立出來稱為BlueDroid,現在好像又改名叫Fluoride了。BlueZ時期和PC上的結構差不多,但是安卓上不使用DBus IPC,因此需要將這部分代碼去除,其他部分可參考BlueZ的介紹。
7.0
在Android<=7.0時期,藍牙協議棧的實現架構如下:
8.0
Android 8.0 以后對藍牙協議棧進行了重構,主要優化是使用HIDL來取代之前的硬件抽象層,方便廠商的接口集成:
實現分析
Android藍牙協議棧的實現在system/bt目錄中,本節記錄下其代碼分析的過程,使用的是 Android 10 分支(ae35d7765)。
首先,該目錄下包含1000+文件,有點無從入手。一般遇到這種情況我們都是從具體的入口函數出發,比如main函數,但這里並不是一個單純的客戶端程序。藍牙協議棧一方面是以系統服務的方式提供接口,另一方面也以client的方式給應用程序提供SDK,不管怎樣,最終都是需要經過HCI協議去與Controller進行交互。
對於BlueZ而言,藍牙協議棧部分在內核中實現,socket系統調用提供了AF_BLUETOOTH的 family,可以支持獲取HCI、L2CAP、RFCOMM類型的socket;但對於BlueDroid而言,協議棧是在用戶層實現的,內核只暴露出HCI(USB/UART)的接口。因此,我們可以從HCI出發,自底向上進行分析,也可以參考上面的框架圖,從用戶應用程序開始,自頂向下進行分析。
用戶層
首先從用戶接口出發,參考Android的開發者文檔是如何發現設備以及創建藍牙連接的:
- https://developer.android.com/guide/topics/connectivity/bluetooth
- https://developer.android.com/guide/topics/connectivity/bluetooth-le
以BR/EDR為例,其中需要注意的是paired和connected的區別:
- paired 表示兩個設備知道彼此的存在,並且已經協商好了鏈路秘鑰(Link Key),可用該秘鑰來進行認證和創建加密鏈接
- connected 表示兩個已經配對的設備創建了一個RFCOMM鏈接,共享一個RFCOMM channel
Android使用藍牙接口的流程大致如下:
// 獲取本地適配器 BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // 開啟藍牙,需要權限 if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } // Discover、Pair、Connect
以設置本機藍牙可被發現(300秒)為例,應用層代碼為:
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);
Settings Activity (Binder Client)
根據intents-filters的介紹,我們知道這是由於其他App去處理的請求,即com.android.settings/.bluetooth.RequestPermissionActivity,該頁面調用彈窗詢問(startActivityForResult)用戶是否允許本設備被發現,並且在回調(onActivityResult)中注冊藍牙的回調中調用:
mBluetoothAdapter.setScanMode( BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout);
frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
@UnsupportedAppUsage public boolean setScanMode(@ScanMode int mode, int duration) { if (getState() != STATE_ON) { return false; } try { mServiceLock.readLock().lock(); if (mService != null) { return mService.setScanMode(mode, duration); } } catch (RemoteException e) { Log.e(TAG, "", e); } finally { mServiceLock.readLock().unlock(); } return false; }
題外話: 上面的annotation表示該接口不是SDK的一部分,在9.0之前APP是可以通過反射進行調用的,9.0之后安卓更新了限制方法,不過也有其他的繞過方式,見: https://stackoverflow.com/questions/55970137/bypass-androids-hidden-api-restrictions
其中mService是IBluetooth類型,直指藍牙服務system/bt/binder/android/bluetooth/IBluetooth.aidl,值得一提的是,該目錄下還包含了數十個AIDL文件,用於描述進程所提供的服務。
AIDL Server
該AIDL的實現在packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java,該Server的JNI實現在packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp,內部主要使用sBluetoothInterface接口來實現功能,該接口的定義為:
static const bt_interface_t* sBluetoothInterface
該接口的實現在btif/src/bluetooth.cc中。
回到SetScanMode,其實現在system/bt/service/adapter.cc:
bool SetScanMode(int scan_mode) override { switch (scan_mode) { case BT_SCAN_MODE_NONE: case BT_SCAN_MODE_CONNECTABLE: case BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE: break; default: LOG(ERROR) << "Unknown scan mode: " << scan_mode; return false; } auto bd_scanmode = static_cast<bt_scan_mode_t>(scan_mode); if (!SetAdapterProperty(BT_PROPERTY_ADAPTER_SCAN_MODE, &bd_scanmode, sizeof(bd_scanmode))) { LOG(ERROR) << "Failed to set scan mode to : " << scan_mode; return false; } return true; }
核心是SetAdapterProperty:
// Sends a request to set the given HAL adapter property type and value. bool SetAdapterProperty(bt_property_type_t type, void* value, int length) { CHECK(length > 0); CHECK(value); bt_property_t property; property.len = length; property.val = value; property.type = type; int status = hal::BluetoothInterface::Get()->GetHALInterface()->set_adapter_property( &property); if (status != BT_STATUS_SUCCESS) { VLOG(1) << "Failed to set property"; return false; } return true; }
其中GetHALInterface是藍牙的核心接口bt_interface_t,定義在接口子系統中system/bt/btif/src/bluetooth.cc,隨后依次調用:
- set_adapter_property
- btif_set_adapter_property (btif/src/btif_core.c)
- BTA_DmSetVisibility (bta/dm/bta_dm_api.cc)
/** This function sets the Bluetooth connectable, discoverable, pairable and * conn paired only modes of local device */ void BTA_DmSetVisibility(tBTA_DM_DISC disc_mode, tBTA_DM_CONN conn_mode, uint8_t pairable_mode, uint8_t conn_paired_only) { do_in_main_thread(FROM_HERE, base::Bind(bta_dm_set_visibility, disc_mode, conn_mode, pairable_mode, conn_paired_only)); }
do_in_main_thread是將任務push到對應的線程任務池中執行,所執行的函數是bta_dm_set_visibility(bta/dm/bta_dm_act.cc),主要功能是根據參數的邏輯分別設置對應的屬性:
- BTM_SetDiscoverability
- BTM_SetConnectability
- BTM_SetPairableMode
stack/btm/btm_inq.cc
這其中涉及了幾個API:
- btm_ble_set_discoverability
- btsnd_hcic_write_cur_iac_lap
- btsnd_hcic_write_inqscan_cfg
- btsnd_hcic_write_scan_enable
第一個API是BLE相關,內部實際上最終也調用了btsnd_hcic_xxx的類似接口。IAC意為Inquiry Access Code,藍牙baseband定義了幾個固定IAC,分別是LIAC和GIAC(見baseband)。LAP是藍牙地址的一部分,如下圖所示:
- NAP: Non-significant Address Part, NAP的值在跳頻同步幀中會用到
- UAP: Upper Address Part,UAP的值會參與對藍牙協議算法的選擇
- LAP: Lower Address Part,由設備廠商分配,LAP的值作為Access Code的一部分,唯一確定某個藍牙設備
- SAP (significant address part) = UAP + LAP
讓我們繼續回到代碼中,以btsnd_hcic_write_cur_iac_lap為例,其實現如下:
// stack/hcic/hcicmds.cc void btsnd_hcic_write_cur_iac_lap(uint8_t num_cur_iac, LAP* const iac_lap) { BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE); uint8_t* pp = (uint8_t*)(p + 1); p->len = HCIC_PREAMBLE_SIZE + 1 + (LAP_LEN * num_cur_iac); p->offset = 0; UINT16_TO_STREAM(pp, HCI_WRITE_CURRENT_IAC_LAP); UINT8_TO_STREAM(pp, p->len - HCIC_PREAMBLE_SIZE); UINT8_TO_STREAM(pp, num_cur_iac); for (int i = 0; i < num_cur_iac; i++) LAP_TO_STREAM(pp, iac_lap[i]); btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); }
UINTx_TO_STREAM(pp, n)的作用是將整數以小端的形式寫入p->data中,最終調用btu_hcif_send_cmd函數發送數據(stack/btu/btu_hcif.cc):
/******************************************************************************* * * Function btu_hcif_send_cmd * * Description This function is called to send commands to the Host * Controller. * * Returns void * ******************************************************************************/ void btu_hcif_send_cmd(UNUSED_ATTR uint8_t controller_id, BT_HDR* p_buf) { if (!p_buf) return; uint16_t opcode; uint8_t* stream = p_buf->data + p_buf->offset; void* vsc_callback = NULL; STREAM_TO_UINT16(opcode, stream); // Eww...horrible hackery here /* If command was a VSC, then extract command_complete callback */ if ((opcode & HCI_GRP_VENDOR_SPECIFIC) == HCI_GRP_VENDOR_SPECIFIC || (opcode == HCI_BLE_RAND) || (opcode == HCI_BLE_ENCRYPT)) { vsc_callback = *((void**)(p_buf + 1)); } // Skip parameter length before logging stream++; btu_hcif_log_command_metrics(opcode, stream, android::bluetooth::hci::STATUS_UNKNOWN, false); hci_layer_get_interface()->transmit_command( p_buf, btu_hcif_command_complete_evt, btu_hcif_command_status_evt, vsc_callback); }
可見p_buf->data中保存的就是HCI數據,前16位為opcode,其中高6字節為ogf,低10字節為ocf,也就是我們平時使用hcitool cmd時的前兩個參數。
HCI 子系統
繼續跟蹤transmit_command,就來到了HCI子系統中(hci/src/hci_layer.cc):
static void transmit_command(BT_HDR* command, command_complete_cb complete_callback, command_status_cb status_callback, void* context) { waiting_command_t* wait_entry = reinterpret_cast<waiting_command_t*>( osi_calloc(sizeof(waiting_command_t))); uint8_t* stream = command->data + command->offset; STREAM_TO_UINT16(wait_entry->opcode, stream); wait_entry->complete_callback = complete_callback; wait_entry->status_callback = status_callback; wait_entry->command = command; wait_entry->context = context; // Store the command message type in the event field // in case the upper layer didn't already command->event = MSG_STACK_TO_HC_HCI_CMD; enqueue_command(wait_entry); }
enqueue_command如其名字所述,就是將待執行的HCI命令放到隊列command_queue的末尾中。那么這個任務隊列在哪消費呢?簡單搜索可以發現:
// Event/packet receiving functions void process_command_credits(int credits) { std::lock_guard<std::mutex> command_credits_lock(command_credits_mutex); if (!hci_thread.IsRunning()) { // HCI Layer was shut down or not running return; } // Subtract commands in flight. command_credits = credits - get_num_waiting_commands(); while (command_credits > 0 && !command_queue.empty()) { if (!hci_thread.DoInThread(FROM_HERE, std::move(command_queue.front()))) { LOG(ERROR) << __func__ << ": failed to enqueue command"; } command_queue.pop(); command_credits--; } }
其調用鏈路為:
- BluetoothHciCallbacks::hciEventReceived (hci/src/hci_layer_android.cc)
- hci_event_received
- filter_incoming_event
- process_command_credits
接收數據
BluetoothHciCallbacks::hciEventReceived這個函數回調是在HCI初始化的時候調用的BluetoothHci::initialize(vendor_libs/linux/interface/bluetooth_hci.cc):
Return<void> BluetoothHci::initialize( const ::android::sp<IBluetoothHciCallbacks>& cb) { ALOGI("BluetoothHci::initialize()"); if (cb == nullptr) { ALOGE("cb == nullptr! -> Unable to call initializationComplete(ERR)"); return Void(); } death_recipient_->setHasDied(false); cb->linkToDeath(death_recipient_, 0); int hci_fd = openBtHci(); auto hidl_status = cb->initializationComplete( hci_fd > 0 ? Status::SUCCESS : Status::INITIALIZATION_ERROR); if (!hidl_status.isOk()) { ALOGE("VendorInterface -> Unable to call initializationComplete(ERR)"); } hci::H4Protocol* h4_hci = new hci::H4Protocol( hci_fd, [cb](const hidl_vec<uint8_t>& packet) { cb->hciEventReceived(packet); }, [cb](const hidl_vec<uint8_t>& packet) { cb->aclDataReceived(packet); }, [cb](const hidl_vec<uint8_t>& packet) { cb->scoDataReceived(packet); }); fd_watcher_.WatchFdForNonBlockingReads( hci_fd, [h4_hci](int fd) { h4_hci->OnDataReady(fd); }); hci_handle_ = h4_hci; unlink_cb_ = [cb](sp<BluetoothDeathRecipient>& death_recipient) { if (death_recipient->getHasDied()) ALOGI("Skipping unlink call, service died."); else cb->unlinkToDeath(death_recipient); }; return Void(); }
fd_watcher_本質上是針對hci_fd文件句柄的讀端事件監控,后者由openBtHci函數產生,該函數由廠商實現,接口文件是hardware/interfaces/bluetooth/1.0/IBluetoothHci.hal。在Linux中的參考實現如下:
// system/bt/vendor_libs/linux/interface/bluetooth_hci.cc int BluetoothHci::openBtHci() { ALOGI( "%s", __func__); int hci_interface = 0; rfkill_state_ = NULL; rfKill(1); int fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (fd < 0) { ALOGE( "Bluetooth socket error: %s", strerror(errno)); return -1; } bt_soc_fd_ = fd; if (waitHciDev(hci_interface)) { ALOGE( "HCI interface (%d) not found", hci_interface); ::close(fd); return -1; } struct sockaddr_hci addr; memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = hci_interface; addr.hci_channel = HCI_CHANNEL_USER; if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { ALOGE( "HCI Channel Control: %s", strerror(errno)); ::close(fd); return -1; } ALOGI( "HCI device ready"); return fd; }
發送數據
繼續回頭接着上節之前的內容講,我們的任務隊列是在process_command_credits中被消費的,取出來之后需要進入到hci_thread線程中執行。從接收數據一節中也能看出,hci接口本身使用的是串行總線,因此不能並發地發送數據,所有命令都是在之前的命令響應后再發送。
值得一提的是,enqueue_command實際上綁定的是函數event_command_ready,以包含我們命令內容和對應回調的類型waiting_command_t為參數:
static void enqueue_command(waiting_command_t* wait_entry) { base::Closure callback = base::Bind(&event_command_ready, wait_entry); //... command_queue.push(std::move(callback)); }
因此,負責執行HCI發送命令的是event_command_ready函數:
static void event_command_ready(waiting_command_t* wait_entry) { { /// Move it to the list of commands awaiting response std::lock_guard<std::recursive_timed_mutex> lock( commands_pending_response_mutex); wait_entry->timestamp = std::chrono::steady_clock::now(); list_append(commands_pending_response, wait_entry); } // Send it off packet_fragmenter->fragment_and_dispatch(wait_entry->command); update_command_response_timer(); }
首先將command放到一個等待響應的隊列里,然后分片發送:
static void fragment_and_dispatch(BT_HDR* packet) { CHECK(packet != NULL); uint16_t event = packet->event & MSG_EVT_MASK; uint8_t* stream = packet->data + packet->offset; // We only fragment ACL packets if (event != MSG_STACK_TO_HC_HCI_ACL) { callbacks->fragmented(packet, true); return; } // ACL/L2CAP fragment... }
實現中只對ACL類型的HCI數據進行分片發送,不管是不是分片,都對最后一個packet調用callbacks->fragmented(),callbacks的類型是packet_fragmenter_callbacks_t,在packet_fragmenter_t->init中初始化並設置。而packet_fragmenter的初始化發生在hci_module_start_up()中,HCI層定義的回調如下:
static const packet_fragmenter_callbacks_t packet_fragmenter_callbacks = { transmit_fragment, dispatch_reassembled, fragmenter_transmit_finished };
fragmented即對應transmit_fragment,對應定義如下:
// Callback for the fragmenter to send a fragment static void transmit_fragment(BT_HDR* packet, bool send_transmit_finished) { btsnoop->capture(packet, false); // HCI command packets are freed on a different thread when the matching // event is received. Check packet->event before sending to avoid a race. bool free_after_transmit = (packet->event & MSG_EVT_MASK) != MSG_STACK_TO_HC_HCI_CMD && send_transmit_finished; hci_transmit(packet); if (free_after_transmit) { buffer_allocator->free(packet); } }
hci_transmit有不同平台的實現,分別在:
- hci/src/hci_layer_linux.c
- hci/src/hci_layer_android.c
前者是通過write直接向HCI socket的fd寫入,后者是調用IBluetoothHci::sendHciCommand去實現,接口定義同樣是在hardware/interfaces/bluetooth/1.0/IBluetoothHci.hal文件中。
因為不同手機廠商的SoC中集成藍牙芯片的接口不同,有的是使用USB連接,有的是使用UART連接,因此需要給安卓提供一個統一的操作接口,這個接口就很適合由HAL(HIDL)來進行抽象。這部分實現通常是使用Linux中已有的UART/USB驅動進行操作,以提高代碼的復用性。
小結
本文通過從從用戶層的一個藍牙接口進行跟蹤,一直向下分析到HCI的硬件抽象層。在這個過程中,穿插了藍牙中的各個子模塊,比如BTA、BTM、BTU 等,並在某些回調注冊的節點中分析了對應的的初始化過程。最后根據初始化以及HCI命令的任務隊列實現,我們也得知了接收數據/事件時的運行流程,當然還包括ACL分片/重組的邏輯等。對整個BlueDroid系統形成大致理解,有助於為后續的代碼審計和漏洞分析奠定基礎。
參考鏈接
- http://www.bluez.org/
- evolution of bluetooth drivers in Linux kernel
- programing bluetooth
- Bluetooth on modern Linux
- bluedroid
- https://medium.com/@muhamed.riyas/android-bluetooth-architecture-853645eff17f
- https://www.sciencedirect.com/topics/computer-science/bluetooth-stack
- https://link.springer.com/content/pdf/10.1007%2F978-0-387-75462-8_20.pdf
- https://www.cnblogs.com/blogs-of-lxl/p/7010061.html



