diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Bit.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Bit.kt new file mode 100644 index 00000000..55a5bfd0 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/Bit.kt @@ -0,0 +1,24 @@ +package net.dankito.fints.tan + + +enum class Bit(val value: Int) { + + Low(0), + + High(1); + + + fun invert(): Bit { + if (this == High) { + return Low + } + + return High + } + + + override fun toString(): String { + return "$value" + } + +} \ No newline at end of file diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickerCanvas.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickerCanvas.kt new file mode 100644 index 00000000..a620be72 --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickerCanvas.kt @@ -0,0 +1,79 @@ +package net.dankito.fints.tan + +import org.slf4j.LoggerFactory + + +open class FlickerCanvas(var code: String) { + + companion object { + private val log = LoggerFactory.getLogger(FlickerCanvas::class.java) + } + + + var halfbyteid = 0 + var clock = Bit.High + var bitarray = mutableListOf>() + + fun reset() { + halfbyteid = 0 + clock = Bit.High + } + + init { + val bits = mutableMapOf>() + /* bitfield: clock, bits 2^1, 2^2, 2^3, 2^4 */ + bits['0'] = listOf(Bit.Low, Bit.Low, Bit.Low, Bit.Low, Bit.Low) + bits['1'] = listOf(Bit.Low, Bit.High, Bit.Low, Bit.Low, Bit.Low) + bits['2'] = listOf(Bit.Low, Bit.Low, Bit.High, Bit.Low, Bit.Low) + bits['3'] = listOf(Bit.Low, Bit.High, Bit.High, Bit.Low, Bit.Low) + bits['4'] = listOf(Bit.Low, Bit.Low, Bit.Low, Bit.High, Bit.Low) + bits['5'] = listOf(Bit.Low, Bit.High, Bit.Low, Bit.High, Bit.Low) + bits['6'] = listOf(Bit.Low, Bit.Low, Bit.High, Bit.High, Bit.Low) + bits['7'] = listOf(Bit.Low, Bit.High, Bit.High, Bit.High, Bit.Low) + bits['8'] = listOf(Bit.Low, Bit.Low, Bit.Low, Bit.Low, Bit.High) + bits['9'] = listOf(Bit.Low, Bit.High, Bit.Low, Bit.Low, Bit.High) + bits['A'] = listOf(Bit.Low, Bit.Low, Bit.High, Bit.Low, Bit.High) + bits['B'] = listOf(Bit.Low, Bit.High, Bit.High, Bit.Low, Bit.High) + bits['C'] = listOf(Bit.Low, Bit.Low, Bit.Low, Bit.High, Bit.High) + bits['D'] = listOf(Bit.Low, Bit.High, Bit.Low, Bit.High, Bit.High) + bits['E'] = listOf(Bit.Low, Bit.Low, Bit.High, Bit.High, Bit.High) + bits['F'] = listOf(Bit.Low, Bit.High, Bit.High, Bit.High, Bit.High) + + /* prepend synchronization identifier */ + code = "0FFF" + code + + for (i in 0 until code.length step 2) { + bits[code[i + 1]]?.let { bitarray.add(mutableListOf(*it.toTypedArray())) } + bits[code[i]]?.let { bitarray.add(mutableListOf(*it.toTypedArray())) } + } + + val steps = mutableListOf>() + + do { + steps.add(step()) + } while (halfbyteid > 0 || clock == Bit.Low) + + log.info("Steps:") + steps.forEach { step -> log.info(step.joinToString("")) } + } + + fun step(): Array { + bitarray[halfbyteid][0] = clock + + val stepBits = Array(5) { index -> bitarray[halfbyteid][index] } + + clock = clock.invert() + + if (clock == Bit.High) { + halfbyteid++ + + if (halfbyteid >= bitarray.size) { + halfbyteid = 0 + } + + } + + return stepBits + } + +} \ 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 new file mode 100644 index 00000000..7f86e8aa --- /dev/null +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/tan/FlickercodeDecoder.kt @@ -0,0 +1,96 @@ +package net.dankito.fints.tan + +import kotlin.math.floor + + +open class FlickercodeDecoder { + + open fun decodeChallenge(challenge: String): String { + var code = challenge.toUpperCase().replace ("[^a-fA-F0-9]", "") + + /* length check: first byte */ + val len = code.length / 2 - 1 + code = toHex(len, 2) + code.substring(2) + + /* luhn checksum */ + val luhndata = getPayload(code) + var luhnsum = 0 + var i = 0 + + while (i < luhndata.length) { + luhnsum += 1 * parseIntToHex(luhndata[i]) + quersumme(2 * parseIntToHex(luhndata[i + 1])) + i += 2 + } + + luhnsum = (10 - luhnsum % 10) % 10 + code = code.substring(0, code.length - 2) + toHex(luhnsum, 1) + code.substring(code.length - 1) + + /* xor checksum */ + var xorsum = 0 + i = 0 + while (i < code.length - 2) { + xorsum = xorsum xor parseIntToHex(code[i]) + i++ + } + + code = code.substring(0, code.length - 1) + toHex(xorsum, 1) + + return code + } + + open fun toHex(number: Int, minLength: Int): String { + var result = number.toString (16).toUpperCase() + + while (result.length < minLength) { + result = '0' + result + } + + return result + } + + fun quersumme(number: Int): Int { + var quersumme = 0 + var temp = number + + while (temp != 0) { + quersumme += temp % 10 + temp = floor(temp / 10.0).toInt() + } + + return quersumme + } + + fun getPayload(code: String): String { + var i = 0 + var payload = "" + + var len = parseIntToHex(code.substring(0, 2)) + i += 2 + + while (i < code.length-2) { + /* skip bcd identifier */ + i += 1 + /* parse length */ + len = parseIntToHex(code.substring(i, i + 1)) + i += 1 + // i = 4 + var endIndex = i + len * 2 + if (endIndex > code.length) { + endIndex = code.length + } + payload += code.substring(i, endIndex) + i += len * 2 + } + + return payload + } + + protected open fun parseIntToHex(char: Char): Int { + return parseIntToHex(char.toString()) + } + + protected open fun parseIntToHex(string: String): Int { + return Integer.parseInt(string, 16) + } + +} \ No newline at end of file