reactormonk
07/04/2022, 12:43 PMsuspendCoroutine
- but the code has some rather strict timing constraints, aka one code block should fully exit before another of the same kind should enter - how would I enforce that?
Code looks like this:
suspend fun BluetoothReader.transmitEscapeCommandS(input: ByteArray): CommandAPDU {
return suspendCoroutine { block ->
if (this.transmitEscapeCommand(input)) {
this.setOnEscapeResponseAvailableListener { _, response, errorCode ->
if (errorCode == 0) {
block.resume(CommandAPDU(response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnEscapeResponseAvailableListener(null)
}
} else {
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
The listener can only really be set once, so there's a bit of a race condition there.Joffrey
07/04/2022, 12:53 PMtransmitEscapeCommand()
? If yes, then why do you call this before setting the listener? It could theoretically fire the listener before you even registered it (even without any other concurrent calls to your function)transmitEscapeCommand
return false without firing the listener?reactormonk
07/04/2022, 12:56 PMtransmitEscapeCommand()
sends data to the hardware. Which then responds back via the set listener. A false
from transmitEscapeCommand()
means it couldn't be sent.Joffrey
07/04/2022, 12:57 PMtransmitEscapeCommand
, and then if it returns false
set the listener to null
before resuming with exceptionreactormonk
07/04/2022, 12:58 PMJoffrey
07/04/2022, 12:58 PMMutex
to prevent concurrent calls to your function from stepping on each other's toesreactormonk
07/04/2022, 12:59 PMval transmitEscapeCommandLock = Mutex()
suspend fun BluetoothReader.transmitEscapeCommandS(input: ByteArray): CommandAPDU {
return transmitEscapeCommandLock.withLock {
suspendCoroutine { block ->
this.setOnEscapeResponseAvailableListener { _, response, errorCode ->
if (errorCode == 0) {
block.resume(CommandAPDU(response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnEscapeResponseAvailableListener(null)
}
if (this.transmitEscapeCommand(input)) {
} else {
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
this.setOnEscapeResponseAvailableListener(null)
}
}
}
^ final codeJoffrey
07/04/2022, 1:01 PMif
this way:
val escapeSent = transmitEscapeCommand(input)
if (!escapeSent) {
setOnEscapeResponseAvailableListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
(set the listener to null first, by the way)reactormonk
07/04/2022, 1:01 PMsuspend fun BluetoothReader.transmitEscapeCommandS(input: ByteArray): CommandAPDU {
return transmitEscapeCommandLock.withLock {
suspendCoroutine { block ->
this.setOnEscapeResponseAvailableListener { _, response, errorCode ->
if (errorCode == 0) {
block.resume(CommandAPDU(response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnEscapeResponseAvailableListener(null)
}
if (this.transmitEscapeCommand(input)) {
Log.d("BluetoothSupport", "Sending command $input")
} else {
this.setOnEscapeResponseAvailableListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
Joffrey
07/04/2022, 1:05 PMtransmit
function. But that's again just my taste/style.
Also in Kotlin it's not necessary to use explicit this
, and not very common to do so.withContext
with a single-threaded dispatcher (or a dispatcher with limitedParallelism
) but that usually implies more context-switching and I would say it's not very desirable to force the dispatcher at such low levelreactormonk
07/04/2022, 1:05 PMthis
here because it's an extension function, feels kinda weird otherwise.Mutex
is the correct solution here IMO.Joffrey
07/04/2022, 1:08 PMsuspend fun BluetoothReader.transmitEscapeCommandS(...) = withContext(yourDispatcher) { .. }
, the callers won't be able to override the dispatcher. But that's not very good practice especially because of that - it makes it hard to test for instance.
But yeah, in any case, I agree that Mutex
is better, which is my initial point 🙂reactormonk
07/04/2022, 1:11 PMval enableNotificationSLock = Mutex()
suspend fun BluetoothReader.enableNotificationS(input: Boolean) {
return enableNotificationSLock.withLock {
suspendCoroutine { block ->
this.setOnEnableNotificationCompleteListener { _, errorCode ->
if (errorCode == 0) {
block.resume(Unit)
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnEnableNotificationCompleteListener(null)
}
val sent = this.enableNotification(input)
if (sent) {
Log.d("BluetoothSupport", "Sent notification request $input")
} else {
this.setOnEnableNotificationCompleteListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
val transmitApduSLock = Mutex()
suspend fun BluetoothReader.transmitApduS(input: ByteArray): CommandAPDU {
return transmitApduSLock.withLock {
suspendCoroutine { block ->
this.setOnResponseApduAvailableListener { _, response, errorCode ->
if (errorCode == 0) {
block.resume(CommandAPDU(response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnResponseApduAvailableListener(null)
}
val sent = this.transmitApdu(input)
if (sent) {
Log.d("BluetoothSupport", "Sent apdu $input")
} else {
this.setOnResponseApduAvailableListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
val transmitEscapeCommandLock = Mutex()
suspend fun BluetoothReader.transmitEscapeCommandS(input: ByteArray): CommandAPDU {
return transmitEscapeCommandLock.withLock {
suspendCoroutine { block ->
this.setOnEscapeResponseAvailableListener { _, response, errorCode ->
if (errorCode == 0) {
block.resume(CommandAPDU(response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnEscapeResponseAvailableListener(null)
}
val sent = this.transmitEscapeCommand(input)
if (sent) {
Log.d("BluetoothSupport", "Sent escape command $input")
} else {
this.setOnEscapeResponseAvailableListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
val getDeviceInfoLock = Mutex()
// TODO figure out the exact type of Any
suspend fun BluetoothReader.getDeviceInfoS(input: Int): Pair<Int, Any> {
return getDeviceInfoLock.withLock {
suspendCoroutine { block ->
this.setOnDeviceInfoAvailableListener { _, int, response, errorCode ->
if (errorCode == 0) {
block.resume(Pair(int, response))
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnDeviceInfoAvailableListener(null)
}
val sent = this.getDeviceInfo(input)
if (sent) {
Log.d("BluetoothSupport", "Sent device info request $input")
} else {
this.setOnDeviceInfoAvailableListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
//interface OnCardStatusChangeListener {
// fun onCardStatusChange(var1: BluetoothReader?, var2: Int)
//}
//
//interface OnCardStatusAvailableListener {
// fun onCardStatusAvailable(var1: BluetoothReader?, var2: Int, var3: Int)
//}
//
//interface OnCardPowerOffCompleteListener {
// fun onCardPowerOffComplete(var1: BluetoothReader?, var2: Int)
//}
val authenticateSLock = Mutex()
suspend fun BluetoothReader.authenticateS(input: ByteArray): Unit {
return authenticateSLock.withLock {
suspendCoroutine { block ->
this.setOnAuthenticationCompleteListener { _, errorCode ->
if (errorCode == 0) {
block.resume(Unit)
} else {
block.resumeWithException(ReaderCommandException("Error code from reader: $errorCode"))
}
this.setOnAuthenticationCompleteListener(null)
}
val sent = this.authenticate(input)
if (sent) {
Log.d("BluetoothSupport", "Sent auth request")
} else {
this.setOnAuthenticationCompleteListener(null)
block.resumeWithException(ReaderCommandException("Couldn't send command. Bonded?"))
}
}
}
}
//interface OnAtrAvailableListener {
// fun onAtrAvailable(var1: BluetoothReader?, var2: ByteArray?, var3: Int)
//}
^ this is the full trainwreck... I'm wondering if there's any reasonable way to condense some of the pasta.Joffrey
07/04/2022, 1:17 PMauthenticate()
happen for instance, how is the OnAuthenticationCompleteListener
(in the original API) supposed to know which call the result is from? Is there something in the first argument to allow that?reactormonk
07/04/2022, 1:18 PMIt's a bit funky.Very diplomatic of you.
there doesn't seem to be a way to track which message was the cause of which callback call.It's basically "the previous one on the timeline". According to the specs. Pretty sure that's gonna blow up somewhere
Joffrey
07/04/2022, 1:21 PMreactormonk
07/04/2022, 1:22 PM} catch (Exception e) {
if (!e.getMessage().contains("null")) {
return;
}
Joffrey
07/04/2022, 1:24 PMException
directly...reactormonk
07/04/2022, 1:28 PMJoffrey
07/04/2022, 1:28 PM-S
suffixes, and more importantly to avoid mistakes of calling the original API directly, you might want to wrap the BluetoothReader
into your own class. This also opens the door for things like registering a single listener of each type, and make them send to channels that the suspending wrappers will receive from. This way, you don't even have to use suspendCoroutine
, nor to keep registering/unregistering new callback instances all the time.reactormonk
07/04/2022, 1:30 PMJoffrey
07/04/2022, 1:31 PMreactormonk
07/04/2022, 1:32 PM