Implemented decoding HHD v1.3 codes (at least BCD encoded ones, no support for ASCII yet)
This commit is contained in:
parent
3c7d897d52
commit
826d3cefa2
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package net.dankito.fints.tan
|
||||
|
||||
|
||||
enum class FlickercodeEncoding {
|
||||
|
||||
BCD,
|
||||
|
||||
ASCII
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue