rust/bt-broadcast-assistant: Add debug logging

Add debug! and trace! logging throughout Broadcast Assistant,
delegator Peer wrapper, and BASS client to improve visibility into
broadcast source discovery, delegator connection, and BASS operations:
 - In assistant: log starting event stream, delegator scanning,
   delegator connection, BASS service discovery, and force-discovery
   calls.
 - In peer: log BASS client wrapper initialization, adding broadcast
   sources, updating synchronization, removing sources, sending
   broadcast codes, informing remote scan state, and processing BASS
   notification events.
 - In bt-bass: log BASS client characteristic discovery, BASS
   notifications on characteristic handles, and updated receive state.
 - Add unit tests in debug.rs verifying AssistantCmd::Verbose verbosity
   updates and scoped command execution with -v and -vv flags.

Test: cargo test -p bt-broadcast-assistant -p bt-bass
      cargo test --workspace
Change-Id: I9c4e875bd172feec7aaf2861c934e05d6a6a6964
Reviewed-on: https://bluetooth-review.googlesource.com/c/bluetooth/+/4122
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs
index b74c268..e8db074 100644
--- a/rust/bt-bass/src/client.rs
+++ b/rust/bt-bass/src/client.rs
@@ -132,6 +132,12 @@
         }
         let bascp_handle = *bascp[0].handle();
         let brs_chars = Self::discover_brs_characteristics(&gatt_client).await?;
+        log::debug!(
+            "BASS client initialized: control point handle={:?}, {} BRS characteristic(s)",
+            bascp_handle,
+            brs_chars.len()
+        );
+        log::trace!("Discovered BRS characteristics: {:?}", brs_chars);
         let mut c = Self {
             gatt_client,
             audio_scan_control_point: bascp_handle,
@@ -390,6 +396,17 @@
         brs
     }
 
+    /// Returns the handle of the Broadcast Audio Scan Control Point
+    /// characteristic.
+    pub fn audio_scan_control_point(&self) -> Handle {
+        self.audio_scan_control_point
+    }
+
+    /// Looks up the Source ID assigned to a Broadcast ID if known.
+    pub fn source_id(&self, broadcast_id: &BroadcastId) -> Option<SourceId> {
+        self.broadcast_sources.lock().source_id(broadcast_id)
+    }
+
     #[cfg(any(test, feature = "test-utils"))]
     pub fn insert_broadcast_receive_state(&mut self, handle: Handle, brs: BroadcastReceiveState) {
         self.broadcast_sources.lock().update_state(handle, brs);
diff --git a/rust/bt-bass/src/client/event.rs b/rust/bt-bass/src/client/event.rs
index 3586568..e5b194a 100644
--- a/rust/bt-bass/src/client/event.rs
+++ b/rust/bt-bass/src/client/event.rs
@@ -150,10 +150,26 @@
             let char_handle = notification.handle;
             let (Ok(new_state), _) = BroadcastReceiveState::decode(notification.value.as_slice())
             else {
+                log::trace!("Failed to decode BASS notification on handle {:?}", char_handle);
                 self.event_queue.push_back(Ok(Event::UnknownPacket));
                 continue;
             };
 
+            log::debug!(
+                "Processing BASS notification for characteristic handle {:?}: {:?}",
+                char_handle,
+                new_state
+            );
+            if let BroadcastReceiveState::NonEmpty(ref state) = new_state {
+                let bad_code = matches!(state.big_encryption(), EncryptionStatus::BadCode(_));
+                log::trace!(
+                    "Updated BroadcastReceiveState: PA sync state={:?}, BIG encryption={:?}, bad code flag={}",
+                    state.pa_sync_state(),
+                    state.big_encryption(),
+                    bad_code
+                );
+            }
+
             let maybe_prev_state = {
                 let mut lock = self.broadcast_sources.lock();
                 lock.update_state(char_handle, new_state.clone())
diff --git a/rust/bt-broadcast-assistant/src/assistant.rs b/rust/bt-broadcast-assistant/src/assistant.rs
index 98883db..9c56a44 100644
--- a/rust/bt-broadcast-assistant/src/assistant.rs
+++ b/rust/bt-broadcast-assistant/src/assistant.rs
@@ -137,6 +137,7 @@
         if self.is_started() {
             return Err(Error::AlreadyStarted);
         }
+        log::debug!("Starting Broadcast Assistant event stream and scanning for broadcast sources");
         let scan_result_stream = self.central.scan(&Self::scan_filters());
         self.broadcast_source_scan_started.store(true, Ordering::Relaxed);
 
@@ -152,6 +153,7 @@
                 );
             })
             .ok();
+        log::trace!("Periodic advertising support available: {}", periodic_advertising.is_some());
 
         Ok(EventStream::<T>::new(
             scan_result_stream,
@@ -172,6 +174,10 @@
                 "Cannot scan for scan delegators while scanning for broadcast sources"
             )));
         }
+        log::debug!(
+            "Scanning for scan delegators with filter for BASS service UUID {:?}",
+            BROADCAST_AUDIO_SCAN_SERVICE
+        );
         // Scan for service data with Broadcast Audio Scan Service UUID to look
         // for Broadcast Sink collocated with the Scan Delegator (see BAP spec
         // v1.0.1 Section 3.9.2 for details).
@@ -182,22 +188,33 @@
     where
         <T as bt_gatt::GattTypes>::NotificationStream: std::marker::Send,
     {
+        log::debug!("Connecting to scan delegator peer {:?}", peer_id);
         let client = self.central.connect(peer_id).await?;
+        log::trace!("Discovering BASS service on peer {:?}", peer_id);
         let service_handles = client.find_service(BROADCAST_AUDIO_SCAN_SERVICE).await?;
+        log::trace!(
+            "Found {} service handle(s) matching BASS UUID for peer {:?}",
+            service_handles.len(),
+            peer_id
+        );
 
         for handle in service_handles {
             if handle.uuid() != BROADCAST_AUDIO_SCAN_SERVICE || !handle.is_primary() {
                 continue;
             }
+            log::trace!("Connecting to primary BASS service handle on peer {:?}", peer_id);
             let service = handle.connect().await?;
+            log::trace!("Creating BroadcastAudioScanServiceClient for peer {:?}", peer_id);
             let bass = BroadcastAudioScanServiceClient::<T>::create(service)
                 .await
                 .map_err(|e| Error::BassClient(peer_id, e))?;
 
+            log::debug!("Creating Peer wrapper for peer {:?}", peer_id);
             let connected_peer =
                 Peer::<T>::new(peer_id, client, bass, self.broadcast_sources.clone());
             return Ok(connected_peer);
         }
+        log::debug!("Failed to find primary BASS service on peer {:?}", peer_id);
         Err(Error::ConnectionFailure(peer_id, BROADCAST_AUDIO_SCAN_SERVICE))
     }
 
@@ -210,6 +227,13 @@
         address_type: bt_common::core::AddressType,
         advertising_sid: bt_common::core::AdvertisingSetId,
     ) -> Result<BroadcastSource, Error> {
+        log::debug!(
+            "Force discovering broadcast source for peer {:?}, sid {:?}, address {:?} ({:?})",
+            peer_id,
+            advertising_sid,
+            address,
+            address_type,
+        );
         let broadcast_source = BroadcastSource {
             address: Some(address),
             address_type: Some(address_type),
@@ -219,10 +243,11 @@
             broadcast_name: None,
         };
 
-        Ok(self
+        let (merged, changed) = self
             .broadcast_sources
-            .merge_broadcast_source_data(&(peer_id, advertising_sid), &broadcast_source)
-            .0)
+            .merge_broadcast_source_data(&(peer_id, advertising_sid), &broadcast_source);
+        log::trace!("Force discovered source (changed={}): {:?}", changed, merged);
+        Ok(merged)
     }
 
     // Manually adds broadcast source information for debugging purposes.
@@ -233,11 +258,18 @@
         advertising_sid: bt_common::core::AdvertisingSetId,
         big_metadata: Vec<Vec<bt_common::generic_audio::metadata_ltv::Metadata>>,
     ) -> Result<BroadcastSource, Error> {
+        log::debug!(
+            "Force discovering broadcast source metadata for peer {:?}, sid {:?}, {} BIG(s)",
+            peer_id,
+            advertising_sid,
+            big_metadata.len(),
+        );
         use bt_bap::types::{BroadcastAudioSourceEndpoint, BroadcastIsochronousGroup};
         use bt_common::core::CodecId;
 
         let mut big = Vec::new();
         for metadata in big_metadata {
+            log::trace!("BIG metadata: {:?}", metadata);
             let group = BroadcastIsochronousGroup {
                 codec_id: CodecId::Assigned(bt_common::core::CodingFormat::ALawLog), // mock.
                 codec_specific_configs: vec![],
@@ -258,10 +290,11 @@
             broadcast_name: None,
         };
 
-        Ok(self
+        let (merged, changed) = self
             .broadcast_sources
-            .merge_broadcast_source_data(&(peer_id, advertising_sid), &broadcast_source)
-            .0)
+            .merge_broadcast_source_data(&(peer_id, advertising_sid), &broadcast_source);
+        log::trace!("Force discovered metadata (changed={}): {:?}", changed, merged);
+        Ok(merged)
     }
 
     // Gets the broadcast sources currently known by the broadcast
diff --git a/rust/bt-broadcast-assistant/src/assistant/peer.rs b/rust/bt-broadcast-assistant/src/assistant/peer.rs
index 8587e38..b2bd347 100644
--- a/rust/bt-broadcast-assistant/src/assistant/peer.rs
+++ b/rust/bt-broadcast-assistant/src/assistant/peer.rs
@@ -4,7 +4,7 @@
 
 use bt_gatt::pii::GetPeerAddr;
 use futures::stream::FusedStream;
-use futures::Stream;
+use futures::{Stream, StreamExt};
 use std::sync::Arc;
 use thiserror::Error;
 
@@ -14,6 +14,7 @@
 use bt_bass::client::BroadcastAudioScanServiceClient;
 #[cfg(any(test, feature = "debug"))]
 use bt_bass::types::BroadcastReceiveState;
+use bt_bass::types::EncryptionStatus;
 use bt_bass::types::{BisSync, PaSync};
 use bt_common::core::{AdvertisingSetId, PeriodicAdvertisingInterval};
 use bt_common::packet_encoding::Error as PacketError;
@@ -65,6 +66,21 @@
         bass: BroadcastAudioScanServiceClient<T>,
         broadcast_sources: Arc<DiscoveredBroadcastSources>,
     ) -> Self {
+        if log::log_enabled!(log::Level::Debug) {
+            let brs_count = bass.known_broadcast_sources().len();
+            let bascp = bass.audio_scan_control_point();
+            log::debug!(
+                "Initializing BASS client for peer {:?}: discovered {} Broadcast Receive State characteristic(s), Audio Scan Control Point handle {:?}",
+                peer_id,
+                brs_count,
+                bascp,
+            );
+            log::trace!(
+                "Peer {:?} initial Broadcast Receive States: {:?}",
+                peer_id,
+                bass.known_broadcast_sources()
+            );
+        }
         Peer { peer_id, _client: client, bass, broadcast_sources }
     }
 
@@ -77,7 +93,36 @@
     pub fn take_event_stream(
         &mut self,
     ) -> Result<impl Stream<Item = Result<BassEvent, BassClientError>> + FusedStream, Error> {
-        self.bass.take_event_stream().ok_or(Error::UnavailableBassEventStream)
+        let peer_id = self.peer_id;
+        log::debug!("Taking BASS event stream for peer {:?}", peer_id);
+        let stream = self.bass.take_event_stream().ok_or(Error::UnavailableBassEventStream)?;
+        Ok(stream.inspect(move |res| match res {
+            Ok(BassEvent::AddedBroadcastSource(bid, pa_sync_state, enc)) => {
+                let bad_code = matches!(enc, EncryptionStatus::BadCode(_));
+                log::trace!(
+                    "BASS notification for peer {:?}: BroadcastId {:?}, PA sync state {:?}, BIG encryption {:?}, bad code flag: {}",
+                    peer_id,
+                    bid,
+                    pa_sync_state,
+                    enc,
+                    bad_code
+                );
+            }
+            Ok(BassEvent::InvalidBroadcastCode(bid, code)) => {
+                log::trace!(
+                    "BASS notification for peer {:?}: BroadcastId {:?}, invalid broadcast code (bad code flag set): {:?}",
+                    peer_id,
+                    bid,
+                    code
+                );
+            }
+            Ok(event) => {
+                log::trace!("BASS notification for peer {:?}: {:?}", peer_id, event);
+            }
+            Err(e) => {
+                log::debug!("BASS notification error for peer {:?}: {:?}", peer_id, e);
+            }
+        }))
     }
 
     /// Send broadcast code for a particular broadcast.
@@ -86,6 +131,16 @@
         broadcast_id: BroadcastId,
         broadcast_code: BroadcastCode,
     ) -> Result<(), Error> {
+        if log::log_enabled!(log::Level::Debug) {
+            let source_id = self.bass.source_id(&broadcast_id);
+            log::debug!(
+                "Sending broadcast code for peer {:?}, broadcast_id {:?} (source_id: {:?})",
+                self.peer_id,
+                broadcast_id,
+                source_id,
+            );
+            log::trace!("Broadcast code for peer {:?}: {:?}", self.peer_id, broadcast_code);
+        }
         self.bass.set_broadcast_code(broadcast_id, broadcast_code).await.map_err(Into::into)
     }
 
@@ -108,6 +163,14 @@
         pa_sync: PaSync,
         bis_sync: HashMap<u8, BisSync>,
     ) -> Result<(), Error> {
+        log::debug!(
+            "Adding broadcast source from peer {:?}, sid {:?} to delegator {:?} (pa_sync: {:?}, bis_sync: {:?})",
+            source_peer_id,
+            advertising_sid,
+            self.peer_id,
+            pa_sync,
+            bis_sync,
+        );
         let mut broadcast_source = self
             .broadcast_sources
             .get_by_key(source_peer_id, advertising_sid)
@@ -120,20 +183,45 @@
         broadcast_source.with_address(broadcast_addr).with_address_type(broadcast_addr_type);
 
         if !broadcast_source.is_ready_to_add() {
+            log::debug!(
+                "Broadcast source from peer {:?}, sid {:?} is not ready to add: {:?}",
+                source_peer_id,
+                advertising_sid,
+                broadcast_source
+            );
             return Err(Error::NotEnoughInfo(source_peer_id));
         }
 
+        let broadcast_id = broadcast_source.broadcast_id.unwrap();
+        let address_type = broadcast_source.address_type.unwrap();
+        let address = broadcast_source.address.unwrap();
+        let pa_interval = broadcast_source
+            .periodic_advertising_interval
+            .unwrap_or(PeriodicAdvertisingInterval::unknown());
+        let subgroups =
+            broadcast_source.endpoint_to_big_subgroups(bis_sync).map_err(Error::PacketError)?;
+
+        log::trace!(
+            "add_broadcast_source to delegator {:?}: source address={:?} ({:?}), sid={:?}, broadcast_id={:?}, pa_sync={:?}, pa_interval={:?}, subgroups (with metadata and BIS sync)={:?}",
+            self.peer_id,
+            address,
+            address_type,
+            advertising_sid,
+            broadcast_id,
+            pa_sync,
+            pa_interval,
+            subgroups,
+        );
+
         self.bass
             .add_broadcast_source(
-                broadcast_source.broadcast_id.unwrap(),
-                broadcast_source.address_type.unwrap(),
-                broadcast_source.address.unwrap(),
+                broadcast_id,
+                address_type,
+                address,
                 advertising_sid,
                 pa_sync,
-                broadcast_source
-                    .periodic_advertising_interval
-                    .unwrap_or(PeriodicAdvertisingInterval::unknown()),
-                broadcast_source.endpoint_to_big_subgroups(bis_sync).map_err(Error::PacketError)?,
+                pa_interval,
+                subgroups,
             )
             .await
             .map_err(Into::into)
@@ -154,12 +242,30 @@
         pa_sync: PaSync,
         bis_sync: HashMap<u8, BisSync>,
     ) -> Result<(), Error> {
+        if log::log_enabled!(log::Level::Debug) {
+            let source_id = self.bass.source_id(&broadcast_id);
+            log::debug!(
+                "Updating broadcast source sync for peer {:?}, broadcast_id {:?} (source_id: {:?}): pa_sync={:?}, bis_sync={:?}",
+                self.peer_id,
+                broadcast_id,
+                source_id,
+                pa_sync,
+                bis_sync,
+            );
+        }
         let pa_interval = self
             .broadcast_sources
             .get_by_broadcast_id(&broadcast_id)
             .map(|bs| bs.periodic_advertising_interval)
             .unwrap_or(None);
 
+        log::trace!(
+            "update_broadcast_source_sync for peer {:?}, broadcast_id {:?}: pa_interval={:?}",
+            self.peer_id,
+            broadcast_id,
+            pa_interval,
+        );
+
         self.bass
             .modify_broadcast_source(broadcast_id, pa_sync, pa_interval, bis_sync, None)
             .await
@@ -173,18 +279,29 @@
     /// * `broadcast_id` - broadcast id of the braodcast source that's to be
     ///   removed from the scan delegator
     pub async fn remove_broadcast_source(&self, broadcast_id: BroadcastId) -> Result<(), Error> {
+        if log::log_enabled!(log::Level::Debug) {
+            let source_id = self.bass.source_id(&broadcast_id);
+            log::debug!(
+                "Removing broadcast source for peer {:?}, broadcast_id {:?} (source_id: {:?})",
+                self.peer_id,
+                broadcast_id,
+                source_id,
+            );
+        }
         self.bass.remove_broadcast_source(broadcast_id).await.map_err(Into::into)
     }
 
     /// Sends a command to inform the scan delegator peer that we have
     /// started scanning for broadcast sources on behalf of it.
     pub async fn inform_remote_scan_started(&self) -> Result<(), Error> {
+        log::debug!("Informing scan delegator peer {:?} that remote scan started", self.peer_id);
         self.bass.remote_scan_started().await.map_err(Into::into)
     }
 
     /// Sends a command to inform the scan delegator peer that we have
     /// stopped scanning for broadcast sources on behalf of it.
     pub async fn inform_remote_scan_stopped(&self) -> Result<(), Error> {
+        log::debug!("Informing scan delegator peer {:?} that remote scan stopped", self.peer_id);
         self.bass.remote_scan_stopped().await.map_err(Into::into)
     }
 
diff --git a/rust/bt-broadcast-assistant/src/debug.rs b/rust/bt-broadcast-assistant/src/debug.rs
index 6afa9eb..bacd4eb 100644
--- a/rust/bt-broadcast-assistant/src/debug.rs
+++ b/rust/bt-broadcast-assistant/src/debug.rs
@@ -531,6 +531,10 @@
 mod tests {
     use super::*;
 
+    use bt_common::debug_command::CliCommand;
+    use bt_gatt::test_utils::{FakeCentral, FakeGetPeerAddr, FakeTypes};
+    use log::LevelFilter;
+
     #[test]
     fn test_parse_peer_id() {
         // In hex string.
@@ -627,4 +631,87 @@
         let code = "";
         assert!(passcode_to_broadcast_code(code).is_err());
     }
+
+    #[test]
+    fn verbose_command_updates_and_displays_verbosity() {
+        let debug: AssistantDebug<FakeTypes, _> =
+            AssistantDebug::new(FakeCentral::new(), FakeGetPeerAddr);
+
+        log::set_max_level(LevelFilter::Info);
+
+        // Verbose with no args displays current verbosity without changing it
+        let res = futures::executor::block_on(debug.run(CliCommand::Verbose, vec![]));
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Info);
+
+        // Update verbosity to Debug
+        let res =
+            futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["debug".to_string()]));
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Debug);
+
+        // Update verbosity to Trace
+        let res =
+            futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["trace".to_string()]));
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Trace);
+
+        // Update verbosity to Off
+        let res =
+            futures::executor::block_on(debug.run(CliCommand::Verbose, vec!["off".to_string()]));
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Off);
+
+        // Invalid level does not change verbosity
+        let res = futures::executor::block_on(
+            debug.run(CliCommand::Verbose, vec!["invalid_level".to_string()]),
+        );
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Off);
+
+        // Reset back to Info
+        log::set_max_level(LevelFilter::Info);
+    }
+
+    #[test]
+    fn run_commands_with_verbosity_flags() {
+        let debug: AssistantDebug<FakeTypes, _> =
+            AssistantDebug::new(FakeCentral::new(), FakeGetPeerAddr);
+
+        log::set_max_level(LevelFilter::Info);
+
+        // Executing Info with -v flag:
+        // 1. debug.run() strips the flag before run_command.
+        // 2. Temporarily elevates max log level during execution.
+        // 3. Executes successfully.
+        let res =
+            futures::executor::block_on(debug.run(AssistantCmd::Info, vec!["-v".to_string()]));
+        assert!(res.is_ok());
+
+        // Persistent verbosity remains Info after scoped execution completes
+        assert_eq!(log::max_level(), LevelFilter::Info);
+
+        // Executing Info with -vv flag elevates to Trace
+        let res =
+            futures::executor::block_on(debug.run(AssistantCmd::Info, vec!["-vv".to_string()]));
+        assert!(res.is_ok());
+        assert_eq!(log::max_level(), LevelFilter::Info);
+
+        // Executing an argument-sensitive command (4 positional arguments + -v)
+        // verifies that -v is cleanly stripped and doesn't cause positional
+        // argument parsing to fail with an incorrect argument count.
+        let res = futures::executor::block_on(debug.run(
+            AssistantCmd::ForceDiscoverBroadcastSource,
+            vec![
+                "1".to_string(),
+                "00:11:22:33:44:55".to_string(),
+                "Random".to_string(),
+                "1".to_string(),
+                "-v".to_string(),
+            ],
+        ));
+        assert!(res.is_ok());
+        assert_eq!(debug.assistant.known_broadcast_sources().len(), 1);
+        assert_eq!(log::max_level(), LevelFilter::Info);
+    }
 }