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

Commit 323be368 authored by Rahul Arya's avatar Rahul Arya Committed by Gerrit Code Review
Browse files

Merge changes I956e035b,Idc3cdd78,I1e5591b0,I470d40e7

* changes:
  [GATT Server] Simplify API of IsolationManager
  [GATT Server] Expose IsolationManager from GATT server module
  [GATT Server] Simplify IsolationManager interface
  [GATT Server] Split up Arbiter logic and FFI
parents e2c4760a df312c3e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ rust_defaults {
        "libbt_common_only_init_flags",
        "libcxx",
        "liblog_rust",
        "libonce_cell",
        "libscopeguard",

        // needed to work around duplicate symbols
+1 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ async-trait = "*"
tokio-test = "0.4.2"
tokio = { version = "1.23.0", features = ["macros"] }
scopeguard = "1.1.0"
once_cell = "1.17.1"

[lib]
crate-type = ["rlib"]
+72 −328
Original line number Diff line number Diff line
//! This module handles "arbitration" of ATT packets, to determine whether they
//! should be handled by the primary stack or by the "Private GATT" stack
//! should be handled by the primary stack or by the Rust stack

use std::{collections::HashMap, sync::Mutex};
use std::sync::{Arc, Mutex};

use log::{error, info, trace};
use log::{error, trace};
use once_cell::sync::OnceCell;

use crate::{
    do_in_rust_thread,
@@ -12,25 +13,18 @@ use crate::{

use super::{
    ffi::{InterceptAction, StoreCallbacksFromRust},
    ids::{AdvertiserId, ConnectionId, ServerId, TransportIndex},
    ids::{AdvertiserId, TransportIndex},
    mtu::MtuEvent,
    opcode_types::{classify_opcode, OperationType},
    server::isolation_manager::IsolationManager,
};

static ARBITER: Mutex<Option<Arbiter>> = Mutex::new(None);

/// This class is responsible for tracking which connections and advertising we
/// own, and using this information to decide what packets should be
/// intercepted, and which should be forwarded to the legacy stack.
#[derive(Default)]
pub struct Arbiter {
    advertiser_to_server: HashMap<AdvertiserId, ServerId>,
    transport_to_owned_connection: HashMap<TransportIndex, ConnectionId>,
}
static ARBITER: OnceCell<Arc<Mutex<IsolationManager>>> = OnceCell::new();

/// Initialize the Arbiter
pub fn initialize_arbiter() {
    *ARBITER.lock().unwrap() = Some(Arbiter::new());
pub fn initialize_arbiter() -> Arc<Mutex<IsolationManager>> {
    let arbiter = Arc::new(Mutex::new(IsolationManager::new()));
    ARBITER.set(arbiter.clone()).unwrap_or_else(|_| panic!("Rust stack should only start up once"));

    StoreCallbacksFromRust(
        on_le_connect,
@@ -40,65 +34,24 @@ pub fn initialize_arbiter() {
        |tcb_idx, mtu| on_mtu_event(TransportIndex(tcb_idx), MtuEvent::IncomingResponse(mtu)),
        |tcb_idx, mtu| on_mtu_event(TransportIndex(tcb_idx), MtuEvent::IncomingRequest(mtu)),
    );

    arbiter
}

/// Acquire the mutex holding the Arbiter and provide a mutable reference to the
/// supplied closure
pub fn with_arbiter<T>(f: impl FnOnce(&mut Arbiter) -> T) -> T {
    f(ARBITER.lock().unwrap().as_mut().unwrap())
}

impl Arbiter {
    /// Constructor
    pub fn new() -> Self {
        Arbiter {
            advertiser_to_server: HashMap::new(),
            transport_to_owned_connection: HashMap::new(),
        }
    }

    /// Link a given GATT server to an LE advertising set, so incoming
    /// connections to this advertiser will be visible only by the linked
    /// server
    pub fn associate_server_with_advertiser(
        &mut self,
        server_id: ServerId,
        advertiser_id: AdvertiserId,
    ) {
        info!("associating server {server_id:?} with advertising set {advertiser_id:?}");
        let old = self.advertiser_to_server.insert(advertiser_id, server_id);
        if let Some(old) = old {
            error!("new server {server_id:?} associated with same advertiser {advertiser_id:?}, displacing old server {old:?}");
        }
    }

    /// Remove all linked advertising sets from the provided server
    pub fn clear_server(&mut self, server_id: ServerId) {
        info!("clearing advertisers associated with {server_id:?}");
        self.advertiser_to_server.retain(|_, server| *server != server_id);
    }

    /// Clear the server associated with this advertiser, if one exists
    pub fn clear_advertiser(&mut self, advertiser_id: AdvertiserId) {
        info!("removing server (if any) associated with advertiser {advertiser_id:?}");
        self.advertiser_to_server.remove(&advertiser_id);
    }

    /// Check if this conn_id is currently owned by the Rust stack
    pub fn is_connection_isolated(&self, conn_id: ConnectionId) -> bool {
        self.transport_to_owned_connection.values().any(|owned_conn_id| *owned_conn_id == conn_id)
pub fn with_arbiter<T>(f: impl FnOnce(&mut IsolationManager) -> T) -> T {
    f(ARBITER.get().unwrap().lock().as_mut().unwrap())
}

/// Test to see if a buffer contains a valid ATT packet with an opcode we
/// are interested in intercepting (those intended for servers that are isolated)
    pub fn try_parse_att_server_packet(
        &self,
fn try_parse_att_server_packet(
    isolation_manager: &IsolationManager,
    tcb_idx: TransportIndex,
    packet: Box<[u8]>,
) -> Option<OwnedAttView> {
        if !self.transport_to_owned_connection.contains_key(&tcb_idx) {
            return None;
        }
    isolation_manager.get_server_id(tcb_idx)?;

    let att = OwnedAttView::try_parse(packet).ok()?;

@@ -109,52 +62,18 @@ impl Arbiter {
    }

    match classify_opcode(att.view().get_opcode()) {
            OperationType::Command | OperationType::Request | OperationType::Confirmation => {
                Some(att)
            }
        OperationType::Command | OperationType::Request | OperationType::Confirmation => Some(att),
        _ => None,
    }
}

    /// Check if an incoming connection should be intercepted and, if so, on
    /// what conn_id
    pub fn on_le_connect(
        &mut self,
        tcb_idx: TransportIndex,
        advertiser: AdvertiserId,
    ) -> Option<ConnectionId> {
        info!(
            "processing incoming connection on transport {tcb_idx:?} to advertiser {advertiser:?}"
        );
        let server_id = *self.advertiser_to_server.get(&advertiser)?;
        info!("connection is isolated to server {server_id:?}");

        let conn_id = ConnectionId::new(tcb_idx, server_id);
        let old = self.transport_to_owned_connection.insert(tcb_idx, conn_id);
        if old.is_some() {
            error!("new server {server_id:?} on transport {tcb_idx:?} displacing existing registered connection {conn_id:?}")
        }
        Some(conn_id)
    }

    /// Handle a disconnection, if any, and return whether the disconnection was registered
    pub fn on_le_disconnect(&mut self, tcb_idx: TransportIndex) -> bool {
        info!("processing disconnection on transport {tcb_idx:?}");
        self.transport_to_owned_connection.remove(&tcb_idx).is_some()
    }

    /// Look up the conn_id for a given tcb_idx, if present
    pub fn get_conn_id(&self, tcb_idx: TransportIndex) -> Option<ConnectionId> {
        self.transport_to_owned_connection.get(&tcb_idx).copied()
    }
}

fn on_le_connect(tcb_idx: u8, advertiser: u8) {
    if let Some(conn_id) = with_arbiter(|arbiter| {
        arbiter.on_le_connect(TransportIndex(tcb_idx), AdvertiserId(advertiser))
    }) {
    let tcb_idx = TransportIndex(tcb_idx);
    let advertiser = AdvertiserId(advertiser);
    let is_isolated = with_arbiter(|arbiter| arbiter.is_advertiser_isolated(advertiser));
    if is_isolated {
        do_in_rust_thread(move |modules| {
            if let Err(err) = modules.gatt_module.on_le_connect(conn_id) {
            if let Err(err) = modules.gatt_module.on_le_connect(tcb_idx, Some(advertiser)) {
                error!("{err:?}")
            }
        })
@@ -163,7 +82,8 @@ fn on_le_connect(tcb_idx: u8, advertiser: u8) {

fn on_le_disconnect(tcb_idx: u8) {
    let tcb_idx = TransportIndex(tcb_idx);
    if with_arbiter(|arbiter| arbiter.on_le_disconnect(tcb_idx)) {
    let was_isolated = with_arbiter(|arbiter| arbiter.is_connection_isolated(tcb_idx));
    if was_isolated {
        do_in_rust_thread(move |modules| {
            if let Err(err) = modules.gatt_module.on_le_disconnect(tcb_idx) {
                error!("{err:?}")
@@ -175,7 +95,7 @@ fn on_le_disconnect(tcb_idx: u8) {
fn intercept_packet(tcb_idx: u8, packet: Vec<u8>) -> InterceptAction {
    let tcb_idx = TransportIndex(tcb_idx);
    if let Some(att) = with_arbiter(|arbiter| {
        arbiter.try_parse_att_server_packet(tcb_idx, packet.into_boxed_slice())
        try_parse_att_server_packet(arbiter, tcb_idx, packet.into_boxed_slice())
    }) {
        do_in_rust_thread(move |modules| {
            trace!("pushing packet to GATT");
@@ -192,7 +112,7 @@ fn intercept_packet(tcb_idx: u8, packet: Vec<u8>) -> InterceptAction {
}

fn on_mtu_event(tcb_idx: TransportIndex, event: MtuEvent) {
    if with_arbiter(|arbiter| arbiter.get_conn_id(tcb_idx)).is_some() {
    if with_arbiter(|arbiter| arbiter.is_connection_isolated(tcb_idx)) {
        do_in_rust_thread(move |modules| {
            let Some(bearer) = modules.gatt_module.get_bearer(tcb_idx) else {
                error!("Bearer for {tcb_idx:?} not found");
@@ -210,7 +130,7 @@ mod test {
    use super::*;

    use crate::{
        gatt::ids::AttHandle,
        gatt::ids::{AttHandle, ServerId},
        packets::{
            AttBuilder, AttExchangeMtuRequestBuilder, AttOpcode, AttReadRequestBuilder,
            Serializable,
@@ -218,260 +138,84 @@ mod test {
    };

    const TCB_IDX: TransportIndex = TransportIndex(1);
    const ANOTHER_TCB_IDX: TransportIndex = TransportIndex(2);
    const ADVERTISER_ID: AdvertiserId = AdvertiserId(3);
    const SERVER_ID: ServerId = ServerId(4);

    const CONN_ID: ConnectionId = ConnectionId::new(TCB_IDX, SERVER_ID);

    const ANOTHER_ADVERTISER_ID: AdvertiserId = AdvertiserId(5);

    #[test]
    fn test_non_isolated_connect() {
        let mut arbiter = Arbiter::new();

        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        assert!(conn_id.is_none())
    }

    #[test]
    fn test_isolated_connect() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);

        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        assert_eq!(conn_id, Some(CONN_ID));
    }

    #[test]
    fn test_non_isolated_connect_with_isolated_advertiser() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);

        let conn_id = arbiter.on_le_connect(TCB_IDX, ANOTHER_ADVERTISER_ID);

        assert!(conn_id.is_none())
    }

    #[test]
    fn test_non_isolated_disconnect() {
        let mut arbiter = Arbiter::new();
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        let ok = arbiter.on_le_disconnect(TCB_IDX);

        assert!(!ok)
    }

    #[test]
    fn test_isolated_disconnect() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        let ok = arbiter.on_le_disconnect(TCB_IDX);

        assert!(ok)
    }

    #[test]
    fn test_advertiser_id_reuse() {
        let mut arbiter = Arbiter::new();
        // start an advertiser associated with the server, then kill the advertiser
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.clear_advertiser(ADVERTISER_ID);

        // a new advertiser appeared with the same ID and got a connection
        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        // but we should not be isolated since this is a new advertiser reusing the old
        // ID
        assert!(conn_id.is_none())
    }

    #[test]
    fn test_server_closed() {
        let mut arbiter = Arbiter::new();
        // start an advertiser associated with the server, then kill the server
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.clear_server(SERVER_ID);

        // then afterwards we get a connection to this advertiser
        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        // since the server is gone, we should not capture the connection
        assert!(conn_id.is_none())
    }

    #[test]
    fn test_connection_isolated() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID).unwrap();

        let is_isolated = arbiter.is_connection_isolated(conn_id);

        assert!(is_isolated)
    }

    #[test]
    fn test_connection_isolated_after_advertiser_stops() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID).unwrap();
        arbiter.clear_advertiser(ADVERTISER_ID);

        let is_isolated = arbiter.is_connection_isolated(conn_id);

        assert!(is_isolated)
    }

    #[test]
    fn test_connection_isolated_after_server_stops() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID).unwrap();
        arbiter.clear_server(SERVER_ID);

        let is_isolated = arbiter.is_connection_isolated(conn_id);

        assert!(is_isolated)
    fn create_manager_with_isolated_connection(
        tcb_idx: TransportIndex,
        server_id: ServerId,
    ) -> IsolationManager {
        let mut isolation_manager = IsolationManager::new();
        isolation_manager.associate_server_with_advertiser(server_id, ADVERTISER_ID);
        isolation_manager.on_le_connect(tcb_idx, Some(ADVERTISER_ID));
        isolation_manager
    }

    #[test]
    fn test_packet_capture_when_isolated() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        let isolation_manager = create_manager_with_isolated_connection(TCB_IDX, SERVER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::READ_REQUEST,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());
        let out = try_parse_att_server_packet(
            &isolation_manager,
            TCB_IDX,
            packet.to_vec().unwrap().into(),
        );

        assert!(out.is_some());
    }

    #[test]
    fn test_packet_bypass_when_isolated() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        let isolation_manager = create_manager_with_isolated_connection(TCB_IDX, SERVER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::ERROR_RESPONSE,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());
        let out = try_parse_att_server_packet(
            &isolation_manager,
            TCB_IDX,
            packet.to_vec().unwrap().into(),
        );

        assert!(out.is_none());
    }

    #[test]
    fn test_mtu_bypass() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        let isolation_manager = create_manager_with_isolated_connection(TCB_IDX, SERVER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::EXCHANGE_MTU_REQUEST,
            _child_: AttExchangeMtuRequestBuilder { mtu: 64 }.into(),
        };

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());
        let out = try_parse_att_server_packet(
            &isolation_manager,
            TCB_IDX,
            packet.to_vec().unwrap().into(),
        );

        assert!(out.is_none());
    }

    #[test]
    fn test_packet_bypass_when_not_isolated() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ANOTHER_ADVERTISER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::READ_REQUEST,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());

        assert!(out.is_none());
    }

    #[test]
    fn test_packet_bypass_when_different_connection() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        arbiter.on_le_connect(ANOTHER_TCB_IDX, ANOTHER_ADVERTISER_ID);
        let isolation_manager = IsolationManager::new();
        let packet = AttBuilder {
            opcode: AttOpcode::READ_REQUEST,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };

        let out =
            arbiter.try_parse_att_server_packet(ANOTHER_TCB_IDX, packet.to_vec().unwrap().into());
        let out = try_parse_att_server_packet(
            &isolation_manager,
            TCB_IDX,
            packet.to_vec().unwrap().into(),
        );

        assert!(out.is_none());
    }

    #[test]
    fn test_packet_capture_when_isolated_after_advertiser_closes() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::READ_REQUEST,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };
        arbiter.clear_advertiser(ADVERTISER_ID);

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());

        assert!(out.is_some());
    }

    #[test]
    fn test_packet_capture_when_isolated_after_server_closes() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        let packet = AttBuilder {
            opcode: AttOpcode::READ_REQUEST,
            _child_: AttReadRequestBuilder { attribute_handle: AttHandle(1).into() }.into(),
        };
        arbiter.clear_server(SERVER_ID);

        let out = arbiter.try_parse_att_server_packet(TCB_IDX, packet.to_vec().unwrap().into());

        assert!(out.is_some());
    }

    #[test]
    fn test_not_isolated_after_disconnection() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        arbiter.on_le_disconnect(TCB_IDX);
        let is_isolated = arbiter.is_connection_isolated(CONN_ID);

        assert!(!is_isolated);
    }

    #[test]
    fn test_tcb_idx_reuse_after_isolated() {
        let mut arbiter = Arbiter::new();
        arbiter.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
        arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);
        arbiter.clear_advertiser(ADVERTISER_ID);
        arbiter.on_le_disconnect(TCB_IDX);

        let conn_id = arbiter.on_le_connect(TCB_IDX, ADVERTISER_ID);

        assert!(conn_id.is_none());
        assert!(!arbiter.is_connection_isolated(CONN_ID));
    }
}
+20 −15
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ use crate::{
};

use super::{
    arbiter::{self, with_arbiter},
    arbiter::with_arbiter,
    callbacks::{GattWriteRequestType, GattWriteType, TransactionDecision},
    channel::AttTransport,
    ids::{AdvertiserId, AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
@@ -288,13 +288,13 @@ fn open_server(server_id: u8) {

    let server_id = ServerId(server_id);

    do_in_rust_thread(move |modules| {
        if always_use_private_gatt_for_debugging_is_enabled() {
        with_arbiter(|arbiter| {
            arbiter.associate_server_with_advertiser(server_id, AdvertiserId(0))
        });
            modules
                .gatt_module
                .get_isolation_manager()
                .associate_server_with_advertiser(server_id, AdvertiserId(0))
        }

    do_in_rust_thread(move |modules| {
        if let Err(err) = modules.gatt_module.open_gatt_server(server_id) {
            error!("{err:?}")
        }
@@ -308,10 +308,6 @@ fn close_server(server_id: u8) {

    let server_id = ServerId(server_id);

    if !always_use_private_gatt_for_debugging_is_enabled() {
        with_arbiter(move |arbiter| arbiter.clear_server(server_id));
    }

    do_in_rust_thread(move |modules| {
        if let Err(err) = modules.gatt_module.close_gatt_server(server_id) {
            error!("{err:?}")
@@ -435,7 +431,7 @@ fn is_connection_isolated(conn_id: u16) -> bool {
        return false;
    }

    with_arbiter(|arbiter| arbiter.is_connection_isolated(ConnectionId(conn_id)))
    with_arbiter(|arbiter| arbiter.is_connection_isolated(ConnectionId(conn_id).get_tcb_idx()))
}

fn send_response(_server_id: u8, conn_id: u16, trans_id: u32, status: u8, value: &[u8]) {
@@ -495,8 +491,13 @@ fn associate_server_with_advertiser(server_id: u8, advertiser_id: u8) {
        return;
    }

    arbiter::with_arbiter(move |arbiter| {
        arbiter.associate_server_with_advertiser(ServerId(server_id), AdvertiserId(advertiser_id))
    let server_id = ServerId(server_id);
    let advertiser_id = AdvertiserId(advertiser_id);
    do_in_rust_thread(move |modules| {
        modules
            .gatt_module
            .get_isolation_manager()
            .associate_server_with_advertiser(server_id, advertiser_id);
    })
}

@@ -505,7 +506,11 @@ fn clear_advertiser(advertiser_id: u8) {
        return;
    }

    arbiter::with_arbiter(move |arbiter| arbiter.clear_advertiser(AdvertiserId(advertiser_id)))
    let advertiser_id = AdvertiserId(advertiser_id);

    do_in_rust_thread(move |modules| {
        modules.gatt_module.get_isolation_manager().clear_advertiser(advertiser_id);
    })
}

#[cfg(test)]
+47 −15

File changed.

Preview size limit exceeded, changes collapsed.

Loading