org.kapott.hbci.manager.FlickerCode Maven / Gradle / Ivy
Show all versions of hbci4j-adorsys Show documentation
/**********************************************************************
* $Source: /cvsroot/hibiscus/hbci4java/src/org/kapott/hbci/manager/FlickerCode.java,v $
* $Revision: 1.9 $
* $Date: 2011/06/24 16:53:23 $
* $Author: willuhn $
*
* Copyright (c) by willuhn - software & services
* All rights reserved
*
**********************************************************************/
package org.kapott.hbci.manager;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
/**
* Implementierung des Flicker-Codes fuer optisches ChipTAN.
* Basiert auf der Javascript-Implementierung von
* http://6xq.net/media/00/20/flickercode.html
*
* Die Javascript-Implementierung war jedoch nicht mehr aktuell (basiert auf HHD 1.3).
*/
@Slf4j
public class FlickerCode {
/**
* Die Anzahl der Bytes, in der die Laenge des Challenge bei HHD 1.4 steht.
* Bei HHD 1.3 war das noch 2 Zeichen lang.
* Wenn der Flicker-Code nicht in "Challenge HHDuc" uebertragen wurde
* sondern direkt im Freitext-Challenge, koennen wir das Problem umgehen,
* indem wir in clean() einfach eine "0" vorn anhaengen.
* Wenn er aber tatsaechlich im "Challenge HHDuc" steht, kann man dem
* Code nicht ansehen, ob es ein HHD 1.3-Code ist. In dem Fall hilft nur
* Try&Error. Also mit HHD 1.4 parsen. Und wenn das fehlschlaegt, dann
* HHD 1.3 versuchen.
*/
private final static int LC_LENGTH_HHD14 = 3;
/**
* Die Anzahl der Bytes, in der die Laenge des Challenge bei HHD 1.3 steht.
*/
private final static int LC_LENGTH_HHD13 = 2;
/**
* Default-Laenge der LDE-Laengen-Angabe.
*/
private final static int LDE_LENGTH_DEFAULT = 2;
/**
* Fallback-Laenge der LDE-Laengen-Angabe bei der Sparda.
*/
private final static int LDE_LENGTH_SPARDA = 3;
/**
* Die Position des Bits, welches das Encoding enthaelt.
*/
private final static int BIT_ENCODING = 6;
/**
* Die Position des Bits, welches festlegt, ob ein Controlbyte folgt.
*/
private final static int BIT_CONTROLBYTE = 7;
/**
* Die HHD-Version.
*/
public HHDVersion version = null;
/**
* Laenge des gesamten Codes.
*/
public int lc = 0;
/**
* Der Startcode.
*/
public Startcode startCode = new Startcode();
/**
* Datenelement 1.
*/
public DE de1 = new DE();
/**
* Datenelement 2.
*/
public DE de2 = new DE();
/**
* Datenelement 3.
*/
public DE de3 = new DE();
/**
* Der Rest des Codes. Mit dem koennen wir nichts anfangen
*/
public String rest = null;
/**
* ct.
* Parameterloser Konstruktor zum manuellen Zusammenstecken eines Codes.
*/
public FlickerCode() {
}
/**
* ct.
* Parst den HHDuc-Code aus dem uebergebenen Code.
*
* @param code der zu parsende Code.
*/
public FlickerCode(String code) {
// Wir versuchen es erstmal als HHD 1.4
try {
try {
parse(code, HHDVersion.HHD14);
} catch (Exception e) {
// Wir versuchen den Sparda-Workaround mit 3 Zeichen langem LDE
parse(code, HHDVersion.HHD14, LDE_LENGTH_SPARDA);
}
} catch (Exception e) {
// OK, dann HHD 1.3
parse(code, HHDVersion.HHD13);
}
}
/**
* Versucht, aus Challenge und Challenge HHDuc den Flicker-Code zu extrahieren
* und ihn in einen flickerfaehigen Code umzuwandeln.
* Nur wenn tatsaechlich ein gueltiger Code enthalten ist, der als
* HHDuc-Code geparst und in einen Flicker-Code umgewandelt werden konnte,
* liefert die Funktion den Code. Sonst immer NULL.
*
* @param challenge der Challenge-Text. Das DE "Challenge HHDuc" gibt es
* erst seit HITAN4. Einige Banken haben aber schon vorher optisches chipTAN
* gemacht. Die haben das HHDuc dann direkt im Freitext des Challenge
* mitgeschickt (mit String-Tokens zum Extrahieren markiert). Die werden vom
* FlickerCode-Parser auch unterstuetzt.
* @param hhduc das echte Challenge HHDuc.
* @return der geparste Flickercode oder NULL.
*/
public static FlickerCode tryParse(String challenge, String hhduc) {
// 1. Prioritaet hat hhduc. Gibts aber erst seit HITAN4
if (hhduc != null && hhduc.trim().length() > 0) {
try {
FlickerCode code = new FlickerCode(hhduc);
code.render(); // testweise rendern
return code;
} catch (Exception e) {
log.debug("unable to parse Challenge HHDuc " + hhduc + ":" + HBCIUtils.exception2String(e));
}
}
// 2. Checken, ob im Freitext-Challenge was parse-faehiges steht.
// Kann seit HITAN1 auftreten
if (challenge != null && challenge.trim().length() > 0) {
try {
FlickerCode code = new FlickerCode(challenge);
code.render(); // testweise rendern
return code;
} catch (Exception e) {
// Das darf durchaus vorkommen, weil das Challenge auch bei manuellem
// chipTAN- und smsTAN Verfahren verwendet wird, wo gar kein Flicker-Code enthalten ist.
// Wir loggen es aber trotzdem - fuer den Fall, dass tatsaechlich ein Flicker-Code
// enthalten ist. Sonst koennen wir das nicht debuggen.
log.debug("challenge contains no HHDuc (no problem in most cases):" + HBCIUtils.exception2String(e));
}
}
// Ne, definitiv kein Flicker-Code.
return null;
}
/**
* Wandelt die Zahl in Hex-Schreibweise um und fuellt links mit Nullen auf, bis die Laenge "len" erreicht ist.
*
* @param n die Zahl.
* @param len die zu erreichende Laenge.
* @return die links mit Nullen aufgefuellte Zahl in HEX-Schreibweise.
*/
private static String toHex(int n, int len) {
String s = Integer.toString(n, 16).toUpperCase();
while (s.length() < len)
s = "0" + s;
return s;
}
/**
* Wandelt alle Zeichen des String gemaess des jeweiligen ASCII-Wertes in HEX-Codierung um.
* Beispiel: Das Zeichen "0" hat den ASCII-Wert "30" in Hexadezimal-Schreibweise.
*
* @param s der umzuwandelnde String.
* @return der codierte String.
*/
private static String toHex(String s) {
StringBuffer sb = new StringBuffer();
char[] chars = s.toCharArray();
for (char c : chars) {
sb.append(toHex(c, 2));
}
return sb.toString();
}
/**
* Berechnet die Quersumme.
*
* @param n die Zahl, deren Quersumme errechnet werden soll.
* @return die Quersumme.
*/
private static int quersumme(int n) {
int q = 0;
while (n != 0) {
q += n % 10;
n = (int) Math.floor(n / 10);
}
return q;
}
/**
* Liefert die Summe der Bit-Wertigkeiten fuer die genannten Bits
* (beginndend bei 0 und beim kleinsten Bit, angegebens inclusive).
*
* Beispiel:
* num = 156 (-> 10011100)
* bits = 5
*
* Es wird die Summe der Bitwertigkeiten 2^0 bis 2^5 errechnet.
* Also der Wert von **011100 = 2^4+2^3+s^2 = 28
*
* @param num Zahl, aus der die Summe berechnet werden soll.
* @param bits Anzahl der Bits (beginnend bei 0 und beim kleinsten Bit, angegebenes inclusive), deren Wertigkeit
* addiert werden soll.
* @return der errechnete Wert.
*/
private static int getBitSum(int num, int bits) {
int sum = 0;
for (int i = 0; i <= bits; ++i)
sum += (num & (1 << i));
return sum;
}
/**
* Prueft, ob in der genannten Zahl das angegebene Bit gesetzt ist.
*
* @param num die zu pruefende Zahl.
* @param bit die Nummer des zu pruefenden Bits.
* Wobei "0" das kleinste (rechts) und "7" das groesste (links) Bit ist.
* @return true, wenn das Bit gesetzt ist.
*/
private static boolean isBitSet(int num, int bit) {
return (num & (1 << bit)) != 0;
}
/**
* Parst den Code mit der angegebenen HHD-Version.
*
* @param code der zu parsende Code.
* @param version die HHD-Version.
*/
private void parse(String code, HHDVersion version) {
this.parse(code, version, LDE_LENGTH_DEFAULT);
}
/**
* Parst den Code mit der angegebenen HHD-Version.
*
* @param code der zu parsende Code.
* @param version die HHD-Version.
* @param ldeLen explizite Angabe der Laenge des LDE.
*/
private void parse(String code, HHDVersion version, int ldeLen) {
reset();
code = clean(code);
// 1. LC ermitteln. Banales ASCII
{
int len = version == HHDVersion.HHD14 ? LC_LENGTH_HHD14 : LC_LENGTH_HHD13;
this.lc = Integer.parseInt(code.substring(0, len));
code = code.substring(len); // und abschneiden
}
// 2. Startcode/Control-Bytes
code = this.startCode.parse(code);
// 3. LDE/DE 1-3
code = this.de1.parse(code, ldeLen);
code = this.de2.parse(code, ldeLen);
code = this.de3.parse(code, ldeLen);
// 4. Den Rest speichern wir hier.
this.rest = code.length() > 0 ? code : null;
}
/**
* Entfernt das CHLGUC0026....CHLGTEXT aus dem Code, falls vorhanden.
* Das sind HHD 1.3-Codes, die nicht im "Challenge HHDuc" uebertragen
* wurden sondern direkt im Challenge-Freitext,
*
* @param code
* @return
*/
private String clean(String code) {
code = code.replaceAll(" ", ""); // Alle Leerzeichen entfernen
code = code.trim(); // Whitespaces entfernen
// Jetzt checken, ob die beiden Tokens enthalten sind
int t1Start = code.indexOf("CHLGUC");
int t2Start = code.indexOf("CHLGTEXT");
if (t1Start == -1 || t2Start == -1 || t2Start <= t1Start)
return code; // Ne, nicht enthalten
// Erstmal den 2. Token abschneiden
code = code.substring(0, t2Start);
// Dann alles abschneiden bis zum Beginn von "CHLGUC"
code = code.substring(t1Start);
// Wir haben eigentlich nicht nur "CHLGUC" sondern "CHLGUC0026"
// Wobei die 4 Zahlen sicher variieren koennen. Wir schneiden einfach alles ab.
code = code.substring(10);
// Jetzt vorn noch ne "0" dran haengen, damit LC wieder 3-stellig ist - wie bei HHD 1.4
return "0" + code;
}
/**
* Rendert den flickerfaehigen Code aus dem Challenge im HHD-Format.
*
* @return der neu generierte Flicker-Code.
*/
public String render() {
// 1. Payload ermitteln
String s = createPayload();
// 2. Luhn-Checksumme neu berechnen
String luhn = createLuhnChecksum();
// 3. XOR-Checksumme neu berechnen
String xor = createXORChecksum(s);
// 4. Alles zusammenbauen und zurueckliefern
return s + luhn + xor;
}
/**
* Generiert den Payload neu.
* Das ist der komplette Code, jedoch ohne Pruefziffern am Ende.
*
* @return der neu generierte Payload.
*/
private String createPayload() {
StringBuffer sb = new StringBuffer();
// 1. Laenge Startcode
sb.append(this.startCode.renderLength());
// 2. Die Control-Bytes
for (Integer i : this.startCode.controlBytes) {
sb.append(toHex(i, 2));
}
// 3. Der Startcode
sb.append(this.startCode.renderData());
// 4. DEs anhaengen.
DE[] deList = new DE[]{this.de1, this.de2, this.de3};
for (int i = 0; i < deList.length; ++i) {
DE de = deList[i];
sb.append(de.renderLength());
sb.append(de.renderData());
}
String s = sb.toString();
// 5. Laenge neu berechnen und vorn dran haengen
int len = s.length();
len += 2; // die zwei Zeichen am Ende mit den Pruefsummen muessen wir noch mit reinrechnen.
len = len / 2; // Anzahl der Bytes. Jedes Byte sind 2 Zeichen.
String lc = toHex(len, 2);
return (lc + s);
}
/**
* Berechnet die XOR-Checksumme fuer den Code neu.
*
* @param payload
* @return die XOR-Checksumme im Hex-Format.
*/
private String createXORChecksum(String payload) {
int xorsum = 0;
for (int i = 0; i < payload.length(); ++i) {
xorsum ^= Integer.parseInt(Character.toString(payload.charAt(i)), 16);
}
return toHex(xorsum, 1);
}
/**
* Berechnet die Luhn-Pruefziffer neu.
*
* @return die Pruefziffer im Hex-Format.
*/
private String createLuhnChecksum() {
////////////////////////////////////////////////////////////////////////////
// Schritt 1: Payload ermitteln
StringBuffer sb = new StringBuffer();
// a) Controlbytes
for (Integer i : this.startCode.controlBytes)
sb.append(toHex(i, 2));
// b) Startcode
sb.append(this.startCode.renderData());
// c) DEs
if (this.de1.data != null) sb.append(this.de1.renderData());
if (this.de2.data != null) sb.append(this.de2.renderData());
if (this.de3.data != null) sb.append(this.de3.renderData());
String payload = sb.toString();
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// Schritt 2: Pruefziffer berechnen
int luhnsum = 0;
int i = 0;
for (i = 0; i < payload.length(); i += 2) {
luhnsum += (1 * Integer.parseInt(Character.toString(payload.charAt(i)), 16)) +
quersumme(2 * Integer.parseInt(Character.toString(payload.charAt(i + 1)), 16));
}
// Ermittelt, wieviel zu "luhnsum" addiert werden muss, um auf die
// naechste Zahl zu kommen, die durch 10 teilbar ist
// Beispiel:
// luhnsum = 129 modulo 10 -> 9
// 10 - 9 = 1
// also 129 + 1 = 130
int mod = luhnsum % 10;
if (mod == 0)
return "0"; // Siehe "Schritt 3" in tan_hhd_uc_v14.pdf, Seite 17
int rest = 10 - mod;
int sum = luhnsum + rest;
// Von dieser Summe ziehen wir die berechnete Summe ab
// Beispiel:
// 130 - 129 = 1
// 1 -> ist die Luhn-Checksumme.
int luhn = sum - luhnsum;
return toHex(luhn, 1);
//
////////////////////////////////////////////////////////////////////////////
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("VERSION:\n" + this.version + "\n");
sb.append("LC: " + this.lc + "\n");
sb.append("Startcode:\n" + this.startCode + "\n");
sb.append("DE1:\n" + this.de1 + "\n");
sb.append("DE2:\n" + this.de2 + "\n");
sb.append("DE3:\n" + this.de3 + "\n");
sb.append("CB : " + this.rest + "\n");
return sb.toString();
}
/**
* Resettet den Code.
*/
private void reset() {
this.version = null;
this.lc = 0;
this.startCode = new Startcode();
this.de1 = new DE();
this.de2 = new DE();
this.de3 = new DE();
this.rest = null;
}
//////////////////////////////////////////////////////////////////////////////
// Hilfsfunktionen fuer die Berechnungen
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (!(obj instanceof FlickerCode))
return false;
FlickerCode other = (FlickerCode) obj;
if (this.lc != other.lc) return false;
if (!this.startCode.equals(other.startCode)) return false;
if (!this.de1.equals(other.de1)) return false;
if (!this.de2.equals(other.de2)) return false;
if (!this.de3.equals(other.de3)) return false;
if (this.rest == null)
return (other.rest == null);
return this.rest.equals(other.rest);
}
/**
* Versionskennung.
*/
public enum HHDVersion {
/**
* HHD-Version 1.4
*/
HHD14,
/**
* HHD-Version 1.3
*/
HHD13
}
/**
* Das Encoding der Nutzdaten.
*/
public enum Encoding {
/**
* ASC-Encoding.
*/
ASC,
/**
* BCD-Encoding.
*/
BCD,
}
/**
* Bean fuer die Eigenschaften eines einzelnen DE.
*/
public class DE {
/**
* Die tatsaechliche Laenge des DE.
* Bereinigt um ggf. vorhandene Control-Bits.
*/
public int length = 0;
/**
* Die Laengen-Angabe des DE im Roh-Format.
* Sie kann noch Control-Bits enthalten, sollte daher
* also NICHT fuer Laengenberechnungen verwendet werden.
* In dem Fall stattdessen length
verwenden.
*/
public int lde = 0;
/**
* Die Laenge des LDE.
*/
public int ldeLen = 0;
/**
* Das Encoding der Nutzdaten.
* Per Definition ist im Challenge HHDuc dieses Bit noch NICHT gesetzt.
* Das Encoding passiert erst beim Rendering.
*/
public Encoding encoding = null;
/**
* Die eigentlichen Nutzdaten des DE.
*/
public String data = null;
/**
* Parst das DE am Beginn des uebergebenen Strings.
*
* @param s der String, dessen Anfang das DE enthaelt.
* @return der Reststring.
*/
String parse(String s) {
return this.parse(s, LDE_LENGTH_DEFAULT);
}
/**
* Parst das DE am Beginn des uebergebenen Strings.
*
* @param s der String, dessen Anfang das DE enthaelt.
* @param ldeLen explizite Angabe der Laenge des LDE.
* @return der Reststring.
*/
String parse(String s, int ldeLen) {
// Nichts mehr zum Parsen da
if (s == null || s.length() == 0)
return s;
// LDE ermitteln (dezimal)
this.lde = Integer.parseInt(s.substring(0, ldeLen));
s = s.substring(ldeLen); // und abschneiden
this.ldeLen = ldeLen;
// Control-Bits abschneiden. Die Laengen-Angabe steht nur in den Bits 0-5.
// In den Bits 6 und 7 stehen Steuer-Informationen
this.length = getBitSum(this.lde, 5); // Bit 0-5
// Encoding gibts hier noch nicht. Das passiert erst beim Rendern
// Nutzdaten ermitteln
this.data = s.substring(0, this.length);
s = s.substring(this.length); // und abschneiden
return s;
}
/**
* Rendert die Laengenangabe fuer die Uebertragung via Flickercode.
*
* @return die codierten Nutzdaten.
* Wenn das DE keine Nutzdaten enthaelt, wird "" zurueck gegeben.
*/
String renderLength() {
// Keine Daten enthalten. Dann muessen wir auch nichts weiter
// beruecksichtigen.
// Laut Belegungsrichtlinien TANve1.4 mit Erratum 1-3 final version vom 2010-11-12.pdf
// duerfen im "ChallengeHHDuc" eigentlich keine leeren DEs enthalten
// sein. Daher geben wir in dem Fall "" zurueck und nicht "00" wie in
// tan_hhd_uc_v14.pdf angegeben. Denn mit "00" wollte es mein TAN-Generator nicht
// lesen. Kann aber auch sein, dass der einfach nicht HHD 1.4 tauglich ist
if (this.data == null)
return "";
Encoding enc = this.getEncoding();
// Die wollen die Anzahl der Bytes, nicht die Laenge der Zeichen
int len = renderData().length() / 2;
// A) BCD -> Muss nichts weiter codiert werden.
if (enc == Encoding.BCD)
return toHex(len, 2);
// B) ASC -> Encoding-Bit reincodieren
// HHD 1.4 -> in das Bit-Feld codieren
if (FlickerCode.this.version == HHDVersion.HHD14) {
len = len + (1 << BIT_ENCODING);
return toHex(len, 2);
}
// HHD 1.3 -> nur ne 1 im linken Halbbyte schicken
return "1" + toHex(len, 1);
}
/**
* Liefert das zu verwendende Encoding fuer die Uebertragung via Flickercode.
* Im Normalfall (also nach dem Parsen eines HHDuc) ist kein Encoding angegeben
* (im Challenge HHDuc ist das per Definition nie gesetzt) machen wir ASC.
* Es sei denn, das Encoding wurde explizit auf BCD gesetzt.
*
* @return das fuer das Rendering zu verwendende Encoding.
*/
Encoding getEncoding() {
if (this.data == null)
return Encoding.BCD;
// Explizit angegeben
if (this.encoding != null)
return this.encoding;
// Siehe tan_hhd_uc_v14.pdf, letzter Absatz in B.2.3
// Bei SEPA-Auftraegen koennen auch Buchstaben in BIC/IBAN vorkommen.
// In dem Fall muss auch ASC-codiert werden. Also machen wir BCD nur
// noch dann, wenn ausschliesslich Zahlen drin stehen.
// Das macht subsembly auch so
// http://www.onlinebanking-forum.de/phpBB2/viewtopic.php?p=75602#75602
if (this.data.matches("[0-9]{1,}"))
return Encoding.BCD;
return Encoding.ASC;
}
/**
* Rendert die Nutzdaten fuer die Uebertragung via Flickercode.
*
* @return die codierten Nutzdaten.
* Wenn das DE keine Nutzdaten enthaelt, wird "" zurueck gegeben.
*/
String renderData() {
if (this.data == null)
return "";
Encoding enc = this.getEncoding();
if (enc == Encoding.ASC)
return toHex(this.data);
// Bei BCD-Encoding noch mit "F" auf Byte-Grenze ergenzen
String s = this.data;
if (s.length() % 2 == 1)
s += "F";
return s;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(" Length : " + this.length + "\n");
sb.append(" LDE : " + this.lde + "\n");
if (this.length > 0)
sb.append(" LDE len : " + this.ldeLen + "\n");
sb.append(" Data : " + this.data + "\n");
sb.append(" Encoding: " + this.encoding + "\n");
return sb.toString();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (!(obj instanceof DE))
return false;
return this.toString().equals(obj.toString());
}
}
/**
* Bean fuer die Eigenschaften des Startcodes.
* Selbstverstaendlich sind hier so einige Sachen anders codiert als im DE.
* Waer ja auch zu einfach sonst.
* Die Laengen-Angabe ist anders codiert (hex statt dec). Und nach der
* Laenge kommen nicht sofort die Nutzdaten sondern erst noch die Control-Bytes.
*/
public class Startcode extends DE {
/**
* Die Control-Bytes.
* In der Regel sollte das nur eines sein.
*/
public List controlBytes = new ArrayList();
/**
* Parst das DE am Beginn des uebergebenen Strings.
*
* @param s der String, dessen Anfang das DE enthaelt.
* @return der Reststring.
* @see org.kapott.hbci.manager.FlickerCode.DE#parse(java.lang.String)
*/
@Override
String parse(String s) {
// 1. LDE ermitteln (hex)
this.lde = Integer.parseInt(s.substring(0, 2), 16);
s = s.substring(2); // und abschneiden
// 2. tatsaechliche Laenge ermitteln
this.length = getBitSum(this.lde, 5); // Bit 0-5
// Encoding gibts hier noch nicht.
// Das passiert erst beim Rendern
// Wenn kein Control-Byte vorhanden ist, muss es HHD 1.3 sein
FlickerCode.this.version = HHDVersion.HHD13;
// 3. Control-Byte ermitteln, falls vorhanden
if (isBitSet(this.lde, BIT_CONTROLBYTE)) {
FlickerCode.this.version = HHDVersion.HHD14;
// Es darf maximal 9 Controlbytes geben
for (int i = 0; i < 10; ++i) {
// 2 Zeichen, Hex
int controlByte = Integer.parseInt(s.substring(0, 2), 16);
this.controlBytes.add(controlByte);
s = s.substring(2); // und abschneiden
// Solange beim Controlbyte das groesste Bit gesetzt ist,
// folgen weitere
if (!isBitSet(controlByte, BIT_CONTROLBYTE))
break;
}
}
// 4. Startcode ermitteln
this.data = s.substring(0, this.length);
s = s.substring(this.length); // und abschneiden
return s;
}
/**
* @see org.kapott.hbci.manager.FlickerCode.DE#renderLength()
* Ueberschrieben, weil wir hier noch reincodieren muessen, ob ein Controlbyte folgt.
*/
String renderLength() {
String s = super.renderLength();
// HHD 1.3 -> gibt keine Controlbytes
if (FlickerCode.this.version == HHDVersion.HHD13)
return s;
// HHD 1.4 -> aber keine Controlbytes vorhanden
if (this.controlBytes.size() == 0)
return s;
// Controlbytes reincodieren
int len = Integer.parseInt(s, 16);
if (this.controlBytes.size() > 0)
len += (1 << BIT_CONTROLBYTE);
return toHex(len, 2);
}
/**
* @see org.kapott.hbci.manager.FlickerCode.DE#toString()
*/
public String toString() {
StringBuffer sb = new StringBuffer(super.toString());
sb.append(" Controlbytes: " + this.controlBytes + "\n");
return sb.toString();
}
/**
* @see org.kapott.hbci.manager.FlickerCode.DE#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (!(obj instanceof Startcode))
return false;
return this.toString().equals(obj.toString());
}
}
//////////////////////////////////////////////////////////////////////////////
}