Skip to content

Commit

Permalink
Split out BclMultiplexer
Browse files Browse the repository at this point in the history
  • Loading branch information
hufman committed Nov 3, 2024
1 parent a5acb70 commit 064720f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 113 deletions.
6 changes: 3 additions & 3 deletions app/src/main/java/io/bimmergestalt/bcl/BclOutputStream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import java.io.OutputStream

class BclOutputStream(private val src: Short, private val dest: Short, private val output: BclPacketSender): OutputStream() {
override fun write(b: Int) {
output.write(BclPacket.Specialized.Data(src, dest, ByteArray(b)))
output.writePacket(BclPacket.Specialized.Data(src, dest, ByteArray(b)))
}

override fun write(b: ByteArray?) {
b ?: return
output.write(BclPacket.Specialized.Data(src, dest, b))
output.writePacket(BclPacket.Specialized.Data(src, dest, b))
}

override fun write(b: ByteArray?, off: Int, len: Int) {
Expand All @@ -22,6 +22,6 @@ class BclOutputStream(private val src: Short, private val dest: Short, private v
}

override fun close() {
output.write(BclPacket.Specialized.Close(src, dest))
output.writePacket(BclPacket.Specialized.Close(src, dest))
}
}
8 changes: 5 additions & 3 deletions app/src/main/java/io/bimmergestalt/bcl/BclPacketSender.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package io.bimmergestalt.bcl
import org.tinylog.Logger
import java.io.OutputStream

class BclPacketSender(private val output: OutputStream) {
@Suppress("BlockingMethodInNonBlockingContext") // Why does linter think this context is nonblocking
fun write(packet: BclPacket) {
interface BclPacketSender {
fun writePacket(packet: BclPacket)
}
class BclPacketSenderConcrete(private val output: OutputStream): BclPacketSender {
override fun writePacket(packet: BclPacket) {
if (packet.dest == 5001.toShort()) {
Logger.debug {"Sending $packet"}
}
Expand Down
18 changes: 13 additions & 5 deletions app/src/main/java/io/bimmergestalt/bcl/android/BtConnection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.bluetooth.BluetoothSocket
import io.bimmergestalt.bcl.client.BclClientTransport
import io.bimmergestalt.bcl.ConnectionState
import io.bimmergestalt.bcl.MutableConnectionState
import io.bimmergestalt.bcl.multiplex.BclMultiplexer
import io.bimmergestalt.bcl.protocols.DestProtocolFactory
import org.tinylog.kotlin.Logger
import java.io.IOException
Expand All @@ -26,6 +27,8 @@ class BtConnection(val device: BluetoothDevice, val connectionState: MutableConn
private var socket: BluetoothSocket? = null
var bclConnection: BclClientTransport? = null
private set
var bclMultiplexer: BclMultiplexer? = null
private set
val isConnected: Boolean
get() = bclConnection?.isConnected == true

Expand Down Expand Up @@ -69,16 +72,21 @@ class BtConnection(val device: BluetoothDevice, val connectionState: MutableConn
private fun connectBcl(socket: BluetoothSocket) {
while (socket.isConnected) {
try {
connectionState.transportState = ConnectionState.TransportState.ACTIVE
val bclConnection = BclClientTransport(socket.inputStream, socket.outputStream, connectionState, destProtocolFactories)
this.bclConnection = bclConnection
bclConnection.connect()
bclConnection.run()
connectionState.transportState = ConnectionState.TransportState.ACTIVE
val bclConnection = BclClientTransport(socket.inputStream, socket.outputStream, connectionState)
this.bclConnection = bclConnection
bclConnection.connect()
val bclMultiplexer = BclMultiplexer(bclConnection, destProtocolFactories)
this.bclMultiplexer = bclMultiplexer
bclMultiplexer.openProtocols()
bclMultiplexer.run()
} catch (_: SecurityException) {
} catch (e: IOException) {
Logger.warn(e) { "IOException communicating BCL" }
} catch (_: InterruptedException) {
} finally {
bclMultiplexer?.shutdown()
bclMultiplexer = null
bclConnection?.shutdown()
bclConnection = null
}
Expand Down
127 changes: 25 additions & 102 deletions app/src/main/java/io/bimmergestalt/bcl/client/BclClientTransport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ import java.io.OutputStream
/**
* Runs the BCL protocol over the given socket streams
*/
class BclClientTransport(input: InputStream, output: OutputStream, val connectionState: MutableConnectionState,
val destProtocolFactories: Iterable<DestProtocolFactory>) {
class BclClientTransport(input: InputStream, output: OutputStream, val connectionState: MutableConnectionState): BclPacketSender {
companion object {
private const val SESSION_INIT_WAIT = 1000L
}

@Suppress("UnstableApiUsage")
private val input = CountingInputStream(input)
private val output = CountingOutputStream(output)
private val packetOutput = BclPacketSender(output)
private val packetOutput = BclPacketSenderConcrete(output)

@Suppress("UnstableApiUsage")
val bytesRead: Long
get() = input.count
val bytesWritten: Long
get() = output.count
var openConnectionCount: Int = 0

var running = true
var state: ConnectionState.BclState
private set(value) { connectionState.bclState = value }
get() = connectionState.bclState
Expand All @@ -40,16 +40,12 @@ class BclClientTransport(input: InputStream, output: OutputStream, val connectio
val instanceId: Int
get() = connectionHandshake?.instanceId?.toInt() ?: -1

val destProtocols: MutableList<Protocol> = ArrayList()
val openConnections: MutableMap<Pair<Short, Short>, ProxyClientConnection> = HashMap()

@Throws(IOException::class)
fun connect() {
state = ConnectionState.BclState.OPENING
waitForHandshake()
selectProtocol()
state = ConnectionState.BclState.ACTIVE
openProtocols()
}

private fun waitForHandshake() {
Expand Down Expand Up @@ -86,14 +82,14 @@ class BclClientTransport(input: InputStream, output: OutputStream, val connectio
val version = connectionHandshake.version.toByte()
if (version > 3) {
// hardcoded to version 3, version 4 has an unknown watchdog behavior
packetOutput.write(BclPacket.Specialized.SelectProto(3.toShort()))
packetOutput.writePacket(BclPacket.Specialized.SelectProto(3.toShort()))
doKnock()
}
}

private fun doKnock() {
// i think empty values work fine here
packetOutput.write(BclPacket.Specialized.Knock(
packetOutput.writePacket(BclPacket.Specialized.Knock(
ByteArray(0), ByteArray(0),
ByteArray(0), ByteArray(0),
0 /*A4A*/, 1
Expand All @@ -102,95 +98,30 @@ class BclClientTransport(input: InputStream, output: OutputStream, val connectio
// otherwise 1 /*TouchCommand*/ and 7
}

private fun openProtocols() {
destProtocols.add(WatchdogProtocol.Factory().onConnect(
this,
BclClientConnectionOpener()
))
destProtocolFactories.forEach { factory ->
try {
destProtocols.add(factory.onConnect(this, BclClientConnectionOpener()))
} catch (e: Exception) {
shutdown()
throw IOException("Failed to initialize protocol $factory")
}
}
}

inner class BclClientConnectionOpener: ProxyConnectionOpener {
override fun openConnection(
srcPort: Short,
destPort: Short,
client: OutputStream
): ProxyClientConnection {
val connection = synchronized(openConnections) {
val key = Pair(srcPort, destPort)
Logger.info {"Opening BCL Connection $srcPort:$destPort"}
if (openConnections.containsKey(key)) {
throw IllegalArgumentException("Duplicate to/from connection")
}
val connection = ProxyClientConnection(
srcPort,
destPort,
client,
BclOutputStream(srcPort, destPort, packetOutput)
) {
closeConnection(it)
}

openConnections[key] = connection
connection
}
packetOutput.write(BclPacket.Specialized.Open(srcPort, destPort))
return connection
}

fun closeConnection(connection: ProxyClientConnection) {
Logger.info {"Closing BCL Connection ${connection.srcPort}:${connection.destPort}"}
synchronized(openConnections) { openConnections.remove(connection.key) }
}
}

fun run() {
while (running) {
readPacket()
}
}

private fun readPacket() {
fun readPacket(): BclPacket? {
val packet = BclPacket.readFrom(input)
packetOutput.write(BclPacket.Specialized.DataAck(8 + packet.data.size))
packetOutput.writePacket(BclPacket.Specialized.DataAck(8 + packet.data.size))

if (packet.dest == 5001.toShort() || (packet.command != BclPacket.COMMAND.DATA && packet.command != BclPacket.COMMAND.DATAACK)) {
Logger.info {"Received support packet $packet"}
}
if (packet.command == BclPacket.COMMAND.DATA) {
val key = Pair(packet.src, packet.dest)
val connection = synchronized(openConnections) { openConnections[key] }
if (connection == null) {
// manually send, which normally the connection.close() handles
packetOutput.write(BclPacket.Specialized.Close(packet.src, packet.dest))
}
try {
connection?.toClient?.write(packet.data)
} catch (e: IOException) {
synchronized(openConnections) { openConnections.remove(key)?.close() }
}
}
else if (packet.command == BclPacket.COMMAND.CLOSE) {
val key = Pair(packet.src, packet.dest)
val connection = synchronized(openConnections) { openConnections[key] }
connection?.toClient?.close()
}
else if (packet.command == BclPacket.COMMAND.HANGUP) {
shutdown()
when (packet.command) {
BclPacket.COMMAND.DATA -> return packet
BclPacket.COMMAND.CLOSE -> return packet
BclPacket.COMMAND.HANGUP -> shutdown()
else -> Logger.warn {"Received unhandled packet $packet"}
}
return null
}

override fun writePacket(packet: BclPacket) {
packetOutput.writePacket(packet)
}

fun getReport() = BclConnectionReport(
startupTimestamp,
bytesRead, bytesWritten,
openConnections.size,
openConnectionCount,
instanceId.toShort(),
0,
connectionHandshake?.bufferSize ?: -1,
Expand All @@ -199,20 +130,12 @@ class BclClientTransport(input: InputStream, output: OutputStream, val connectio
)

fun shutdown() {
if (running) {
running = false
try {
destProtocols.forEach { protocol ->
protocol.shutdown()
}
openConnections.values.toMutableList().forEach {
it.close()
}
packetOutput.write(BclPacket(BclPacket.COMMAND.HANGUP, 0, 0, ByteArray(0)))
output.flush()
} finally {
state = ConnectionState.BclState.SHUTDOWN
}
try {
packetOutput.writePacket(BclPacket(BclPacket.COMMAND.HANGUP, 0, 0, ByteArray(0)))
output.flush()
state = ConnectionState.BclState.SHUTDOWN
} catch (e: Exception) {
Logger.warn(e) {"Error while shutting down BCL"}
}
}
}
Loading

0 comments on commit 064720f

Please sign in to comment.