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

Commit 970c374b authored by En-Shuo Hsu's avatar En-Shuo Hsu
Browse files

Floss: Hook SCO HCI debug dump for Floss

Such that btclient can trigger the debug dump and check the result.

Bug: 272101425
Tag: #floss
Test: build, btclient > media log
Change-Id: I0a7d76db530094e3e109c7e5d6ca3a9ef5694cd5
parent 29f33663
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ shell-words = "1.1.0"
tokio = { version = "1", features = ['bytes', 'fs', 'io-util', 'libc', 'macros', 'memchr', 'mio', 'net', 'num_cpus', 'rt', 'rt-multi-thread', 'sync', 'time', 'tokio-macros'] }

clap = "2.33.3"
chrono = "0.4.24"
bitflags = "1.2"
hex = "0.4.3"
[build-dependencies]
+110 −3
Original line number Diff line number Diff line
@@ -3,9 +3,9 @@ use crate::dbus_iface::{
    export_admin_policy_callback_dbus_intf, export_advertising_set_callback_dbus_intf,
    export_bluetooth_callback_dbus_intf, export_bluetooth_connection_callback_dbus_intf,
    export_bluetooth_gatt_callback_dbus_intf, export_bluetooth_manager_callback_dbus_intf,
    export_gatt_server_callback_dbus_intf, export_qa_callback_dbus_intf,
    export_scanner_callback_dbus_intf, export_socket_callback_dbus_intf,
    export_suspend_callback_dbus_intf,
    export_bluetooth_media_callback_dbus_intf, export_gatt_server_callback_dbus_intf,
    export_qa_callback_dbus_intf, export_scanner_callback_dbus_intf,
    export_socket_callback_dbus_intf, export_suspend_callback_dbus_intf,
};
use crate::ClientContext;
use crate::{console_red, console_yellow, print_error, print_info};
@@ -21,6 +21,7 @@ use btstack::bluetooth_gatt::{
    BluetoothGattService, IBluetoothGattCallback, IBluetoothGattServerCallback, IScannerCallback,
    ScanResult,
};
use btstack::bluetooth_media::{BluetoothAudioDevice, IBluetoothMediaCallback};
use btstack::bluetooth_qa::IBluetoothQACallback;
use btstack::socket_manager::{
    BluetoothServerSocket, BluetoothSocket, IBluetoothSocketManager,
@@ -29,10 +30,12 @@ use btstack::socket_manager::{
use btstack::suspend::ISuspendCallback;
use btstack::uuid::UuidWrapper;
use btstack::{RPCProxy, SuspendMode};
use chrono::{TimeZone, Utc};
use dbus::nonblock::SyncConnection;
use dbus_crossroads::Crossroads;
use dbus_projection::DisconnectWatcher;
use manager_service::iface_bluetooth_manager::IBluetoothManagerCallback;
use std::convert::TryFrom;
use std::io::{Read, Write};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@@ -40,6 +43,10 @@ use std::time::Duration;
const SOCKET_TEST_WRITE: &[u8] =
    b"01234567890123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

// Avoid 32, 40, 64 consecutive hex characters so CrOS feedback redact tool
// doesn't trim our dump.
const BINARY_PACKET_STATUS_WRAP: usize = 50;

/// Callback context for manager interface callbacks.
pub(crate) struct BtManagerCallback {
    objpath: String,
@@ -1287,3 +1294,103 @@ impl RPCProxy for QACallback {
        cr.lock().unwrap().insert(self.get_object_id(), &[iface], Arc::new(Mutex::new(self)));
    }
}

pub(crate) struct MediaCallback {
    objpath: String,

    dbus_connection: Arc<SyncConnection>,
    dbus_crossroads: Arc<Mutex<Crossroads>>,
}

impl MediaCallback {
    pub(crate) fn new(
        objpath: String,
        dbus_connection: Arc<SyncConnection>,
        dbus_crossroads: Arc<Mutex<Crossroads>>,
    ) -> Self {
        Self { objpath, dbus_connection, dbus_crossroads }
    }
}

fn timestamp_to_string(ts_in_us: u64) -> String {
    i64::try_from(ts_in_us)
        .and_then(|ts| Ok(Utc.timestamp_nanos(ts * 1000).to_rfc3339()))
        .unwrap_or("UNKNOWN".to_string())
}

impl IBluetoothMediaCallback for MediaCallback {
    fn on_bluetooth_audio_device_added(&mut self, _device: BluetoothAudioDevice) {}
    fn on_bluetooth_audio_device_removed(&mut self, _addr: String) {}
    fn on_absolute_volume_supported_changed(&mut self, _supported: bool) {}
    fn on_absolute_volume_changed(&mut self, _volume: u8) {}
    fn on_hfp_volume_changed(&mut self, _volume: u8, _addr: String) {}
    fn on_hfp_audio_disconnected(&mut self, _addr: String) {}
    fn on_hfp_debug_dump(
        &mut self,
        active: bool,
        wbs: bool,
        total_num_decoded_frames: i32,
        pkt_loss_ratio: f64,
        begin_ts: u64,
        end_ts: u64,
        pkt_status_in_hex: String,
        pkt_status_in_binary: String,
    ) {
        let wbs_dump = if active && wbs {
            let mut to_split_binary = pkt_status_in_binary.clone();
            let mut wrapped_binary = String::new();
            while to_split_binary.len() > BINARY_PACKET_STATUS_WRAP {
                let remaining = to_split_binary.split_off(BINARY_PACKET_STATUS_WRAP);
                wrapped_binary.push_str(&to_split_binary);
                wrapped_binary.push('\n');
                to_split_binary = remaining;
            }
            wrapped_binary.push_str(&to_split_binary);

            format!(
                "\n--------WBS packet loss--------\n\
                   Decoded Packets: {}, Packet Loss Ratio: {} \n\
                   {} [begin]\n\
                   {} [end]\n\
                   In Hex format:\n\
                   {}\n\
                   In binary format:\n\
                   {}",
                total_num_decoded_frames,
                pkt_loss_ratio,
                timestamp_to_string(begin_ts),
                timestamp_to_string(end_ts),
                pkt_status_in_hex,
                wrapped_binary
            )
        } else {
            "".to_string()
        };

        print_info!(
            "\n--------HFP debug dump---------\n\
             HFP SCO: {}, Codec: {}\
             {}
             ",
            if active { "active" } else { "inactive" },
            if wbs { "mSBC" } else { "CVSD" },
            wbs_dump
        );
    }
}

impl RPCProxy for MediaCallback {
    fn get_object_id(&self) -> String {
        self.objpath.clone()
    }

    fn export_for_rpc(self: Box<Self>) {
        let cr = self.dbus_crossroads.clone();
        let iface = export_bluetooth_media_callback_dbus_intf(
            self.dbus_connection.clone(),
            &mut cr.lock().unwrap(),
            Arc::new(Mutex::new(DisconnectWatcher::new())),
        );
        cr.lock().unwrap().insert(self.get_object_id(), &[iface], Arc::new(Mutex::new(self)));
    }
}
+26 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ use bt_topshim::profiles::sdp::{BtSdpMpsRecord, BtSdpRecord};
use bt_topshim::profiles::{gatt::LePhy, ProfileConnectionState};
use btstack::bluetooth::{BluetoothDevice, IBluetooth};
use btstack::bluetooth_gatt::{GattWriteType, IBluetoothGatt, ScanSettings, ScanType};
use btstack::bluetooth_media::IBluetoothTelephony;
use btstack::bluetooth_media::{IBluetoothMedia, IBluetoothTelephony};
use btstack::bluetooth_qa::IBluetoothQA;
use btstack::socket_manager::{IBluetoothSocketManager, SocketResult};
use btstack::uuid::{Profile, UuidHelper, UuidWrapper};
@@ -325,6 +325,14 @@ fn build_commands() -> HashMap<String, CommandOption> {
            function_pointer: CommandHandler::cmd_telephony,
        },
    );
    command_options.insert(
        String::from("media"),
        CommandOption {
            rules: vec![String::from("media log")],
            description: String::from("Audio tools."),
            function_pointer: CommandHandler::cmd_media,
        },
    );
    command_options.insert(
        String::from("quit"),
        CommandOption {
@@ -1965,6 +1973,23 @@ impl CommandHandler {

        Ok(())
    }

    fn cmd_media(&mut self, args: &Vec<String>) -> CommandResult {
        if !self.context.lock().unwrap().adapter_ready {
            return Err(self.adapter_not_ready());
        }

        match &get_arg(args, 0)?[..] {
            "log" => {
                self.context.lock().unwrap().media_dbus.as_mut().unwrap().trigger_debug_dump();
            }
            other => {
                return Err(format!("Invalid argument '{}'", other).into());
            }
        }

        Ok(())
    }
}

#[cfg(test)]
+237 −1
Original line number Diff line number Diff line
@@ -4,7 +4,10 @@ use bt_topshim::btif::{
    BtBondState, BtConnectionState, BtDeviceType, BtDiscMode, BtPropertyType, BtSspVariant,
    BtStatus, BtTransport, BtVendorProductInfo, Uuid, Uuid128Bit,
};
use bt_topshim::profiles::a2dp::{A2dpCodecConfig, PresentationPosition};
use bt_topshim::profiles::avrcp::PlayerMetadata;
use bt_topshim::profiles::gatt::{AdvertisingStatus, GattStatus, LePhy};
use bt_topshim::profiles::hfp::HfpCodecCapability;
use bt_topshim::profiles::hid_host::BthhReportType;
use bt_topshim::profiles::sdp::{
    BtSdpDipRecord, BtSdpHeaderOverlay, BtSdpMasRecord, BtSdpMnsRecord, BtSdpMpsRecord,
@@ -29,7 +32,9 @@ use btstack::bluetooth_gatt::{
    IBluetoothGattServerCallback, IScannerCallback, ScanFilter, ScanFilterCondition,
    ScanFilterPattern, ScanResult, ScanSettings, ScanType,
};
use btstack::bluetooth_media::IBluetoothTelephony;
use btstack::bluetooth_media::{
    BluetoothAudioDevice, IBluetoothMedia, IBluetoothMediaCallback, IBluetoothTelephony,
};
use btstack::bluetooth_qa::IBluetoothQA;
use btstack::socket_manager::{
    BluetoothServerSocket, BluetoothSocket, CallbackId, IBluetoothSocketManager,
@@ -397,6 +402,29 @@ struct ScanFilterPatternDBus {
    content: Vec<u8>,
}

#[dbus_propmap(A2dpCodecConfig)]
pub struct A2dpCodecConfigDBus {
    codec_type: i32,
    codec_priority: i32,
    sample_rate: i32,
    bits_per_sample: i32,
    channel_mode: i32,
    codec_specific_1: i64,
    codec_specific_2: i64,
    codec_specific_3: i64,
    codec_specific_4: i64,
}

impl_dbus_arg_from_into!(HfpCodecCapability, i32);
#[dbus_propmap(BluetoothAudioDevice)]
pub struct BluetoothAudioDeviceDBus {
    address: String,
    name: String,
    a2dp_caps: Vec<A2dpCodecConfig>,
    hfp_cap: HfpCodecCapability,
    absolute_volume: bool,
}

// Manually converts enum variant from/into D-Bus.
//
// The ScanFilterCondition enum variant is represented as a D-Bus dictionary with one and only one
@@ -502,6 +530,22 @@ struct ScanResultDBus {
    adv_data: Vec<u8>,
}

#[dbus_propmap(PresentationPosition)]
struct PresentationPositionDBus {
    remote_delay_report_ns: u64,
    total_bytes_read: u64,
    data_position_sec: i64,
    data_position_nsec: i32,
}

#[dbus_propmap(PlayerMetadata)]
struct PlayerMetadataDBus {
    title: String,
    artist: String,
    album: String,
    length_us: i64,
}

struct IBluetoothCallbackDBus {}

impl RPCProxy for IBluetoothCallbackDBus {}
@@ -2378,3 +2422,195 @@ impl IBluetoothQACallback for IBluetoothQACallbackDBus {
        dbus_generated!()
    }
}

pub(crate) struct BluetoothMediaDBusRPC {
    client_proxy: ClientDBusProxy,
}

pub(crate) struct BluetoothMediaDBus {
    client_proxy: ClientDBusProxy,
    pub rpc: BluetoothMediaDBusRPC,
}

impl BluetoothMediaDBus {
    fn make_client_proxy(conn: Arc<SyncConnection>, index: i32) -> ClientDBusProxy {
        ClientDBusProxy::new(
            conn.clone(),
            String::from("org.chromium.bluetooth"),
            make_object_path(index, "media"),
            String::from("org.chromium.bluetooth.BluetoothMedia"),
        )
    }

    pub(crate) fn new(conn: Arc<SyncConnection>, index: i32) -> BluetoothMediaDBus {
        BluetoothMediaDBus {
            client_proxy: Self::make_client_proxy(conn.clone(), index),
            rpc: BluetoothMediaDBusRPC {
                client_proxy: Self::make_client_proxy(conn.clone(), index),
            },
        }
    }
}

#[generate_dbus_interface_client(BluetoothMediaDBusRPC)]
impl IBluetoothMedia for BluetoothMediaDBus {
    #[dbus_method("RegisterCallback")]
    fn register_callback(&mut self, callback: Box<dyn IBluetoothMediaCallback + Send>) -> bool {
        dbus_generated!()
    }

    #[dbus_method("Initialize")]
    fn initialize(&mut self) -> bool {
        dbus_generated!()
    }

    #[dbus_method("Cleanup")]
    fn cleanup(&mut self) -> bool {
        dbus_generated!()
    }

    #[dbus_method("Connect")]
    fn connect(&mut self, address: String) {
        dbus_generated!()
    }

    #[dbus_method("Disconnect")]
    fn disconnect(&mut self, address: String) {
        dbus_generated!()
    }

    #[dbus_method("SetActiveDevice")]
    fn set_active_device(&mut self, address: String) {
        dbus_generated!()
    }

    #[dbus_method("ResetActiveDevice")]
    fn reset_active_device(&mut self) {
        dbus_generated!()
    }

    #[dbus_method("SetHfpActiveDevice")]
    fn set_hfp_active_device(&mut self, address: String) {
        dbus_generated!()
    }

    #[dbus_method("SetAudioConfig")]
    fn set_audio_config(
        &mut self,
        sample_rate: i32,
        bits_per_sample: i32,
        channel_mode: i32,
    ) -> bool {
        dbus_generated!()
    }

    #[dbus_method("SetVolume")]
    fn set_volume(&mut self, volume: u8) {
        dbus_generated!()
    }

    #[dbus_method("SetHfpVolume")]
    fn set_hfp_volume(&mut self, volume: u8, address: String) {
        dbus_generated!()
    }

    #[dbus_method("StartAudioRequest")]
    fn start_audio_request(&mut self) -> bool {
        dbus_generated!()
    }

    #[dbus_method("GetA2dpAudioStarted")]
    fn get_a2dp_audio_started(&mut self, address: String) -> bool {
        dbus_generated!()
    }

    #[dbus_method("StopAudioRequest")]
    fn stop_audio_request(&mut self) {
        dbus_generated!()
    }

    #[dbus_method("StartScoCall")]
    fn start_sco_call(&mut self, address: String, sco_offload: bool, force_cvsd: bool) -> bool {
        dbus_generated!()
    }

    #[dbus_method("GetHfpAudioFinalCodecs")]
    fn get_hfp_audio_final_codecs(&mut self, address: String) -> u8 {
        dbus_generated!()
    }

    #[dbus_method("StopScoCall")]
    fn stop_sco_call(&mut self, address: String) {
        dbus_generated!()
    }

    #[dbus_method("GetPresentationPosition")]
    fn get_presentation_position(&mut self) -> PresentationPosition {
        dbus_generated!()
    }

    // Temporary AVRCP-related meida DBUS APIs. The following APIs intercept between Chrome CRAS
    // and cras_server as an expedited solution for AVRCP implementation. The APIs are subject to
    // change when retiring Chrome CRAS.
    #[dbus_method("SetPlayerPlaybackStatus")]
    fn set_player_playback_status(&mut self, status: String) {
        dbus_generated!()
    }

    #[dbus_method("SetPlayerPosition")]
    fn set_player_position(&mut self, position_us: i64) {
        dbus_generated!()
    }

    #[dbus_method("SetPlayerMetadata")]
    fn set_player_metadata(&mut self, metadata: PlayerMetadata) {
        dbus_generated!()
    }

    #[dbus_method("TriggerDebugDump")]
    fn trigger_debug_dump(&mut self) {
        dbus_generated!()
    }
}

struct IBluetoothMediaCallbackDBus {}

impl RPCProxy for IBluetoothMediaCallbackDBus {}

#[generate_dbus_exporter(
    export_bluetooth_media_callback_dbus_intf,
    "org.chromium.bluetooth.BluetoothMediaCallback"
)]
impl IBluetoothMediaCallback for IBluetoothMediaCallbackDBus {
    #[dbus_method("OnBluetoothAudioDeviceAdded")]
    fn on_bluetooth_audio_device_added(&mut self, device: BluetoothAudioDevice) {}

    #[dbus_method("OnBluetoothAudioDeviceRemoved")]
    fn on_bluetooth_audio_device_removed(&mut self, addr: String) {}

    #[dbus_method("OnAbsoluteVolumeSupportedChanged")]
    fn on_absolute_volume_supported_changed(&mut self, supported: bool) {}

    #[dbus_method("OnAbsoluteVolumeChanged")]
    fn on_absolute_volume_changed(&mut self, volume: u8) {}

    #[dbus_method("OnHfpVolumeChanged")]
    fn on_hfp_volume_changed(&mut self, volume: u8, addr: String) {}

    #[dbus_method("OnHfpAudioDisconnected")]
    fn on_hfp_audio_disconnected(&mut self, addr: String) {}

    #[dbus_method("OnHfpDebugDump")]
    fn on_hfp_debug_dump(
        &mut self,
        active: bool,
        wbs: bool,
        total_num_decoded_frames: i32,
        pkt_loss_ratio: f64,
        begin_ts: u64,
        end_ts: u64,
        pkt_status_in_hex: String,
        pkt_status_in_binary: String,
    ) {
    }
}
+27 −3
Original line number Diff line number Diff line
@@ -13,12 +13,13 @@ use crate::bt_adv::AdvSet;
use crate::bt_gatt::GattClientContext;
use crate::callbacks::{
    AdminCallback, AdvertisingSetCallback, BtCallback, BtConnectionCallback, BtManagerCallback,
    BtSocketManagerCallback, QACallback, ScannerCallback, SuspendCallback,
    BtSocketManagerCallback, MediaCallback, QACallback, ScannerCallback, SuspendCallback,
};
use crate::command_handler::{CommandHandler, SocketSchedule};
use crate::dbus_iface::{
    BluetoothAdminDBus, BluetoothDBus, BluetoothGattDBus, BluetoothManagerDBus, BluetoothQADBus,
    BluetoothQALegacyDBus, BluetoothSocketManagerDBus, BluetoothTelephonyDBus, SuspendDBus,
    BluetoothAdminDBus, BluetoothDBus, BluetoothGattDBus, BluetoothManagerDBus, BluetoothMediaDBus,
    BluetoothQADBus, BluetoothQALegacyDBus, BluetoothSocketManagerDBus, BluetoothTelephonyDBus,
    SuspendDBus,
};
use crate::editor::AsyncEditor;
use bt_topshim::topstack;
@@ -95,6 +96,9 @@ pub(crate) struct ClientContext {
    /// Proxy for Telephony interface.
    pub(crate) telephony_dbus: Option<BluetoothTelephonyDBus>,

    /// Proxy for Media interface.
    pub(crate) media_dbus: Option<BluetoothMediaDBus>,

    /// Channel to send actions to take in the foreground
    fg: mpsc::Sender<ForegroundActions>,

@@ -168,6 +172,7 @@ impl ClientContext {
            suspend_dbus: None,
            socket_manager_dbus: None,
            telephony_dbus: None,
            media_dbus: None,
            fg: tx,
            dbus_connection,
            dbus_crossroads,
@@ -228,6 +233,8 @@ impl ClientContext {

        self.telephony_dbus = Some(BluetoothTelephonyDBus::new(conn.clone(), idx));

        self.media_dbus = Some(BluetoothMediaDBus::new(conn.clone(), idx));

        // Trigger callback registration in the foreground
        let fg = self.fg.clone();
        tokio::spawn(async move {
@@ -470,6 +477,8 @@ async fn start_interactive_shell(
                    format!("/org/chromium/bluetooth/client/{}/socket_manager_callback", adapter);
                let qa_cb_objpath: String =
                    format!("/org/chromium/bluetooth/client/{}/qa_manager_callback", adapter);
                let media_cb_objpath: String =
                    format!("/org/chromium/bluetooth/client/{}/bluetooth_media_callback", adapter);

                let dbus_connection = context.lock().unwrap().dbus_connection.clone();
                let dbus_crossroads = context.lock().unwrap().dbus_crossroads.clone();
@@ -601,6 +610,21 @@ async fn start_interactive_shell(
                    ),
                ));

                context
                    .lock()
                    .unwrap()
                    .media_dbus
                    .as_mut()
                    .unwrap()
                    .rpc
                    .register_callback(Box::new(MediaCallback::new(
                        media_cb_objpath,
                        dbus_connection.clone(),
                        dbus_crossroads.clone(),
                    )))
                    .await
                    .expect("D-Bus error on IBluetoothMedia::RegisterCallback");

                context.lock().unwrap().adapter_ready = true;
                let adapter_address = context.lock().unwrap().update_adapter_address();
                context.lock().unwrap().update_bonded_devices();
Loading