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