Implemented decoding HHD v1.3 codes (at least BCD encoded ones, no support for ASCII yet)

This commit is contained in:
dankl 2019-10-25 20:47:17 +02:00 committed by dankito
parent 3c7d897d52
commit 826d3cefa2
4 changed files with 113 additions and 2 deletions

View File

@ -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
)

View File

@ -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
}
}

View File

@ -0,0 +1,10 @@
package net.dankito.fints.tan
enum class FlickercodeEncoding {
BCD,
ASCII
}

View File

@ -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")
}
}