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

Commit 19a91c3e authored by Rahul Arya's avatar Rahul Arya
Browse files

[Pandora] Implementation of Pandora Pairing interface

Test: HOGP PTS tests (next CL)
Bug: 242326310
Tag: #feature
Change-Id: Iabf59cc20d1392bf341f925dabe58421629d8917
parent 8a31565c
Loading
Loading
Loading
Loading
+20 −13
Original line number Diff line number Diff line
@@ -40,22 +40,29 @@ message PairingEvent {
    bytes address = 1;
    // Authentication method used for this pairing event
    oneof method {
      // "Just Works" Secure Simple Pairing association
      // "Just Works" SSP / LE pairing association
      // model. Confirmation is automatic.
      google.protobuf.Empty just_works = 2;
      // Numeric Comparison Secure Simple Pairing association
      // Numeric Comparison SSP / LE pairing association
      // model. Confirmation is required.
      uint32 numeric_comparison = 3;
      // Passkey Entry Secure Simple Pairing association model.
      // Passkey Entry SSP / LE pairing association model.
      // Passkey is typed by the user.
      // Only for LE legacy pairing or on devices without a display
      google.protobuf.Empty passkey_entry_request = 4;
      // Passkey Entry SSP / LE pairing association model.
      // Passkey is shown to the user.
      // The peer device receives a Passkey Entry request.
      bytes passkey_entry_notification = 4;
      // Passkey Entry Secure Simple Pairing association model.
      // Passkey is typed by the user.
      google.protobuf.Empty passkey_entry_request = 5;
      uint32 passkey_entry_notification = 5;
      // Legacy PIN Pairing.
      // A PIN Code is typed by the user on IUT.
      google.protobuf.Empty pin_code_request = 6;
      // Legacy PIN Pairing.
      // A PIN Code is typed by the user.
      google.protobuf.Empty pin_code = 6;
      // We generate a PIN code, and the user enters it in the peer
      // device. While this is not part of the specification, some display
      // devices automatically generate their PIN Code, instead of asking the
      // user to type it
      bytes pin_code_notification = 7;
    }
}

@@ -65,14 +72,14 @@ message PairingEventAnswer {
    // Answer when needed to the pairing event method.
    oneof answer {
      // Numeric Comparison confirmation.
      // Used when pairing event method is `numeric_comparison`.
      // Used when pairing event method is `numeric_comparison` or `just_works`
      bool confirm = 2;
      // Passkey typed by the user.
      // Used when pairing event method is `passkey_entry_request`.
      bytes passkey = 3;
      uint32 passkey = 3;
      // Pin typed by the user.
      // Used when pairing event method is `pin_code`.
      uint32 pin = 4;
      // Used when pairing event method is `pin_code_request`.
      bytes pin = 4;
    };
}

+89 −20
Original line number Diff line number Diff line
@@ -18,57 +18,53 @@ package com.android.pandora

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.EXTRA_PAIRING_VARIANT
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log

import com.google.protobuf.Empty
import com.google.protobuf.ByteString
import com.google.protobuf.Empty
import io.grpc.stub.StreamObserver

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch

import pandora.SecurityGrpc.SecurityImplBase
import pandora.HostProto.*
import pandora.SecurityGrpc.SecurityImplBase
import pandora.SecurityProto.*

const val TAG = "PandoraSecurity"

@kotlinx.coroutines.ExperimentalCoroutinesApi
class Security(private val context: Context) : SecurityImplBase() {
  private val TAG = "PandoraSecurity"

  private val scope: CoroutineScope
  private val globalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
  private val flow: Flow<Intent>

  private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
  private val bluetoothAdapter = bluetoothManager.adapter

  init {
    scope = CoroutineScope(Dispatchers.Default)

    val intentFilter = IntentFilter()
    intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)

    flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly)
    flow = intentFlow(context, intentFilter).shareIn(globalScope, SharingStarted.Eagerly)
  }

  fun deinit() {
    scope.cancel()
    globalScope.cancel()
  }

  override fun pair(request: PairRequest, responseObserver: StreamObserver<Empty>) {
    grpcUnary(scope, responseObserver) {
    grpcUnary(globalScope, responseObserver) {
      val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter)
      Log.i(TAG, "pair: ${bluetoothDevice.address}")
      bluetoothDevice.createBond()
@@ -80,11 +76,11 @@ class Security(private val context: Context) : SecurityImplBase() {
    request: PairingConfirmationRequest,
    responseObserver: StreamObserver<Empty>
  ) {
    grpcUnary<Empty>(scope, responseObserver) {
    grpcUnary(globalScope, responseObserver) {
      val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter)
      Log.i(TAG, "Confirm pairing for: address=${bluetoothDevice.getAddress()}")
      Log.i(TAG, "Confirm pairing for: address=${bluetoothDevice.address}")
      flow
        .filter { it.getAction() == BluetoothDevice.ACTION_PAIRING_REQUEST }
        .filter { it.action == BluetoothDevice.ACTION_PAIRING_REQUEST }
        .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
        .first()
      bluetoothDevice.setPairingConfirmation(request.pairingConfirmationValue)
@@ -96,7 +92,7 @@ class Security(private val context: Context) : SecurityImplBase() {
    request: DeletePairingRequest,
    responseObserver: StreamObserver<DeletePairingResponse>
  ) {
    grpcUnary<DeletePairingResponse>(scope, responseObserver) {
    grpcUnary<DeletePairingResponse>(globalScope, responseObserver) {
      val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter)
      Log.i(TAG, "DeletePairing: device=$bluetoothDevice")

@@ -120,4 +116,77 @@ class Security(private val context: Context) : SecurityImplBase() {
      DeletePairingResponse.getDefaultInstance()
    }
  }

  override fun onPairing(
    responseObserver: StreamObserver<PairingEvent>
  ): StreamObserver<PairingEventAnswer> =
    grpcBidirectionalStream(globalScope, responseObserver) {
      it
        .map { answer ->
          val device = answer.event.address.toBluetoothDevice(bluetoothAdapter)
          when (answer.answerCase!!) {
            PairingEventAnswer.AnswerCase.CONFIRM -> device.setPairingConfirmation(true)
            PairingEventAnswer.AnswerCase.PASSKEY ->
              error("We don't support SSP PASSKEY_ENTRY, since we always have a Display")
            PairingEventAnswer.AnswerCase.PIN -> device.setPin(answer.pin.toByteArray())
            PairingEventAnswer.AnswerCase.ANSWER_NOT_SET -> error("unexpected pairing answer type")
          }
        }
        .launchIn(this)

      // TODO(243977710) - Resolve the transport on which pairing is taking place
      // so we can disambiguate intents
      val transport = BluetoothDevice.TRANSPORT_AUTO

      flow.map { intent ->
        val device = intent.getBluetoothDeviceExtra()
        val variant = intent.getIntExtra(EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR)
        val eventBuilder =
          PairingEvent.newBuilder().setAddress(ByteString.copyFrom(device.toByteArray()))
        when (variant) {
          // SSP / LE Just Works
          BluetoothDevice.PAIRING_VARIANT_CONSENT ->
            eventBuilder.justWorks = Empty.getDefaultInstance()

          // SSP / LE Numeric Comparison
          BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ->
            eventBuilder.numericComparison =
              intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)

          // Out-Of-Band not currently supported
          BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT ->
            error("Received OOB pairing confirmation (UNSUPPORTED)")

          // Legacy PIN entry, or LE legacy passkey entry, depending on transport
          BluetoothDevice.PAIRING_VARIANT_PIN ->
            when (transport) {
              BluetoothDevice.TRANSPORT_BREDR ->
                eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
              BluetoothDevice.TRANSPORT_LE ->
                eventBuilder.passkeyEntryRequest = Empty.getDefaultInstance()
              else -> error("cannot determine pairing variant, since transport is unknown")
            }
          BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS ->
            eventBuilder.pinCodeRequest = Empty.getDefaultInstance()

          // Legacy PIN entry or LE legacy passkey entry, except we just generate the PIN in the
          // stack and display it to the user for convenience
          BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN -> {
            val passkey =
              intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
            when (transport) {
              BluetoothDevice.TRANSPORT_BREDR ->
                eventBuilder.pinCodeNotification =
                  ByteString.copyFrom(passkey.toString().toByteArray())
              BluetoothDevice.TRANSPORT_LE -> eventBuilder.passkeyEntryNotification = passkey
              else -> error("cannot determine pairing variant, since transport is unknown")
            }
          }
          else -> {
            error("Received unknown pairing variant $variant")
          }
        }
        eventBuilder.build()
      }
    }
}