<lambda>null1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package com.github.google.bumble.btbench
16
17 import android.bluetooth.BluetoothSocket
18 import java.io.IOException
19 import java.nio.ByteBuffer
20 import java.util.logging.Logger
21 import kotlin.math.min
22
23 private val Log = Logger.getLogger("btbench.packet")
24
25 fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
26
27 abstract class Packet(val type: Int, val payload: ByteArray = ByteArray(0)) {
28 companion object {
29 const val RESET = 0
30 const val SEQUENCE = 1
31 const val ACK = 2
32
33 const val LAST_FLAG = 1
34
fromnull35 fun from(data: ByteArray): Packet {
36 return when (data[0].toInt()) {
37 RESET -> ResetPacket()
38 SEQUENCE -> SequencePacket(
39 data[1].toInt(),
40 ByteBuffer.wrap(data, 2, 4).getInt(),
41 data.sliceArray(6..<data.size)
42 )
43
44 ACK -> AckPacket(data[1].toInt(), ByteBuffer.wrap(data, 2, 4).getInt())
45 else -> GenericPacket(data[0].toInt(), data.sliceArray(1..<data.size))
46 }
47 }
48 }
49
toBytesnull50 open fun toBytes(): ByteArray {
51 return ByteBuffer.allocate(1 + payload.size).put(type.toByte()).put(payload).array()
52 }
53 }
54
55 class GenericPacket(type: Int, payload: ByteArray) : Packet(type, payload)
56 class ResetPacket : Packet(RESET)
57
58 class AckPacket(val flags: Int, val sequenceNumber: Int) : Packet(ACK) {
toBytesnull59 override fun toBytes(): ByteArray {
60 return ByteBuffer.allocate(1 + 1 + 4).put(type.toByte()).put(flags.toByte())
61 .putInt(sequenceNumber).array()
62 }
63 }
64
65 class SequencePacket(val flags: Int, val sequenceNumber: Int, payload: ByteArray) :
66 Packet(SEQUENCE, payload) {
toBytesnull67 override fun toBytes(): ByteArray {
68 return ByteBuffer.allocate(1 + 1 + 4 + payload.size).put(type.toByte()).put(flags.toByte())
69 .putInt(sequenceNumber).put(payload).array()
70 }
71 }
72
73 abstract class PacketSink {
onPacketnull74 fun onPacket(packet: Packet) {
75 when (packet) {
76 is ResetPacket -> onResetPacket()
77 is AckPacket -> onAckPacket()
78 is SequencePacket -> onSequencePacket(packet)
79 }
80 }
81
onResetPacketnull82 abstract fun onResetPacket()
83 abstract fun onAckPacket()
84 abstract fun onSequencePacket(packet: SequencePacket)
85 }
86
87 interface DataSink {
88 fun onData(data: ByteArray)
89 }
90
91 interface PacketIO {
92 var packetSink: PacketSink?
sendPacketnull93 fun sendPacket(packet: Packet)
94 }
95
96 class StreamedPacketIO(private val dataSink: DataSink) : PacketIO {
97 private var bytesNeeded: Int = 0
98 private var rxPacket: ByteBuffer? = null
99 private var rxHeader = ByteBuffer.allocate(2)
100
101 override var packetSink: PacketSink? = null
102
103 fun onData(data: ByteArray) {
104 var current = data
105 while (current.isNotEmpty()) {
106 if (bytesNeeded > 0) {
107 val chunk = current.sliceArray(0..<min(bytesNeeded, current.size))
108 rxPacket!!.put(chunk)
109 current = current.sliceArray(chunk.size..<current.size)
110 bytesNeeded -= chunk.size
111 if (bytesNeeded == 0) {
112 // Packet completed.
113 //Log.fine("packet complete: ${current.toHex()}")
114 packetSink?.onPacket(Packet.from(rxPacket!!.array()))
115
116 // Reset.
117 reset()
118 }
119 } else {
120 val headerBytesNeeded = 2 - rxHeader.position()
121 val headerBytes = current.sliceArray(0..<min(headerBytesNeeded, current.size))
122 current = current.sliceArray(headerBytes.size..<current.size)
123 rxHeader.put(headerBytes)
124 if (rxHeader.position() != 2) {
125 return
126 }
127 bytesNeeded = rxHeader.getShort(0).toInt()
128 if (bytesNeeded == 0) {
129 Log.warning("found 0 size packet!")
130 reset()
131 return
132 }
133 rxPacket = ByteBuffer.allocate(bytesNeeded)
134 }
135 }
136 }
137
138 private fun reset() {
139 rxPacket = null
140 rxHeader.position(0)
141 }
142
143 override fun sendPacket(packet: Packet) {
144 val packetBytes = packet.toBytes()
145 val packetData =
146 ByteBuffer.allocate(2 + packetBytes.size).putShort(packetBytes.size.toShort())
147 .put(packetBytes).array()
148 dataSink.onData(packetData)
149 }
150 }
151
152 class SocketDataSink(private val socket: BluetoothSocket) : DataSink {
onDatanull153 override fun onData(data: ByteArray) {
154 socket.outputStream.write(data)
155 }
156 }
157
158 class SocketDataSource(
159 private val socket: BluetoothSocket,
160 private val onData: (data: ByteArray) -> Unit
161 ) {
receivenull162 fun receive() {
163 val buffer = ByteArray(4096)
164 do {
165 try {
166 val bytesRead = socket.inputStream.read(buffer)
167 if (bytesRead <= 0) {
168 break
169 }
170 onData(buffer.sliceArray(0..<bytesRead))
171 } catch (error: IOException) {
172 Log.warning("IO Exception: $error")
173 break
174 }
175 } while (true)
176 Log.info("end of stream")
177 }
178 }