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 QRCODE_VERSION = 4
31
readFullynull32 private fun readFully(socket: BluetoothSocket, n: Int): ByteArray {
33 val b = ByteArray(n)
34 val stream = socket.inputStream
35
36 var offset = 0
37 while (offset < n) {
38 val maxRead = min(n - offset, BT_READ_SIZE)
39 offset += stream.read(b, offset, maxRead)
40 }
41 return b
42 }
43
readBluetoothPacketOrEofnull44 fun readBluetoothPacketOrEof(socket: BluetoothSocket): BluetoothPacket? {
45 val payloadSizeBytes = readFully(socket, 4)
46 val payloadSize = ByteBuffer.wrap(payloadSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt()
47 if (payloadSize == 0) {
48 return null
49 }
50 if (payloadSize > BT_MAX_PACKET_SIZE) {
51 throw Exception("Maximum packet size exceeded: $payloadSize")
52 }
53 val payloadBytes = readFully(socket, payloadSize)
54 return BluetoothPacket.parseFrom(payloadBytes)
55 }
56
readBluetoothPacketnull57 fun readBluetoothPacket(socket: BluetoothSocket): BluetoothPacket {
58 val packet = readBluetoothPacketOrEof(socket)
59 if (packet == null) {
60 throw Exception("Expected packet, got EOF")
61 }
62 return packet
63 }
64
writeBluetoothPacketnull65 fun writeBluetoothPacket(socket: BluetoothSocket, packetBuilder: BluetoothPacket.Builder) {
66 val packet = packetBuilder.setVersion(BT_VERSION).build()
67 val bytes = packet.toByteArray()
68 val header = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(bytes.size).array()
69 socket.outputStream.write(header)
70 socket.outputStream.write(bytes)
71 }
72
73 class BluetoothConnectionData(val psm: Int = 0, val challenge: ByteArray) {
74 companion object Helpers {
75 private const val JSON_PSM_KEY = "PSM"
76 private const val JSON_VERSION_KEY = "version"
77 private const val JSON_CHALLENGE_KEY = "challenge"
78
fromJsonnull79 fun fromJson(json: String): BluetoothConnectionData {
80 val obj = JSONObject(json)
81 var version: Int = 0
82 if (obj.has(JSON_VERSION_KEY)) {
83 version = obj.getInt(JSON_VERSION_KEY)
84 }
85 if (version < QRCODE_VERSION) {
86 throw Exception("Unsupported version")
87 }
88 val psm = obj.getInt(JSON_PSM_KEY)
89 val challengeBase64 = obj.getString(JSON_CHALLENGE_KEY)
90 val challenge = Base64.decode(challengeBase64, Base64.URL_SAFE)
91 return BluetoothConnectionData(psm, challenge)
92 }
93 }
94
toStringnull95 override fun toString(): String {
96 val challengeBase64 = Base64.encodeToString(challenge, Base64.URL_SAFE)
97 return JSONObject()
98 .put(JSON_PSM_KEY, psm)
99 .put(JSON_VERSION_KEY, QRCODE_VERSION)
100 .put(JSON_CHALLENGE_KEY, challengeBase64)
101 .toString()
102 }
103 }
104