io.github.binaryfoo.decoders.TLVDecoder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of emv-bertlv Show documentation
Show all versions of emv-bertlv Show documentation
Some decoders for the data used in EMV credit card transactions.
package io.github.binaryfoo.decoders
import io.github.binaryfoo.DecodedData
import io.github.binaryfoo.Decoder
import io.github.binaryfoo.TagInfo
import io.github.binaryfoo.tlv.*
import java.util.*
import kotlin.collections.*
class TLVDecoder : Decoder {
override fun decode(input: String, startIndexInBytes: Int, session: DecodeSession): List {
try {
return decode(input, session, startIndexInBytes).second
} catch(e: TlvParseException) {
val errorMessage = e.message ?: e.javaClass.simpleName
if (session.tagRecognitionMode == CommonVendorErrorMode) {
if (!e.resultsSoFar.filter(::hasCommonVendorErrorTag).isEmpty()) {
try {
val (tlvs, decoded) = decode(input, session, startIndexInBytes, CommonVendorErrorMode)
val tagErrors = HashSet(tlvs.filter(::hasCommonVendorErrorTag).map { it.tag.hexString }).toList().sorted()
val warning = DecodedData(null, "Warning", "This result is a second attempt ignoring the spec for these (often abused) tags: $tagErrors. The first attempt (following the the spec) produced an error: $errorMessage", 0, 0, category = "parse-warning")
return decoded + warning
} catch(e: TlvParseException) {
}
}
}
val decoded = decodeTlvs(e.resultsSoFar, startIndexInBytes, session)
val error = DecodedData(null, "Error", errorMessage, decoded.lastOrNull()?.endIndex ?: 0, input.length / 2, category = "parse-error")
return decoded + error
}
}
private fun decode(input: String, session: DecodeSession, startIndexInBytes: Int, mode: TagRecognitionMode = CompliantTagMode): Pair, List> {
val tlvs = BerTlv.parseList(input.decodeAsHex(), true, mode)
val decoded = decodeTlvs(tlvs, startIndexInBytes, session)
return Pair(tlvs, decoded)
}
private fun decodeTlvs(list: List, startIndex: Int, session: DecodeSession): List {
var currentStartIndex = startIndex
val decodedItems = ArrayList()
for (tlv in list) {
val valueAsHexString = tlv.valueAsHexString
val tag = tlv.tag
val length = tlv.toBinary().size
val contentEndIndex = currentStartIndex + length
val compositeStartElementIndex = currentStartIndex + tlv.startIndexOfValue
val tagMetaData = session.tagMetaData!!
val decoded = if (tag.constructed) {
DecodedData.fromTlv(tlv, tagMetaData, valueAsHexString, currentStartIndex, contentEndIndex, decodeTlvs(tlv.getChildren(), compositeStartElementIndex, session))
} else {
val tagInfo = tagMetaData.get(tag)
DecodedData.fromTlv(tlv, tagMetaData, tagInfo.decodePrimitiveTlvValue(valueAsHexString), currentStartIndex, contentEndIndex, decodeOrBackDown(compositeStartElementIndex, tagInfo, valueAsHexString, session))
}
decodedItems.add(decoded)
currentStartIndex += length
}
return decodedItems
}
private fun decodeOrBackDown(compositeStartElementIndex: Int, tagInfo: TagInfo, valueAsHexString: String, session: DecodeSession): List {
try {
return tagInfo.decoder.decode(valueAsHexString, compositeStartElementIndex, session)
} catch(e: Exception) {
return listOf(DecodedData(null, "Error: Failed parsing " + valueAsHexString, e.message ?:e.javaClass.simpleName, compositeStartElementIndex, compositeStartElementIndex + valueAsHexString.length /2, category = "parse-error"))
}
}
override fun getMaxLength(): Int {
return 10000
}
override fun validate(input: String?): String? {
if (input == null || input.length < 2) {
return "Value must be at least 2 characters"
}
if (input.length % 2 != 0) {
return "Length must be a multiple of 2"
}
if (!ISOUtil.isValidHexString(input)) {
return "Value must contain only the characters 0-9 and A-F"
}
return null
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy