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