diff --git a/floss/hcidoc/Cargo.toml b/floss/hcidoc/Cargo.toml
index ce6437943b2616c096fc0868e7d592972ea49006..d7f279760d0821ae4ff4e6e5e69b4991932536e3 100644
--- a/floss/hcidoc/Cargo.toml
+++ b/floss/hcidoc/Cargo.toml
@@ -10,3 +10,4 @@ clap = "4.0"
 chrono = "0.4"
 num-derive = "0.3"
 num-traits = "0.2"
+lazy_static = "1.0"
diff --git a/floss/hcidoc/src/groups/connections.rs b/floss/hcidoc/src/groups/connections.rs
index 509eb7f7388a13e3fe2b1af70742ddbd720e3b95..765f7eed9286c107fa6c72e132b21075a4f806a0 100644
--- a/floss/hcidoc/src/groups/connections.rs
+++ b/floss/hcidoc/src/groups/connections.rs
@@ -801,6 +801,9 @@ impl Rule for OddDisconnectionsRule {
 
             // We don't do anything with RX packets yet.
             PacketChild::AclRx(_) => (),
+
+            // End packet.inner match
+            _ => (),
         }
     }
 
diff --git a/floss/hcidoc/src/groups/controllers.rs b/floss/hcidoc/src/groups/controllers.rs
index 3682814796fecbb375b30310c0530944a50c2d33..46927607658a764637689af94b15edc963c962b4 100644
--- a/floss/hcidoc/src/groups/controllers.rs
+++ b/floss/hcidoc/src/groups/controllers.rs
@@ -1,35 +1,60 @@
 ///! Rule group for tracking controller related issues.
 use chrono::NaiveDateTime;
+use lazy_static::lazy_static;
+use std::collections::HashSet;
 use std::convert::Into;
 use std::io::Write;
 
 use crate::engine::{Rule, RuleGroup, Signal};
-use crate::parser::{Packet, PacketChild};
-use bt_packets::hci::EventChild;
+use crate::parser::{NewIndex, Packet, PacketChild};
+use bt_packets::hci::{CommandCompleteChild, ErrorCode, EventChild, LocalVersionInformation};
 
 enum ControllerSignal {
-    HardwareError, // Controller reports HCI event: Hardware Error
+    HardwareError,            // Controller reports HCI event: Hardware Error
+    LikelyExternalController, // Controller is not in the known list. Likely to be an external controller.
 }
 
 impl Into<&'static str> for ControllerSignal {
     fn into(self) -> &'static str {
         match self {
             ControllerSignal::HardwareError => "HardwareError",
+            ControllerSignal::LikelyExternalController => "LikelyExternalController",
         }
     }
 }
 
+lazy_static! {
+    static ref KNOWN_CONTROLLER_NAMES: [String; 6] = [
+        String::from("Bluemoon Universal Bluetooth Host Controller"),    // AC7625
+        String::from("MTK MT7961 #1"),    // MT7921LE/MT7921LS
+        String::from("MTK MT7922 #1"),    // MT7922
+        String::from("RTK_BT_5.0"),       // RTL8822CE
+        String::from("RT_BT"),            // RTL8852AE
+        String::from(""),                 // AC9260/AC9560/AX200/AX201/AX203/AX211/MVL8897/QCA6174A3/QCA6174A5/QC_WCN6856
+    ];
+}
+const KNOWN_CONTROLLER_MANUFACTURERS: [u16; 5] = [
+    2,  // Intel.
+    29, // Qualcomm
+    70, // MediaTek
+    72, // Marvell
+    93, // Realtek
+];
+
 struct ControllerRule {
     /// Pre-defined signals discovered in the logs.
     signals: Vec<Signal>,
 
     /// Interesting occurrences surfaced by this rule.
     reportable: Vec<(NaiveDateTime, String)>,
+
+    /// All detected open_index.
+    controllers: HashSet<String>,
 }
 
 impl ControllerRule {
     pub fn new() -> Self {
-        ControllerRule { signals: vec![], reportable: vec![] }
+        ControllerRule { signals: vec![], reportable: vec![], controllers: HashSet::new() }
     }
 
     pub fn report_hardware_error(&mut self, packet: &Packet) {
@@ -41,6 +66,48 @@ impl ControllerRule {
 
         self.reportable.push((packet.ts, format!("controller reported hardware error")));
     }
+
+    fn process_local_name(&mut self, local_name: &[u8; 248], packet: &Packet) {
+        let null_index = local_name.iter().position(|&b| b == 0).unwrap_or(local_name.len());
+        match String::from_utf8(local_name[..null_index].to_vec()) {
+            Ok(name) => {
+                if !KNOWN_CONTROLLER_NAMES.contains(&name) {
+                    self.signals.push(Signal {
+                        index: packet.index,
+                        ts: packet.ts,
+                        tag: ControllerSignal::LikelyExternalController.into(),
+                    })
+                }
+            }
+            Err(_) => self.signals.push(Signal {
+                index: packet.index,
+                ts: packet.ts,
+                tag: ControllerSignal::LikelyExternalController.into(),
+            }),
+        }
+    }
+
+    fn process_local_version(&mut self, version_info: &LocalVersionInformation, packet: &Packet) {
+        if !KNOWN_CONTROLLER_MANUFACTURERS.contains(&version_info.manufacturer_name) {
+            self.signals.push(Signal {
+                index: packet.index,
+                ts: packet.ts,
+                tag: ControllerSignal::LikelyExternalController.into(),
+            })
+        }
+    }
+
+    fn process_new_index(&mut self, new_index: &NewIndex, packet: &Packet) {
+        self.controllers.insert(new_index.get_addr_str());
+
+        if self.controllers.len() > 1 {
+            self.signals.push(Signal {
+                index: packet.index,
+                ts: packet.ts,
+                tag: ControllerSignal::LikelyExternalController.into(),
+            });
+        }
+    }
 }
 
 impl Rule for ControllerRule {
@@ -50,8 +117,28 @@ impl Rule for ControllerRule {
                 EventChild::HardwareError(_ev) => {
                     self.report_hardware_error(&packet);
                 }
+                EventChild::CommandComplete(ev) => match ev.specialize() {
+                    CommandCompleteChild::ReadLocalNameComplete(ev) => {
+                        if ev.get_status() != ErrorCode::Success {
+                            return;
+                        }
+
+                        self.process_local_name(ev.get_local_name(), &packet);
+                    }
+                    CommandCompleteChild::ReadLocalVersionInformationComplete(ev) => {
+                        if ev.get_status() != ErrorCode::Success {
+                            return;
+                        }
+
+                        self.process_local_version(ev.get_local_version_information(), &packet);
+                    }
+                    _ => {}
+                },
                 _ => {}
             },
+            PacketChild::NewIndex(ni) => {
+                self.process_new_index(ni, &packet);
+            }
             _ => {}
         }
     }
diff --git a/floss/hcidoc/src/groups/informational.rs b/floss/hcidoc/src/groups/informational.rs
index 9ae82c9f191eac1544d9e00c1cc8b401cb4b2b5b..31181ebf1c673853221aa309eee5faf3ae9099dd 100644
--- a/floss/hcidoc/src/groups/informational.rs
+++ b/floss/hcidoc/src/groups/informational.rs
@@ -930,7 +930,10 @@ impl Rule for InformationalRule {
                     // PacketChild::AclRx(rx).specialize()
                     _ => {}
                 }
-            } // packet.inner
+            }
+
+            // End packet.inner match
+            _ => (),
         }
     }
 
diff --git a/floss/hcidoc/src/parser.rs b/floss/hcidoc/src/parser.rs
index bec8df41c17c8e7ade2fe0bbb998730d319f5a8f..7d11b983189ff3f2fdfc8d0122d38272af0f07ff 100644
--- a/floss/hcidoc/src/parser.rs
+++ b/floss/hcidoc/src/parser.rs
@@ -287,6 +287,7 @@ pub enum PacketChild {
     HciEvent(Event),
     AclTx(Acl),
     AclRx(Acl),
+    NewIndex(NewIndex),
 }
 
 impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild {
@@ -314,6 +315,11 @@ impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild {
                 Err(e) => Err(format!("Couldn't parse acl rx: {:?}", e)),
             },
 
+            LinuxSnoopOpcodes::NewIndex => match NewIndex::parse(item.data.as_slice()) {
+                Ok(data) => Ok(PacketChild::NewIndex(data)),
+                Err(e) => Err(format!("Couldn't parse new index: {:?}", e)),
+            },
+
             // TODO(b/262928525) - Add packet handlers for more packet types.
             _ => Err(format!("Unhandled packet opcode: {:?}", item.opcode())),
         }
@@ -400,3 +406,44 @@ pub fn get_acl_content(acl: &Acl) -> AclContent {
         _ => AclContent::None,
     }
 }
+
+#[derive(Clone, Debug)]
+pub struct NewIndex {
+    _hci_type: u8,
+    _bus: u8,
+    bdaddr: [u8; 6],
+    _name: [u8; 8],
+}
+
+impl NewIndex {
+    fn parse(data: &[u8]) -> Result<NewIndex, std::string::String> {
+        if data.len() != std::mem::size_of::<NewIndex>() {
+            return Err(format!("Invalid size for New Index packet: {}", data.len()));
+        }
+
+        let rest = data;
+        let (hci_type, rest) = rest.split_at(std::mem::size_of::<u8>());
+        let (bus, rest) = rest.split_at(std::mem::size_of::<u8>());
+        let (bdaddr, rest) = rest.split_at(6 * std::mem::size_of::<u8>());
+        let (name, _rest) = rest.split_at(8 * std::mem::size_of::<u8>());
+
+        Ok(NewIndex {
+            _hci_type: hci_type[0],
+            _bus: bus[0],
+            bdaddr: bdaddr.try_into().unwrap(),
+            _name: name.try_into().unwrap(),
+        })
+    }
+
+    pub fn get_addr_str(&self) -> String {
+        String::from(format!(
+            "[{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}]",
+            self.bdaddr[0],
+            self.bdaddr[1],
+            self.bdaddr[2],
+            self.bdaddr[3],
+            self.bdaddr[4],
+            self.bdaddr[5]
+        ))
+    }
+}