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

Commit 3ebc2918 authored by Pomai Ahlo's avatar Pomai Ahlo
Browse files

RfcommTest: Bumble pairing config override

Override pairing config so that insecure tests run without needing to
bond the devices beforehand

Bug: 331415222
Test: atest BumbleBluetoothTests:RfcommTest
Flag: TEST_ONLY
Change-Id: I2cdb8003c314d33f77ed503a0361cdbf6f64d5dd
parent e83fb87c
Loading
Loading
Loading
Loading
+11 −0
Original line number Original line Diff line number Diff line
@@ -30,6 +30,7 @@ import io.grpc.okhttp.OkHttpChannelBuilder;


import org.junit.rules.ExternalResource;
import org.junit.rules.ExternalResource;


import pandora.BumbleConfigGrpc;
import pandora.DckGrpc;
import pandora.DckGrpc;
import pandora.GATTGrpc;
import pandora.GATTGrpc;
import pandora.HIDGrpc;
import pandora.HIDGrpc;
@@ -174,6 +175,16 @@ public final class PandoraDevice extends ExternalResource {
        return HostGrpc.newBlockingStub(mChannel);
        return HostGrpc.newBlockingStub(mChannel);
    }
    }


    /** Get Pandora BumbleConfig service */
    public BumbleConfigGrpc.BumbleConfigStub bumbleConfig() {
        return BumbleConfigGrpc.newStub(mChannel);
    }

    /** Get Pandora BumbleConfig service */
    public BumbleConfigGrpc.BumbleConfigBlockingStub bumbleConfigBlocking() {
        return BumbleConfigGrpc.newBlockingStub(mChannel);
    }

    /** Get Pandora HID service */
    /** Get Pandora HID service */
    public HIDGrpc.HIDStub hid() {
    public HIDGrpc.HIDStub hid() {
        return HIDGrpc.newStub(mChannel);
        return HIDGrpc.newStub(mChannel);
+88 −18
Original line number Original line Diff line number Diff line
@@ -18,7 +18,10 @@ package android.bluetooth
import android.Manifest
import android.Manifest
import android.annotation.SuppressLint
import android.annotation.SuppressLint
import android.bluetooth.test_utils.EnableBluetoothRule
import android.bluetooth.test_utils.EnableBluetoothRule
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -37,8 +40,16 @@ import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlin.concurrent.thread
import kotlinx.coroutines.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withTimeout
import org.junit.After
import org.junit.After
import org.junit.Before
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Rule
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
@@ -47,6 +58,8 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.timeout
import org.mockito.kotlin.timeout
import org.mockito.kotlin.verify
import org.mockito.kotlin.verify
import pandora.BumbleConfigProto
import pandora.HostProto
import pandora.RfcommProto
import pandora.RfcommProto
import pandora.RfcommProto.ServerId
import pandora.RfcommProto.ServerId


@@ -79,27 +92,48 @@ class RfcommTest {
    @Rule(order = 3) @JvmField val enableBluetoothRule = EnableBluetoothRule(false, true)
    @Rule(order = 3) @JvmField val enableBluetoothRule = EnableBluetoothRule(false, true)


    private lateinit var mRemoteDevice: BluetoothDevice
    private lateinit var mRemoteDevice: BluetoothDevice
    private lateinit var host: Host
    private lateinit var mHost: Host
    private var mConnectionCounter = 1
    private var mConnectionCounter = 1
    private var mProfileServiceListener = mock<BluetoothProfile.ServiceListener>()
    private var mProfileServiceListener = mock<BluetoothProfile.ServiceListener>()


    private val mFlow: Flow<Intent>
    private val mScope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(2))

    @OptIn(ExperimentalStdlibApi::class)
    @OptIn(ExperimentalStdlibApi::class)
    private val bdAddrFormat = HexFormat { bytes { byteSeparator = ":" } }
    private val bdAddrFormat = HexFormat { bytes { byteSeparator = ":" } }
    @OptIn(ExperimentalStdlibApi::class)
    @OptIn(ExperimentalStdlibApi::class)
    private val mLocalAddress: ByteString =
    private val mLocalAddress: ByteString =
        ByteString.copyFrom("DA:4C:10:DE:17:00".hexToByteArray(bdAddrFormat))
        ByteString.copyFrom("DA:4C:10:DE:17:00".hexToByteArray(bdAddrFormat))


    init {
        val intentFilter = IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
        mFlow = intentFlow(mContext, intentFilter, mScope).shareIn(mScope, SharingStarted.Eagerly)
    }

    /*
    /*
        Setup:
        Setup:
        1. Initialize host and mRemoteDevice
        1. Initialize host and mRemoteDevice
        2. Disable A2DP, HFP, and HID profiles
        2. Override pairing config to enable insecure tests
        3. Bond devices
        3. Disable A2DP, HFP, and HID profiles
        4. Disconnect devices
        4. Disconnect devices, if they are connected
    */
    */
    @Before
    @Before
    fun setUp() {
    fun setUp() {
        mRemoteDevice = mBumble.remoteDevice
        mRemoteDevice = mBumble.remoteDevice
        host = Host(mContext)
        mHost = Host(mContext)

        // Set Bonding
        val pairingConfig =
            BumbleConfigProto.PairingConfig.newBuilder()
                .setBonding(false)
                .setMitm(false)
                .setSc(false)
                .setIdentityAddressType(HostProto.OwnAddressType.PUBLIC)
                .build()
        val overrideRequest =
            BumbleConfigProto.OverrideRequest.newBuilder().setPairingConfig(pairingConfig).build()
        mBumble.bumbleConfigBlocking().override(overrideRequest)

        val bluetoothA2dp = getProfileProxy(mContext, BluetoothProfile.A2DP) as BluetoothA2dp
        val bluetoothA2dp = getProfileProxy(mContext, BluetoothProfile.A2DP) as BluetoothA2dp
        bluetoothA2dp.setConnectionPolicy(
        bluetoothA2dp.setConnectionPolicy(
            mRemoteDevice,
            mRemoteDevice,
@@ -116,23 +150,22 @@ class RfcommTest {
            mRemoteDevice,
            mRemoteDevice,
            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
        )
        )
        host.createBondAndVerify(mRemoteDevice)
        if (mRemoteDevice.isConnected) {
        if (mRemoteDevice.isConnected) {
            host.disconnectAndVerify(mRemoteDevice)
            mHost.disconnectAndVerify(mRemoteDevice)
        }
        }
    }
    }


    /*
    /*
        TearDown:
        TearDown:
        1. unbond
        1. remove bond
        2. shutdown host
        2. shutdown host
    */
    */
    @After
    @After
    fun tearDown() {
    fun tearDown() {
        if (mAdapter.bondedDevices.contains(mRemoteDevice)) {
        if (mAdapter.bondedDevices.contains(mRemoteDevice)) {
            host.removeBondAndVerify(mRemoteDevice)
            mHost.removeBondAndVerify(mRemoteDevice)
        }
        }
        host.close()
        mHost.close()
    }
    }


    /*
    /*
@@ -142,7 +175,7 @@ class RfcommTest {
       3. Verify that devices are connected.
       3. Verify that devices are connected.
    */
    */
    @Test
    @Test
    fun clientConnectToOpenServerSocketBondedInsecure() {
    fun clientConnectToOpenServerSocketInsecure() {
        startServer { serverId -> createConnectAcceptSocket(isSecure = false, serverId) }
        startServer { serverId -> createConnectAcceptSocket(isSecure = false, serverId) }
    }
    }


@@ -153,7 +186,7 @@ class RfcommTest {
       3. Verify that devices are connected.
       3. Verify that devices are connected.
    */
    */
    @Test
    @Test
    fun clientConnectToOpenServerSocketBondedSecure() {
    fun clientConnectToOpenServerSocketSecure() {
        startServer { serverId -> createConnectAcceptSocket(isSecure = true, serverId) }
        startServer { serverId -> createConnectAcceptSocket(isSecure = true, serverId) }
    }
    }


@@ -361,13 +394,14 @@ class RfcommTest {
        4. Repeat for secure socket 2
        4. Repeat for secure socket 2
    */
    */
    @Test
    @Test
    @Ignore("b/380091558")
    fun connectTwoMixedClientsInsecureThenSecure() {
    fun connectTwoMixedClientsInsecureThenSecure() {
        startServer("ServerPort1", TEST_UUID) { serverId1 ->
        startServer("ServerPort1", TEST_UUID) { serverId1 ->
            startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
            startServer("ServerPort2", SERIAL_PORT_UUID) { serverId2 ->
                val socket2 = createSocket(mRemoteDevice, isSecure = false, SERIAL_PORT_UUID)
                val socket2 = createSocket(mRemoteDevice, isSecure = false, SERIAL_PORT_UUID)
                acceptSocket(serverId2)
                acceptSocket(serverId2)
                Truth.assertThat(socket2.isConnected).isTrue()
                Truth.assertThat(socket2.isConnected).isTrue()

                Log.i(TAG, "Finished with socket number 2")
                val socket1 = createSocket(mRemoteDevice, isSecure = true, TEST_UUID)
                val socket1 = createSocket(mRemoteDevice, isSecure = true, TEST_UUID)
                acceptSocket(serverId1)
                acceptSocket(serverId1)
                Truth.assertThat(socket1.isConnected).isTrue()
                Truth.assertThat(socket1.isConnected).isTrue()
@@ -405,11 +439,11 @@ class RfcommTest {
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @Test
    @Test
    fun serverSecureConnectThenRemoteDisconnect() {
    fun serverSecureConnectThenRemoteDisconnect() {
        // connect
        // step 1
        val (serverSock, connection) = connectRemoteToListeningSocket()
        val (serverSock, connection) = connectRemoteToListeningSocket()
        val disconnectRequest =
        val disconnectRequest =
            RfcommProto.DisconnectionRequest.newBuilder().setConnection(connection).build()
            RfcommProto.DisconnectionRequest.newBuilder().setConnection(connection).build()
        // disconnect from remote
        // step 2
        mBumble.rfcommBlocking().disconnect(disconnectRequest)
        mBumble.rfcommBlocking().disconnect(disconnectRequest)
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
    }
    }
@@ -422,8 +456,9 @@ class RfcommTest {
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @Test
    @Test
    fun serverSecureConnectThenLocalDisconnect() {
    fun serverSecureConnectThenLocalDisconnect() {
        // connect
        // step 1
        val (serverSock, _) = connectRemoteToListeningSocket()
        val (serverSock, _) = connectRemoteToListeningSocket()
        // step 2
        serverSock.close()
        serverSock.close()
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
    }
    }
@@ -434,7 +469,6 @@ class RfcommTest {
        uuid: String = TEST_UUID,
        uuid: String = TEST_UUID,
    ): Pair<BluetoothSocket, RfcommProto.RfcommConnection> {
    ): Pair<BluetoothSocket, RfcommProto.RfcommConnection> {
        val socket = createSocket(mRemoteDevice, isSecure, uuid)
        val socket = createSocket(mRemoteDevice, isSecure, uuid)

        val connection = acceptSocket(server)
        val connection = acceptSocket(server)
        Truth.assertThat(socket.isConnected).isTrue()
        Truth.assertThat(socket.isConnected).isTrue()


@@ -452,7 +486,24 @@ class RfcommTest {
            } else {
            } else {
                device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(uuid))
                device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(uuid))
            }
            }

        runBlocking(mScope.coroutineContext) {
            withTimeout(CONNECT_TIMEOUT.toMillis()) {
                // We need to reply to the pairing request in the case where the devices aren't
                // bonded yet
                if (isSecure && !mAdapter.bondedDevices.contains(device)) {
                    launch {
                        Log.i(TAG, "Waiting for ACTION_PAIRING_REQUEST")
                        mFlow
                            .filter { it.action == BluetoothDevice.ACTION_PAIRING_REQUEST }
                            .filter { it.getBluetoothDeviceExtra() == device }
                            .first()
                        device.setPairingConfirmation(true)
                    }
                }
                socket.connect()
                socket.connect()
            }
        }
        return socket
        return socket
    }
    }


@@ -526,9 +577,28 @@ class RfcommTest {
        return proxyCaptor.lastValue
        return proxyCaptor.lastValue
    }
    }


    fun Intent.getBluetoothDeviceExtra(): BluetoothDevice =
        this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)!!

    @kotlinx.coroutines.ExperimentalCoroutinesApi
    fun intentFlow(context: Context, intentFilter: IntentFilter, scope: CoroutineScope) =
        callbackFlow {
            val broadcastReceiver: BroadcastReceiver =
                object : BroadcastReceiver() {
                    override fun onReceive(context: Context, intent: Intent) {
                        Log.d(TAG, "intentFlow: onReceive: ${intent.action}")
                        scope.launch { trySendBlocking(intent) }
                    }
                }
            context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED)

            awaitClose { context.unregisterReceiver(broadcastReceiver) }
        }

    companion object {
    companion object {
        private val TAG = RfcommTest::class.java.getSimpleName()
        private val TAG = RfcommTest::class.java.getSimpleName()
        private val GRPC_TIMEOUT = Duration.ofSeconds(10)
        private val GRPC_TIMEOUT = Duration.ofSeconds(10)
        private val CONNECT_TIMEOUT = Duration.ofSeconds(7)
        private const val TEST_UUID = "2ac5d8f1-f58d-48ac-a16b-cdeba0892d65"
        private const val TEST_UUID = "2ac5d8f1-f58d-48ac-a16b-cdeba0892d65"
        private const val SERIAL_PORT_UUID = "00001101-0000-1000-8000-00805F9B34FB"
        private const val SERIAL_PORT_UUID = "00001101-0000-1000-8000-00805F9B34FB"
        private const val TEST_SERVER_NAME = "RFCOMM Server"
        private const val TEST_SERVER_NAME = "RFCOMM Server"
+8 −5
Original line number Original line Diff line number Diff line
@@ -20,19 +20,21 @@ from bumble.device import Device
from bumble.pairing import PairingConfig
from bumble.pairing import PairingConfig
from bumble.pairing import PairingDelegate as BasePairingDelegate
from bumble.pairing import PairingDelegate as BasePairingDelegate
from bumble.pandora import Config, utils
from bumble.pandora import Config, utils
from bumble.pandora.security import PairingDelegate
from bumble.pandora.security import PairingDelegate, SecurityService
from google.protobuf.empty_pb2 import Empty
from google.protobuf.empty_pb2 import Empty
from pandora.host_pb2 import PUBLIC
from pandora.host_pb2 import PUBLIC
from pandora_experimental.bumble_config_grpc_aio import BumbleConfigServicer
from pandora_experimental.bumble_config_grpc_aio import BumbleConfigServicer
from pandora_experimental.bumble_config_pb2 import (KeyDistribution,
from pandora_experimental.bumble_config_pb2 import (KeyDistribution, OverrideRequest)
                                                    OverrideRequest)




class BumbleConfigService(BumbleConfigServicer):
class BumbleConfigService(BumbleConfigServicer):
    device: Device
    device: Device


    def __init__(self, device: Device, server_config: Config) -> None:
    def __init__(self, device: Device, server_config: Config) -> None:
        self.log = utils.BumbleServerLoggerAdapter(logging.getLogger(), {"service_name": "BumbleConfig", "device": device})
        self.log = utils.BumbleServerLoggerAdapter(logging.getLogger(), {
            "service_name": "BumbleConfig",
            "device": device,
        })
        self.device = device
        self.device = device
        self.server_config = server_config
        self.server_config = server_config


@@ -50,6 +52,7 @@ class BumbleConfigService(BumbleConfigServicer):
        def pairing_config_factory(connection: BumbleConnection) -> PairingConfig:
        def pairing_config_factory(connection: BumbleConnection) -> PairingConfig:
            pairing_delegate = PairingDelegate(
            pairing_delegate = PairingDelegate(
                connection=connection,
                connection=connection,
                service=SecurityService(self.device, self.server_config),
                io_capability=BasePairingDelegate.IoCapability(request.io_capability),
                io_capability=BasePairingDelegate.IoCapability(request.io_capability),
                local_initiator_key_distribution=parseProtoKeyDistribution(request.initiator_key_distribution),
                local_initiator_key_distribution=parseProtoKeyDistribution(request.initiator_key_distribution),
                local_responder_key_distribution=parseProtoKeyDistribution(request.responder_key_distribution),
                local_responder_key_distribution=parseProtoKeyDistribution(request.responder_key_distribution),
@@ -61,7 +64,7 @@ class BumbleConfigService(BumbleConfigServicer):
                mitm=pc_req.mitm,
                mitm=pc_req.mitm,
                bonding=pc_req.bonding,
                bonding=pc_req.bonding,
                identity_address_type=PairingConfig.AddressType.PUBLIC
                identity_address_type=PairingConfig.AddressType.PUBLIC
                if request.identity_address_type == PUBLIC else PairingConfig.AddressType.RANDOM,
                if pc_req.identity_address_type == PUBLIC else PairingConfig.AddressType.RANDOM,
                delegate=pairing_delegate,
                delegate=pairing_delegate,
            )
            )
            self.log.debug(f"Override: {pairing_config}")
            self.log.debug(f"Override: {pairing_config}")