diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Flickercode.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Flickercode.kt new file mode 100644 index 00000000..0401eeed --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Flickercode.kt @@ -0,0 +1,14 @@ +package net.dankito.fints.tan + + +open class Flickercode( + val challenge: String, + val challengeLength: Int, + val hasControlByte: Boolean, + val startCodeEncoding: FlickercodeEncoding, + val startCodeLength: Int, + val startCode: String, + val luhnChecksum: Int, + val xorChecksum: String, + val rendered: String +) \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeDecoder.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeDecoder.kt index 7f86e8aa..f5582327 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeDecoder.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeDecoder.kt @@ -5,9 +5,58 @@ import kotlin.math.floor open class FlickercodeDecoder { - open fun decodeChallenge(challenge: String): String { + open fun decodeChallenge(challenge: String): Flickercode { var code = challenge.toUpperCase().replace ("[^a-fA-F0-9]", "") + val challengeLength = parseIntToHex(challenge.substring(0, 2)) + + val startCodeLengthByte = parseIntToHex(challenge.substring(2, 4)) + val hasControlByte = isBitSet(startCodeLengthByte, 7) + val startCodeEncoding = if (isBitNotSet(startCodeLengthByte, 6)) FlickercodeEncoding.BCD else FlickercodeEncoding.ASCII + val startCodeLength = startCodeLengthByte and 0b00011111 // TODO: is this correct? + + val controlByte = "" // TODO (there can be multiple of them!) + + val startCodeStartIndex = if (hasControlByte) 6 else 4 + val startCodeEndIndex = startCodeStartIndex + startCodeLength + val startCode = code.substring(startCodeStartIndex, startCodeEndIndex) + + val de1 = "" // TODO + val de2 = "" // TODO + val de3 = "" // TODO + + var luhnData = controlByte + startCode + de1 + de2 + de3 + if (luhnData.length % 2 != 0) { + luhnData = luhnData + "F" // for Luhn checksum it's required to have full bytes // TODO: should be incorrect. E.g. controlByte has to be checked / stuffed to full byte + } + + val luhnSum = luhnData.mapIndexed { index, char -> + val asNumber = char.toString().toInt() + + if (index % 2 == 1) { + val doubled = asNumber * 2 + return@mapIndexed (doubled / 10) + (doubled % 10) + } + + asNumber + }.sum() + + val luhnChecksum = 10 - (luhnSum % 10) + + val countStartCodeBytes = startCodeLength / 2 + val dataWithoutLengthAndChecksum = toHex(countStartCodeBytes, 2) + controlByte + startCode + de1 + de2 + de3 // TODO add length of de1-3 (for controlByte as well?) + val dataLength = (dataWithoutLengthAndChecksum.length + 2) / 2 // + 2 for checksum + val dataWithoutChecksum = toHex(dataLength, 2) + dataWithoutLengthAndChecksum + val xorByteData = dataWithoutChecksum.map { parseIntToHex(it) } + + var xorChecksum = 0 + xorByteData.forEach { xorChecksum = xorChecksum xor it } + + val xorChecksumString = toHex(xorChecksum, 1) + + val rendered = dataWithoutChecksum + luhnChecksum + xorChecksumString + + /* length check: first byte */ val len = code.length / 2 - 1 code = toHex(len, 2) + code.substring(2) @@ -35,7 +84,7 @@ open class FlickercodeDecoder { code = code.substring(0, code.length - 1) + toHex(xorsum, 1) - return code + return Flickercode(challenge, challengeLength, hasControlByte, startCodeEncoding, startCodeLength, startCode, luhnChecksum, toHex(xorChecksum, 1), rendered) } open fun toHex(number: Int, minLength: Int): String { @@ -93,4 +142,12 @@ open class FlickercodeDecoder { return Integer.parseInt(string, 16) } + protected open fun isBitSet(num: Int, bit: Int): Boolean { + return num and (1 shl bit) != 0 + } + + protected open fun isBitNotSet(num: Int, bit: Int): Boolean { + return num and (1 shl bit) == 0 + } + } \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeEncoding.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeEncoding.kt new file mode 100644 index 00000000..6ad84383 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeEncoding.kt @@ -0,0 +1,10 @@ +package net.dankito.fints.tan + + +enum class FlickercodeEncoding { + + BCD, + + ASCII + +} \ No newline at end of file diff --git a/fints4javaLib/src/test/kotlin/net/dankito/fints/tan/FlickercodeDecoderTest.kt b/fints4javaLib/src/test/kotlin/net/dankito/fints/tan/FlickercodeDecoderTest.kt new file mode 100644 index 00000000..2fcaf342 --- /dev/null +++ b/fints4javaLib/src/test/kotlin/net/dankito/fints/tan/FlickercodeDecoderTest.kt @@ -0,0 +1,30 @@ +package net.dankito.fints.tan + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + + +class FlickercodeDecoderTest { + + private val underTest = FlickercodeDecoder() + + + @Test + fun decodeChallenge_ExampleFromChapter_C_4_1_BCD_without_ControlByte() { + + // given + val challenge = "070A2082901998" + + + // when + val response = underTest.decodeChallenge(challenge) + + + // then + assertThat(response.startCode).isEqualTo("2082901998") + assertThat(response.luhnChecksum).isEqualTo(1) + assertThat(response.xorChecksum).isEqualTo("A") + assertThat(response.rendered).isEqualTo("070520829019981A") + } + +} \ No newline at end of file