• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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  *      https://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 package com.android.devicediagnostics.bluetooth
17 
18 import android.bluetooth.BluetoothSocket
19 import android.util.Base64
20 import com.android.devicediagnostics.Protos.BluetoothPacket
21 import java.nio.ByteBuffer
22 import java.nio.ByteOrder
23 import kotlin.math.min
24 import org.json.JSONObject
25 
26 const val BT_READ_SIZE = 4096
27 const val BT_MAX_PACKET_SIZE = 1024 * 1024
28 const val BT_VERSION = 1
29 
30 private const val OLDEST_QRCODE_VERSION = 4
31 private const val QRCODE_VERSION = 5
32 
readFullynull33 private fun readFully(socket: BluetoothSocket, n: Int): ByteArray {
34     val b = ByteArray(n)
35     val stream = socket.inputStream
36 
37     var offset = 0
38     while (offset < n) {
39         val maxRead = min(n - offset, BT_READ_SIZE)
40         offset += stream.read(b, offset, maxRead)
41     }
42     return b
43 }
44 
readBluetoothPacketOrEofnull45 fun readBluetoothPacketOrEof(socket: BluetoothSocket): BluetoothPacket? {
46     val payloadSizeBytes = readFully(socket, 4)
47     val payloadSize = ByteBuffer.wrap(payloadSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt()
48     if (payloadSize == 0) {
49         return null
50     }
51     if (payloadSize > BT_MAX_PACKET_SIZE) {
52         throw Exception("Maximum packet size exceeded: $payloadSize")
53     }
54     val payloadBytes = readFully(socket, payloadSize)
55     return BluetoothPacket.parseFrom(payloadBytes)
56 }
57 
readBluetoothPacketnull58 fun readBluetoothPacket(socket: BluetoothSocket): BluetoothPacket {
59     val packet = readBluetoothPacketOrEof(socket)
60     if (packet == null) {
61         throw Exception("Expected packet, got EOF")
62     }
63     return packet
64 }
65 
writeBluetoothPacketnull66 fun writeBluetoothPacket(socket: BluetoothSocket, packetBuilder: BluetoothPacket.Builder) {
67     val packet = packetBuilder.setVersion(BT_VERSION).build()
68     val bytes = packet.toByteArray()
69     val header = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(bytes.size).array()
70     socket.outputStream.write(header)
71     socket.outputStream.write(bytes)
72 }
73 
74 class BluetoothConnectionData(
75     val psm: Int = 0,
76     val challenge: ByteArray,
77     val version: Int = QRCODE_VERSION,
78 ) {
79     companion object Helpers {
80         private const val JSON_PSM_KEY = "PSM"
81         private const val JSON_VERSION_KEY = "version"
82         private const val JSON_CHALLENGE_KEY = "challenge"
83 
fromJsonnull84         fun fromJson(json: String): BluetoothConnectionData {
85             val obj = JSONObject(json)
86             var version: Int = 0
87             if (obj.has(JSON_VERSION_KEY)) {
88                 version = obj.getInt(JSON_VERSION_KEY)
89             }
90             if (version < OLDEST_QRCODE_VERSION) {
91                 throw Exception("Unsupported version")
92             }
93             val psm = obj.getInt(JSON_PSM_KEY)
94             val challengeBase64 = obj.getString(JSON_CHALLENGE_KEY)
95             val challenge = Base64.decode(challengeBase64, Base64.URL_SAFE)
96             return BluetoothConnectionData(psm, challenge, version)
97         }
98     }
99 
toStringnull100     override fun toString(): String {
101         val challengeBase64 = Base64.encodeToString(challenge, Base64.URL_SAFE)
102         return JSONObject()
103             .put(JSON_PSM_KEY, psm)
104             .put(JSON_VERSION_KEY, version)
105             .put(JSON_CHALLENGE_KEY, challengeBase64)
106             .toString()
107     }
108 }
109