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

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

Merge "RfcommTest: ConnectToServer and Disconnect" into main

parents 4cfaedf2 da5f3ac0
Loading
Loading
Loading
Loading
+80 −5
Original line number Diff line number Diff line
@@ -19,15 +19,22 @@ import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.test_utils.EnableBluetoothRule
import android.content.Context
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.bluetooth.flags.Flags
import com.android.compatibility.common.util.AdoptShellPermissionsRule
import com.google.common.truth.Truth
import com.google.protobuf.ByteString
import java.io.IOException
import java.time.Duration
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import org.junit.After
@@ -42,7 +49,6 @@ import org.mockito.kotlin.timeout
import org.mockito.kotlin.verify
import pandora.RfcommProto
import pandora.RfcommProto.ServerId
import pandora.RfcommProto.StartServerRequest

@SuppressLint("MissingPermission")
@RunWith(AndroidJUnit4::class)
@@ -52,9 +58,13 @@ class RfcommTest {
    private val mManager = mContext.getSystemService(BluetoothManager::class.java)
    private val mAdapter = mManager!!.adapter

    // Gives shell permissions during the test.
    @Rule(order = 0)
    @JvmField
    val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()

    // Gives shell permissions during the test.
    @Rule(order = 1)
    @JvmField
    val mPermissionsRule =
        AdoptShellPermissionsRule(
            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
@@ -64,15 +74,21 @@ class RfcommTest {
        )

    // Set up a Bumble Pandora device for the duration of the test.
    @Rule(order = 1) @JvmField val mBumble = PandoraDevice()
    @Rule(order = 2) @JvmField val mBumble = PandoraDevice()

    @Rule(order = 2) @JvmField val enableBluetoothRule = EnableBluetoothRule(false, true)
    @Rule(order = 3) @JvmField val enableBluetoothRule = EnableBluetoothRule(false, true)

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

    @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))

    /*
        Setup:
        1. Initialize host and mRemoteDevice
@@ -381,6 +397,37 @@ class RfcommTest {
        }
    }

    /*
      Test Steps:
      1. Create listening socket and connect
      2. Disconnect RFCOMM from remote device
    */
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @Test
    fun serverSecureConnectThenRemoteDisconnect() {
        // connect
        val (serverSock, connection) = connectRemoteToListeningSocket()
        val disconnectRequest =
            RfcommProto.DisconnectionRequest.newBuilder().setConnection(connection).build()
        // disconnect from remote
        mBumble.rfcommBlocking().disconnect(disconnectRequest)
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
    }

    /*
      Test Steps:
      1. Create listening socket and connect
      2. Disconnect RFCOMM from local device
    */
    @RequiresFlagsEnabled(Flags.FLAG_TRIGGER_SEC_PROC_ON_INC_ACCESS_REQ)
    @Test
    fun serverSecureConnectThenLocalDisconnect() {
        // connect
        val (serverSock, _) = connectRemoteToListeningSocket()
        serverSock.close()
        Truth.assertThat(serverSock.channel).isEqualTo(-1) // ensure disconnected at RFCOMM Layer
    }

    private fun createConnectAcceptSocket(
        isSecure: Boolean,
        server: ServerId,
@@ -428,7 +475,8 @@ class RfcommTest {
        uuid: String = TEST_UUID,
        block: (ServerId) -> Unit,
    ) {
        val request = StartServerRequest.newBuilder().setName(name).setUuid(uuid).build()
        val request =
            RfcommProto.StartServerRequest.newBuilder().setName(name).setUuid(uuid).build()
        val response = mBumble.rfcommBlocking().startServer(request)

        try {
@@ -443,6 +491,33 @@ class RfcommTest {
        }
    }

    private fun connectRemoteToListeningSocket(
        name: String = TEST_SERVER_NAME,
        uuid: String = TEST_UUID,
    ): Pair<BluetoothServerSocket, RfcommProto.RfcommConnection> {
        var connection: RfcommProto.RfcommConnection? = null
        val connectRequest =
            RfcommProto.ConnectionRequest.newBuilder()
                .setAddress(mLocalAddress)
                .setUuid(uuid)
                .build()
        val t = thread {
            val connectResponse = mBumble.rfcommBlocking().connectToServer(connectRequest)
            connection = connectResponse.connection
        }
        val socket = mAdapter.listenUsingRfcommWithServiceRecord(name, UUID.fromString(uuid))

        try {
            socket.accept(3000) // 3 second timeout
        } catch (e: IOException) {
            Log.e(TAG, "Unexpected IOException: $e")
        }
        t.join()
        Truth.assertThat(connection).isNotNull()

        return Pair(socket, connection!!)
    }

    private fun getProfileProxy(context: Context, profile: Int): BluetoothProfile {
        mAdapter.getProfileProxy(context, mProfileServiceListener, profile)
        val proxyCaptor = argumentCaptor<BluetoothProfile>()
+42 −3
Original line number Diff line number Diff line
@@ -17,10 +17,13 @@ from typing import Dict, Optional

from bumble import core
from bumble.device import Device
from bumble.hci import Address
from bumble.rfcomm import (
    Server,
    make_service_sdp_records,
    Client,
    DLC,
    make_service_sdp_records,
    find_rfcomm_channel_with_uuid,
    Server,
)
from bumble.pandora import utils
import grpc
@@ -30,6 +33,8 @@ from pandora_experimental.rfcomm_pb2 import (
    AcceptConnectionResponse,
    ConnectionRequest,
    ConnectionResponse,
    DisconnectionRequest,
    DisconnectionResponse,
    RfcommConnection,
    RxRequest,
    RxResponse,
@@ -59,9 +64,12 @@ class RFCOMMService(RFCOMMServicer):

    class Connection:

        def __init__(self, dlc):
        client: Optional[Client]

        def __init__(self, dlc, client=None):
            self.dlc = dlc
            self.data_queue = asyncio.Queue()
            self.client = client

    class ServerPort:

@@ -83,6 +91,27 @@ class RFCOMMService(RFCOMMServicer):
            else:
                self.saved_dlc = dlc

    @utils.rpc
    async def ConnectToServer(self, request: ConnectionRequest, context: grpc.ServicerContext) -> ConnectionResponse:
        logging.info(f"ConnectToServer")
        address = Address(address=bytes(reversed(request.address)), address_type=Address.PUBLIC_DEVICE_ADDRESS)
        acl_connection = self.device.find_connection_by_bd_addr(address, transport=0)  # BR/EDR
        if acl_connection is None:
            acl_connection = await self.device.connect(address, transport=0)  # BR/EDR transport

        channel = await find_rfcomm_channel_with_uuid(acl_connection, request.uuid)

        client = Client(acl_connection)
        mux = await client.start()
        assert mux is not None

        dlc = await mux.open_dlc(channel)
        id = self.next_conn_id
        self.next_conn_id += 1
        self.connections[id] = self.Connection(dlc=dlc, client=client)
        self.connections[id].dlc.sink = self.connections[id].data_queue.put_nowait
        return ConnectionResponse(connection=RfcommConnection(id=id))

    @utils.rpc
    async def StartServer(self, request: StartServerRequest, context: grpc.ServicerContext) -> StartServerResponse:
        uuid = core.UUID(request.uuid)
@@ -118,6 +147,16 @@ class RFCOMMService(RFCOMMServicer):
        self.connections[id].dlc.sink = self.connections[id].data_queue.put_nowait
        return AcceptConnectionResponse(connection=RfcommConnection(id=id))

    @utils.rpc
    async def Disconnect(self, request: DisconnectionRequest, context: grpc.ServicerContext) -> DisconnectionResponse:
        logging.info(f"Disconnect")
        rfcomm_connection = self.connections[request.connection.id]
        assert rfcomm_connection is not None
        if rfcomm_connection.client is not None:
            await rfcomm_connection.client.shutdown()
        del rfcomm_connection
        return DisconnectionResponse()

    @utils.rpc
    async def StopServer(self, request: StopServerRequest, context: grpc.ServicerContext) -> StopServerResponse:
        logging.info(f"StopServer")