1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pandora 18 19 import android.bluetooth.BluetoothManager 20 import android.bluetooth.BluetoothServerSocket 21 import android.content.Context 22 import android.util.Log 23 import com.google.protobuf.ByteString 24 import io.grpc.stub.StreamObserver 25 import java.io.Closeable 26 import java.io.IOException 27 import java.io.InputStream 28 import java.io.OutputStream 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.Dispatchers 31 import kotlinx.coroutines.cancel 32 import kotlinx.coroutines.withContext 33 import pandora.HostProto.Connection 34 import pandora.L2CAPGrpc.L2CAPImplBase 35 import pandora.L2capProto.AcceptL2CAPChannelRequest 36 import pandora.L2capProto.AcceptL2CAPChannelResponse 37 import pandora.L2capProto.CreateLECreditBasedChannelRequest 38 import pandora.L2capProto.CreateLECreditBasedChannelResponse 39 import pandora.L2capProto.ListenL2CAPChannelRequest 40 import pandora.L2capProto.ListenL2CAPChannelResponse 41 import pandora.L2capProto.ReceiveDataRequest 42 import pandora.L2capProto.ReceiveDataResponse 43 import pandora.L2capProto.SendDataRequest 44 import pandora.L2capProto.SendDataResponse 45 46 @kotlinx.coroutines.ExperimentalCoroutinesApi 47 class L2cap(val context: Context) : L2CAPImplBase(), Closeable { 48 private val TAG = "PandoraL2cap" 49 private val scope: CoroutineScope 50 private val BLUETOOTH_SERVER_SOCKET_TIMEOUT: Int = 10000 51 private val BUFFER_SIZE = 512 52 53 private val bluetoothManager = 54 context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager 55 private val bluetoothAdapter = bluetoothManager.adapter 56 private var connectionInStreamMap: HashMap<Connection, InputStream> = hashMapOf() 57 private var connectionOutStreamMap: HashMap<Connection, OutputStream> = hashMapOf() 58 private var connectionServerSocketMap: HashMap<Connection, BluetoothServerSocket> = hashMapOf() 59 60 init { 61 // Init the CoroutineScope 62 scope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 63 } 64 closenull65 override fun close() { 66 // Deinit the CoroutineScope 67 scope.cancel() 68 } 69 receivenull70 suspend fun receive(inStream: InputStream): ByteArray { 71 return withContext(Dispatchers.IO) { 72 val buf = ByteArray(BUFFER_SIZE) 73 inStream.read(buf, 0, BUFFER_SIZE) // blocking 74 Log.i(TAG, "receive: $buf") 75 buf 76 } 77 } 78 79 /** Open a BluetoothServerSocket to accept connections */ listenL2CAPChannelnull80 override fun listenL2CAPChannel( 81 request: ListenL2CAPChannelRequest, 82 responseObserver: StreamObserver<ListenL2CAPChannelResponse>, 83 ) { 84 grpcUnary(scope, responseObserver) { 85 Log.i(TAG, "listenL2CAPChannel: secure=${request.secure}") 86 val connection = request.connection 87 val bluetoothServerSocket = 88 if (request.secure) { 89 bluetoothAdapter.listenUsingL2capChannel() 90 } else { 91 bluetoothAdapter.listenUsingInsecureL2capChannel() 92 } 93 connectionServerSocketMap[connection] = bluetoothServerSocket 94 ListenL2CAPChannelResponse.newBuilder().build() 95 } 96 } 97 acceptL2CAPChannelnull98 override fun acceptL2CAPChannel( 99 request: AcceptL2CAPChannelRequest, 100 responseObserver: StreamObserver<AcceptL2CAPChannelResponse>, 101 ) { 102 grpcUnary(scope, responseObserver) { 103 Log.i(TAG, "acceptL2CAPChannel") 104 105 val connection = request.connection 106 val bluetoothServerSocket = connectionServerSocketMap[connection] 107 try { 108 val bluetoothSocket = 109 bluetoothServerSocket!!.accept(BLUETOOTH_SERVER_SOCKET_TIMEOUT) 110 connectionInStreamMap[connection] = bluetoothSocket.getInputStream()!! 111 connectionOutStreamMap[connection] = bluetoothSocket.getOutputStream()!! 112 } catch (e: IOException) { 113 Log.e(TAG, "bluetoothServerSocket not accepted", e) 114 throw e 115 } 116 117 AcceptL2CAPChannelResponse.newBuilder().build() 118 } 119 } 120 121 /** Set device to send LE based connection request */ createLECreditBasedChannelnull122 override fun createLECreditBasedChannel( 123 request: CreateLECreditBasedChannelRequest, 124 responseObserver: StreamObserver<CreateLECreditBasedChannelResponse>, 125 ) { 126 // Creates a gRPC coroutine in a given coroutine scope which executes a given suspended 127 // function 128 // returning a gRPC response and sends it on a given gRPC stream observer. 129 grpcUnary(scope, responseObserver) { 130 Log.i(TAG, "createLECreditBasedChannel: secure=${request.secure}, psm=${request.psm}") 131 val connection = request.connection 132 val device = request.connection.toBluetoothDevice(bluetoothAdapter) 133 val psm = request.psm 134 135 try { 136 val bluetoothSocket = 137 if (request.secure) { 138 device.createL2capChannel(psm) 139 } else { 140 device.createInsecureL2capChannel(psm) 141 } 142 bluetoothSocket.connect() 143 connectionInStreamMap[connection] = bluetoothSocket.getInputStream()!! 144 connectionOutStreamMap[connection] = bluetoothSocket.getOutputStream()!! 145 } catch (e: IOException) { 146 Log.d(TAG, "bluetoothSocket not connected: $e") 147 throw e 148 } 149 150 // Response sent to client 151 CreateLECreditBasedChannelResponse.newBuilder().build() 152 } 153 } 154 155 /** send data packet */ sendDatanull156 override fun sendData( 157 request: SendDataRequest, 158 responseObserver: StreamObserver<SendDataResponse>, 159 ) { 160 grpcUnary(scope, responseObserver) { 161 Log.i(TAG, "sendDataPacket: data=${request.data}") 162 val buffer = request.data!!.toByteArray() 163 val connection = request.connection 164 val outputStream = connectionOutStreamMap[connection]!! 165 166 withContext(Dispatchers.IO) { 167 try { 168 outputStream.write(buffer) 169 outputStream.flush() 170 } catch (e: IOException) { 171 Log.e(TAG, "Exception during writing to sendDataPacket output stream", e) 172 } 173 } 174 175 // Response sent to client 176 SendDataResponse.newBuilder().build() 177 } 178 } 179 receiveDatanull180 override fun receiveData( 181 request: ReceiveDataRequest, 182 responseObserver: StreamObserver<ReceiveDataResponse>, 183 ) { 184 grpcUnary(scope, responseObserver) { 185 Log.i(TAG, "receiveData") 186 val connection = request.connection 187 val inputStream = connectionInStreamMap[connection]!! 188 val buf = receive(inputStream) 189 190 ReceiveDataResponse.newBuilder().setData(ByteString.copyFrom(buf)).build() 191 } 192 } 193 } 194