Implemented catching Flickercode decoding errors and displaying them in UI

This commit is contained in:
dankl 2020-01-02 14:05:43 +01:00 committed by dankito
parent b3f815eb7e
commit 9869b44ccd
4 changed files with 65 additions and 27 deletions

View File

@ -26,7 +26,6 @@ import net.dankito.banking.ui.model.TanMediumStatus
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.* import net.dankito.fints.model.*
import net.dankito.fints.response.client.FinTsClientResponse import net.dankito.fints.response.client.FinTsClientResponse
import net.dankito.fints.tan.TanImage
open class EnterTanDialog : DialogFragment() { open class EnterTanDialog : DialogFragment() {
@ -136,7 +135,14 @@ open class EnterTanDialog : DialogFragment() {
if (tanChallenge is FlickercodeTanChallenge) { if (tanChallenge is FlickercodeTanChallenge) {
val flickerCodeView = rootView.flickerCodeView val flickerCodeView = rootView.flickerCodeView
flickerCodeView.visibility = View.VISIBLE flickerCodeView.visibility = View.VISIBLE
flickerCodeView.setCode((tanChallenge as FlickercodeTanChallenge).flickercode)
val flickercode = (tanChallenge as FlickercodeTanChallenge).flickercode
if (flickercode.decodingSuccessful) {
flickerCodeView.setCode(flickercode)
}
else {
showDecodingTanChallengeFailedErrorDelayed(flickercode.error)
}
} }
else if (tanChallenge is ImageTanChallenge) { else if (tanChallenge is ImageTanChallenge) {
rootView.tanImageView.visibility = View.VISIBLE rootView.tanImageView.visibility = View.VISIBLE
@ -147,7 +153,7 @@ open class EnterTanDialog : DialogFragment() {
rootView.imgTanImageView.setImageBitmap(bitmap) rootView.imgTanImageView.setImageBitmap(bitmap)
} }
else { else {
showDecodingTanImageFailedErrorDelayed(decodedImage) // this method gets called right on start up before dialog is shown -> Alert would get displayed before dialog and therefore covered by dialog if we don't delay displaying dialog showDecodingTanChallengeFailedErrorDelayed(decodedImage.error)
} }
} }
@ -156,16 +162,20 @@ open class EnterTanDialog : DialogFragment() {
} }
protected open fun showDecodingTanImageFailedErrorDelayed(decodedImage: TanImage) { /**
* This method gets called right on start up before dialog is shown -> Alert would get displayed before dialog and
* therefore covered by dialog -> delay displaying alert.
*/
protected open fun showDecodingTanChallengeFailedErrorDelayed(error: Exception?) {
val handler = Handler() val handler = Handler()
handler.postDelayed({ showDecodingTanImageFailedError(decodedImage) }, 500) handler.postDelayed({ showDecodingTanChallengeFailedError(error) }, 500)
} }
protected open fun showDecodingTanImageFailedError(decodedImage: TanImage) { protected open fun showDecodingTanChallengeFailedError(error: Exception?) {
activity?.let { context -> activity?.let { context ->
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setMessage(context.getString(R.string.dialog_enter_tan_error_could_not_decode_tan_image, decodedImage.error?.localizedMessage)) .setMessage(context.getString(R.string.dialog_enter_tan_error_could_not_decode_tan_image, error?.localizedMessage))
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show() .show()
} }

View File

@ -3,5 +3,20 @@ package net.dankito.fints.tan
open class Flickercode( open class Flickercode(
val challengeHHD_UC: String, val challengeHHD_UC: String,
val parsedDataSet: String val parsedDataSet: String,
) val error: Exception? = null
) {
val decodingSuccessful: Boolean
get() = error == null
override fun toString(): String {
if (decodingSuccessful == false) {
return "Decoding error: $error"
}
return "Parsed $challengeHHD_UC to $parsedDataSet"
}
}

View File

@ -1,5 +1,6 @@
package net.dankito.fints.tan package net.dankito.fints.tan
import org.slf4j.LoggerFactory
import java.util.regex.Pattern import java.util.regex.Pattern
@ -7,36 +8,44 @@ open class FlickercodeDecoder {
companion object { companion object {
val ContainsOtherSymbolsThanFiguresPattern: Pattern = Pattern.compile("\\D") val ContainsOtherSymbolsThanFiguresPattern: Pattern = Pattern.compile("\\D")
private val log = LoggerFactory.getLogger(FlickercodeDecoder::class.java)
} }
open fun decodeChallenge(challengeHHD_UC: String): Flickercode { open fun decodeChallenge(challengeHHD_UC: String): Flickercode {
val challengeLength = parseIntToHex(challengeHHD_UC.substring(0, 2)) try {
val challengeLength = parseIntToHex(challengeHHD_UC.substring(0, 2))
val startCode = parseStartCode(challengeHHD_UC, 2) val startCode = parseStartCode(challengeHHD_UC, 2)
val controlByte = "" // TODO (there can be multiple of them!) val controlByte = "" // TODO (there can be multiple of them!)
val de1 = parseDatenelement(challengeHHD_UC, startCode.endIndex) val de1 = parseDatenelement(challengeHHD_UC, startCode.endIndex)
val de2 = parseDatenelement(challengeHHD_UC, de1.endIndex) val de2 = parseDatenelement(challengeHHD_UC, de1.endIndex)
val de3 = parseDatenelement(challengeHHD_UC, de2.endIndex) val de3 = parseDatenelement(challengeHHD_UC, de2.endIndex)
val luhnChecksum = calculateLuhnChecksum(startCode, controlByte, de1, de2, de3) val luhnChecksum = calculateLuhnChecksum(startCode, controlByte, de1, de2, de3)
// TODO: // TODO:
// können im HHDUC-Protokoll Datenelemente ausgelassen werden, indem als Länge LDE1, LDE2 oder LDE3 = 00 angegeben wird. // können im HHDUC-Protokoll Datenelemente ausgelassen werden, indem als Länge LDE1, LDE2 oder LDE3 = 00 angegeben wird.
// Dadurch wird gekennzeichnet, dass das jeweilige, durch den Start-Code definierte Datenelement nicht im HHDUC-Datenstrom // Dadurch wird gekennzeichnet, dass das jeweilige, durch den Start-Code definierte Datenelement nicht im HHDUC-Datenstrom
// enthalten ist. Somit sind für leere Datenelemente die Längenfelder zu übertragen, wenn danach noch nicht-leere // enthalten ist. Somit sind für leere Datenelemente die Längenfelder zu übertragen, wenn danach noch nicht-leere
// Datenelemente folgen. Leere Datenelemente am Ende des Datenstromes können komplett inklusive Längenfeld entfallen. // Datenelemente folgen. Leere Datenelemente am Ende des Datenstromes können komplett inklusive Längenfeld entfallen.
val dataWithoutLengthAndChecksum = startCode.lengthInByte + controlByte + startCode + de1.lengthInByte + de1.data + de2.lengthInByte + de2.data + de3.lengthInByte + de3.data val dataWithoutLengthAndChecksum = startCode.lengthInByte + controlByte + startCode + de1.lengthInByte + de1.data + de2.lengthInByte + de2.data + de3.lengthInByte + de3.data
val dataLength = (dataWithoutLengthAndChecksum.length + 2) / 2 // + 2 for checksum val dataLength = (dataWithoutLengthAndChecksum.length + 2) / 2 // + 2 for checksum
val dataWithoutChecksum = toHex(dataLength, 2) + dataWithoutLengthAndChecksum val dataWithoutChecksum = toHex(dataLength, 2) + dataWithoutLengthAndChecksum
val xorChecksumString = calculateXorChecksum(dataWithoutChecksum) val xorChecksumString = calculateXorChecksum(dataWithoutChecksum)
val parsedDataSet = dataWithoutChecksum + luhnChecksum + xorChecksumString val parsedDataSet = dataWithoutChecksum + luhnChecksum + xorChecksumString
return Flickercode(challengeHHD_UC, parsedDataSet) return Flickercode(challengeHHD_UC, parsedDataSet)
} catch (e: Exception) {
log.error("Could not decode challenge $challengeHHD_UC")
return Flickercode(challengeHHD_UC, "", e)
}
} }
protected fun parseStartCode(challengeHHD_UC: String, startIndex: Int): FlickercodeDatenelement { protected fun parseStartCode(challengeHHD_UC: String, startIndex: Int): FlickercodeDatenelement {

View File

@ -12,6 +12,10 @@ open class TanImage(
override fun toString(): String { override fun toString(): String {
if (decodingSuccessful == false) {
return "Decoding error: $error"
}
return "$mimeType ${imageBytes.size} bytes" return "$mimeType ${imageBytes.size} bytes"
} }