Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 091aebd0 authored by Katherine Lai's avatar Katherine Lai Committed by Gerrit Code Review
Browse files

Merge "Floss: Add additional fields to LE ScanResult"

parents 32187a43 6e7eb9ea
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -237,6 +237,7 @@ struct ScanFilterDBus {

#[dbus_propmap(ScanResult)]
struct ScanResultDBus {
    name: String,
    address: String,
    addr_type: u8,
    event_type: u16,
@@ -246,6 +247,10 @@ struct ScanResultDBus {
    tx_power: i8,
    rssi: i8,
    periodic_adv_int: u16,
    flags: u8,
    service_uuids: Vec<Uuid128Bit>,
    service_data: HashMap<String, Vec<u8>>,
    manufacturer_data: HashMap<u16, Vec<u8>>,
    adv_data: Vec<u8>,
}

+5 −0
Original line number Diff line number Diff line
@@ -210,6 +210,7 @@ struct ScanSettingsDBus {

#[dbus_propmap(ScanResult)]
struct ScanResultDBus {
    name: String,
    address: String,
    addr_type: u8,
    event_type: u16,
@@ -219,6 +220,10 @@ struct ScanResultDBus {
    tx_power: i8,
    rssi: i8,
    periodic_adv_int: u16,
    flags: u8,
    service_uuids: Vec<Uuid128Bit>,
    service_data: HashMap<String, Vec<u8>>,
    manufacturer_data: HashMap<u16, Vec<u8>>,
    adv_data: Vec<u8>,
}

+12 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ use bt_topshim::profiles::gatt::{
    GattServerCallbacksDispatcher, GattStatus, LePhy,
};
use bt_topshim::topstack;
use bt_utils::adv_parser;

use crate::bluetooth::{Bluetooth, IBluetooth};
use crate::bluetooth_adv::{
@@ -631,6 +632,7 @@ pub struct ScanSettings {
/// Represents scan result
#[derive(Debug)]
pub struct ScanResult {
    pub name: String,
    pub address: String,
    pub addr_type: u8,
    pub event_type: u16,
@@ -640,6 +642,11 @@ pub struct ScanResult {
    pub tx_power: i8,
    pub rssi: i8,
    pub periodic_adv_int: u16,
    pub flags: u8,
    pub service_uuids: Vec<Uuid128Bit>,
    /// A map of 128-bit UUID and its corresponding service data.
    pub service_data: HashMap<String, Vec<u8>>,
    pub manufacturer_data: HashMap<u16, Vec<u8>>,
    pub adv_data: Vec<u8>,
}

@@ -2314,6 +2321,7 @@ impl BtifGattScannerCallbacks for BluetoothGatt {
    ) {
        self.scanner_callbacks.for_all_callbacks(|callback| {
            callback.on_scan_result(ScanResult {
                name: adv_parser::extract_name(adv_data.as_slice()),
                address: address.to_string(),
                addr_type,
                event_type,
@@ -2323,6 +2331,10 @@ impl BtifGattScannerCallbacks for BluetoothGatt {
                tx_power,
                rssi,
                periodic_adv_int,
                flags: adv_parser::extract_flags(adv_data.as_slice()),
                service_uuids: adv_parser::extract_service_uuids(adv_data.as_slice()),
                service_data: adv_parser::extract_service_data(adv_data.as_slice()),
                manufacturer_data: adv_parser::extract_manufacturer_data(adv_data.as_slice()),
                adv_data: adv_data.clone(),
            });
        });
+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ version = "0.0.1"
edition = "2021"

[dependencies]
bt_topshim = { path = "../../topshim" }

libc = "0.2"
log = "0.4.14"
nix = "0.19"
+250 −0
Original line number Diff line number Diff line
//! This library provides helper functions to parse info from advertising data.

use std::collections::HashMap;

use bt_topshim::bindings::root::bluetooth::Uuid;
use bt_topshim::btif::Uuid128Bit;

// Advertising data types.
const FLAGS: u8 = 0x01;
const COMPLETE_LIST_128_BIT_SERVICE_UUIDS: u8 = 0x07;
const SHORTENED_LOCAL_NAME: u8 = 0x08;
const COMPLETE_LOCAL_NAME: u8 = 0x09;
const SERVICE_DATA_128_BIT_UUID: u8 = 0x21;
const MANUFACTURER_SPECIFIC_DATA: u8 = 0xff;

struct AdvDataIterator<'a> {
    data: &'a [u8],
    data_type: u8,
    cur: usize, // to keep current position
}

// Iterates over Advertising Data's elements having the given AD type. `next()`
// returns the next slice of the advertising data element excluding the length
// and type.
impl<'a> Iterator for AdvDataIterator<'a> {
    type Item = &'a [u8];
    fn next(&mut self) -> Option<&'a [u8]> {
        let mut i = self.cur;
        while i < self.data.len() {
            let len: usize = self.data[i].into();
            if (len == 0) || (i + len >= self.data.len()) {
                break;
            }
            if self.data[i + 1] == self.data_type {
                self.cur = i + len + 1;
                return Some(&self.data[i + 2..self.cur]);
            }
            i += len + 1;
        }
        None
    }
}

fn iterate_adv_data(data: &[u8], data_type: u8) -> AdvDataIterator {
    AdvDataIterator { data, data_type, cur: 0 }
}

// Helper function to extract flags from advertising data
pub fn extract_flags(bytes: &[u8]) -> u8 {
    iterate_adv_data(bytes, FLAGS).next().map_or(0, |v| v[0])
}

// Helper function to extract service uuids (128bit) from advertising data
pub fn extract_service_uuids(bytes: &[u8]) -> Vec<Uuid128Bit> {
    iterate_adv_data(bytes, COMPLETE_LIST_128_BIT_SERVICE_UUIDS)
        .flat_map(|slice| slice.chunks(16))
        .filter_map(|chunk| Uuid::try_from(chunk.to_vec()).ok().map(|uuid| uuid.uu))
        .collect()
}

// Helper function to extract name from advertising data
pub fn extract_name(bytes: &[u8]) -> String {
    iterate_adv_data(bytes, COMPLETE_LOCAL_NAME)
        .next()
        .or(iterate_adv_data(bytes, SHORTENED_LOCAL_NAME).next())
        .map_or("".to_string(), |v| String::from_utf8_lossy(v).to_string())
}

// Helper function to extract service data from advertising data
pub fn extract_service_data(bytes: &[u8]) -> HashMap<String, Vec<u8>> {
    iterate_adv_data(bytes, SERVICE_DATA_128_BIT_UUID)
        .filter_map(|slice| {
            Uuid::try_from(slice.get(0..16)?.to_vec())
                .ok()
                .map(|uuid| (uuid.to_string(), slice[16..].to_vec()))
        })
        .collect()
}

// Helper function to extract manufacturer data from advertising data
pub fn extract_manufacturer_data(bytes: &[u8]) -> HashMap<u16, Vec<u8>> {
    iterate_adv_data(bytes, MANUFACTURER_SPECIFIC_DATA)
        .filter_map(|slice| {
            slice.get(0..2)?.try_into().ok().map(|be| (u16::from_be_bytes(be), slice[2..].to_vec()))
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_extract_flags() {
        let payload: Vec<u8> = vec![
            2,
            FLAGS,
            3,
            17,
            COMPLETE_LIST_128_BIT_SERVICE_UUIDS,
            0,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
        ];
        let flags = extract_flags(payload.as_slice());
        assert_eq!(flags, 3);
    }

    #[test]
    fn test_extract_service_uuids() {
        let payload: Vec<u8> = vec![2, FLAGS, 3];
        let uuids = extract_service_uuids(payload.as_slice());
        assert_eq!(uuids.len(), 0);

        let payload: Vec<u8> = vec![
            2,
            FLAGS,
            3,
            17,
            COMPLETE_LIST_128_BIT_SERVICE_UUIDS,
            0,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
        ];
        let uuids = extract_service_uuids(payload.as_slice());
        assert_eq!(uuids.len(), 1);
        assert_eq!(
            uuids[0],
            Uuid::try_from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]).unwrap().uu
        );
    }

    #[test]
    fn test_extract_name() {
        let payload: Vec<u8> = vec![2, FLAGS, 3];
        let name = extract_name(payload.as_slice());
        assert_eq!(name, "");

        let payload: Vec<u8> = vec![2, FLAGS, 3, 5, COMPLETE_LOCAL_NAME, 116, 101, 115, 116];
        let name = extract_name(payload.as_slice());
        assert_eq!(name, "test");

        let payload: Vec<u8> = vec![2, FLAGS, 3, 5, SHORTENED_LOCAL_NAME, 116, 101, 115, 116];
        let name = extract_name(payload.as_slice());
        assert_eq!(name, "test");
    }

    #[test]
    fn test_extract_service_data() {
        let payload: Vec<u8> = vec![2, FLAGS, 3];
        let service_data = extract_service_data(payload.as_slice());
        assert_eq!(service_data.len(), 0);

        let payload: Vec<u8> = vec![
            18,
            SERVICE_DATA_128_BIT_UUID,
            0,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            16,
            17,
            SERVICE_DATA_128_BIT_UUID,
            1,
            2,
            3,
            4,
            5,
            6,
            7,
            8,
            9,
            10,
            11,
            12,
            13,
            14,
            15,
            16,
        ];
        let service_data = extract_service_data(payload.as_slice());
        assert_eq!(service_data.len(), 2);
        let expected_uuid =
            Uuid::try_from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
                .unwrap()
                .to_string();
        assert_eq!(service_data.get(&expected_uuid), Some(&vec![16]));
        let expected_uuid =
            Uuid::try_from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
                .unwrap()
                .to_string();
        assert_eq!(service_data.get(&expected_uuid), Some(&vec![]));
    }

    #[test]
    fn test_extract_manufacturer_data() {
        let payload: Vec<u8> = vec![2, FLAGS, 3];
        let manufacturer_data = extract_manufacturer_data(payload.as_slice());
        assert_eq!(manufacturer_data.len(), 0);

        let payload: Vec<u8> = vec![2, MANUFACTURER_SPECIFIC_DATA, 0];
        let manufacturer_data = extract_manufacturer_data(payload.as_slice());
        assert_eq!(manufacturer_data.len(), 0);

        let payload: Vec<u8> =
            vec![4, MANUFACTURER_SPECIFIC_DATA, 0, 1, 2, 3, MANUFACTURER_SPECIFIC_DATA, 1, 2];
        let manufacturer_data = extract_manufacturer_data(payload.as_slice());
        assert_eq!(manufacturer_data.len(), 2);
        assert_eq!(manufacturer_data.get(&1), Some(&vec![2]));
        assert_eq!(manufacturer_data.get(&258), Some(&vec![]));
    }
}
Loading