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

Commit f547af68 authored by Pomai Ahlo's avatar Pomai Ahlo Committed by Gerrit Code Review
Browse files

Merge "RfcommTest: Bumble pairing config override" into main

parents d10e4e6d 3ebc2918
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import io.grpc.okhttp.OkHttpChannelBuilder;

import org.junit.rules.ExternalResource;

import pandora.BumbleConfigGrpc;
import pandora.DckGrpc;
import pandora.GATTGrpc;
import pandora.HIDGrpc;
@@ -174,6 +175,16 @@ public final class PandoraDevice extends ExternalResource {
        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 */
    public HIDGrpc.HIDStub hid() {
        return HIDGrpc.newStub(mChannel);
+88 −18
Original line number Diff line number Diff line
@@ -18,7 +18,10 @@ package android.bluetooth
import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.test_utils.EnableBluetoothRule
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -37,8 +40,16 @@ import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlinx.coroutines.*
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.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +58,8 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.timeout
import org.mockito.kotlin.verify
import pandora.BumbleConfigProto
import pandora.HostProto
import pandora.RfcommProto
import pandora.RfcommProto.ServerId

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

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

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

    @OptIn(ExperimentalStdlibApi::class)
    private val bdAddrFormat = HexFormat { bytes { byteSeparator = ":" } }
    @OptIn(ExperimentalStdlibApi::class)
    private val mLocalAddress: ByteString =
        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:
        1. Initialize host and mRemoteDevice
        2. Disable A2DP, HFP, and HID profiles
        3. Bond devices
        4. Disconnect devices
        2. Override pairing config to enable insecure tests
        3. Disable A2DP, HFP, and HID profiles
        4. Disconnect devices, if they are connected
    */
    @Before
    fun setUp() {
        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
        bluetoothA2dp.setConnectionPolicy(
            mRemoteDevice,
@@ -116,23 +150,22 @@ class RfcommTest {
            mRemoteDevice,
            BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
        )
        host.createBondAndVerify(mRemoteDevice)
        if (mRemoteDevice.isConnected) {
            host.disconnectAndVerify(mRemoteDevice)
            mHost.disconnectAndVerify(mRemoteDevice)
        }
    }

    /*
        TearDown:
        1. unbond
        1. remove bond
        2. shutdown host
    */
    @After
    fun tearDown() {
        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.
    */
    @Test
    fun clientConnectToOpenServerSocketBondedInsecure() {
    fun clientConnectToOpenServerSocketInsecure() {
        startServer { serverId -> createConnectAcceptSocket(isSecure = false, serverId) }
    }

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

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

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

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

@@ -452,7 +486,24 @@ class RfcommTest {
            } else {
                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()
            }
        }
        return socket
    }

@@ -526,9 +577,28 @@ class RfcommTest {
        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 {
        private val TAG = RfcommTest::class.java.getSimpleName()
        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 SERIAL_PORT_UUID = "00001101-0000-1000-8000-00805F9B34FB"
        private const val TEST_SERVER_NAME = "RFCOMM Server"
+8 −5
Original line number Diff line number Diff line
@@ -20,19 +20,21 @@ from bumble.device import Device
from bumble.pairing import PairingConfig
from bumble.pairing import PairingDelegate as BasePairingDelegate
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 pandora.host_pb2 import PUBLIC
from pandora_experimental.bumble_config_grpc_aio import BumbleConfigServicer
from pandora_experimental.bumble_config_pb2 import (KeyDistribution,
                                                    OverrideRequest)
from pandora_experimental.bumble_config_pb2 import (KeyDistribution, OverrideRequest)


class BumbleConfigService(BumbleConfigServicer):
    device: Device

    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.server_config = server_config

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