From 16a73aa055ec7d5d6bfdfac0752d4c030d724b1a Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 21 Aug 2024 00:01:37 +0200 Subject: [PATCH] Fixed multi-threadening segment numbers generation --- .../banking/fints/messages/MessageBuilder.kt | 108 +++++++------- .../segmente/ISegmentNumberGenerator.kt | 15 -- .../segmente/SegmentNumberGenerator.kt | 26 ---- .../dankito/banking/fints/FinTsTestBase.kt | 5 + .../fints/messages/MessageBuilderTest.kt | 2 +- .../MultiThreadedMessageBuilderTest.kt | 141 ++++++++++++++++++ 6 files changed, 204 insertions(+), 93 deletions(-) delete mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/ISegmentNumberGenerator.kt delete mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/SegmentNumberGenerator.kt create mode 100644 fints4k/src/jvmTest/kotlin/net/dankito/banking/fints/messages/MultiThreadedMessageBuilderTest.kt diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt index a3da1384..c8812dd0 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/MessageBuilder.kt @@ -8,9 +8,7 @@ import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGe import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanProcess -import net.dankito.banking.fints.messages.segmente.ISegmentNumberGenerator import net.dankito.banking.fints.messages.segmente.Segment -import net.dankito.banking.fints.messages.segmente.SegmentNumberGenerator import net.dankito.banking.fints.messages.segmente.Synchronisierung import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.messages.segmente.id.ISegmentId @@ -30,13 +28,20 @@ import kotlin.math.absoluteValue * Takes the Segments of they payload, may signs and encrypts them, calculates message size, * adds the message header and ending, and formats the whole message to string. */ -open class MessageBuilder(protected val generator: ISegmentNumberGenerator = SegmentNumberGenerator(), - protected val utils: FinTsUtils = FinTsUtils()) { +open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) { companion object { const val MessageHeaderMinLength = 28 const val AddedSeparatorsLength = 3 + + private const val MessageHeaderSegmentNumber = 1 + + private const val UnsignedMessagePayloadSegmentNumberStart = MessageHeaderSegmentNumber + 1 + + private const val SignatureHeaderSegmentNumber = MessageHeaderSegmentNumber + 1 + + private const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1 } @@ -60,15 +65,15 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg open fun createAnonymousDialogInitMessage(context: JobContext): MessageBuilderResult { return createUnsignedMessageBuilderResult(context, MessageType.AnonymousDialogInit, listOf( - IdentifikationsSegment(generator.resetSegmentNumber(1), context), - Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), context) + IdentifikationsSegment(UnsignedMessagePayloadSegmentNumberStart, context), + Verarbeitungsvorbereitung(UnsignedMessagePayloadSegmentNumberStart + 1, context) )) } open fun createAnonymousDialogEndMessage(context: JobContext): MessageBuilderResult { return createUnsignedMessageBuilderResult(context, MessageType.DialogEnd, listOf( - Dialogende(generator.resetSegmentNumber(1), context.dialog) + Dialogende(UnsignedMessagePayloadSegmentNumberStart, context.dialog) )) } @@ -116,40 +121,37 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg } protected open fun createInitDialogMessage(context: JobContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?): MessageBuilderResult { - val segments = mutableListOf( - IdentifikationsSegment(generator.resetSegmentNumber(2), context), - Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), context) + IdentifikationsSegment(SignedMessagePayloadFirstSegmentNumber, context), + Verarbeitungsvorbereitung(SignedMessagePayloadFirstSegmentNumber + 1, context) ) if (segmentIdForTwoStepTanProcess != null) { - segments.add(createTwoStepTanSegment(context, segmentIdForTwoStepTanProcess)) - } - else if (context.bank.isTanMethodSelected) { - segments.add(createTwoStepTanSegment(context, CustomerSegmentId.Identification)) + segments.add(createTwoStepTanSegment(context, segmentIdForTwoStepTanProcess, SignedMessagePayloadFirstSegmentNumber + 2)) + } else if (context.bank.isTanMethodSelected) { + segments.add(createTwoStepTanSegment(context, CustomerSegmentId.Identification, SignedMessagePayloadFirstSegmentNumber + 2)) } if (context.bank.customerSystemId == KundensystemID.Anonymous) { - segments.add(Synchronisierung(generator.getNextSegmentNumber(), Synchronisierungsmodus.NeueKundensystemIdZurueckmelden)) + segments.add(Synchronisierung(segments.size + 3, Synchronisierungsmodus.NeueKundensystemIdZurueckmelden)) } return createSignedMessageBuilderResult(context, MessageType.DialogInit, segments) } open fun createSynchronizeCustomerSystemIdMessage(context: JobContext): MessageBuilderResult { - return createSignedMessageBuilderResult(context, MessageType.SynchronizeCustomerSystemId, listOf( - IdentifikationsSegment(generator.resetSegmentNumber(2), context), - Verarbeitungsvorbereitung(generator.getNextSegmentNumber(), context), - createTwoStepTanSegment(context, CustomerSegmentId.Identification), - Synchronisierung(generator.getNextSegmentNumber(), Synchronisierungsmodus.NeueKundensystemIdZurueckmelden) + IdentifikationsSegment(SignedMessagePayloadFirstSegmentNumber, context), + Verarbeitungsvorbereitung(SignedMessagePayloadFirstSegmentNumber + 1, context), + createTwoStepTanSegment(context, CustomerSegmentId.Identification, SignedMessagePayloadFirstSegmentNumber + 2), + Synchronisierung(SignedMessagePayloadFirstSegmentNumber + 3, Synchronisierungsmodus.NeueKundensystemIdZurueckmelden) )) } open fun createDialogEndMessage(context: JobContext): MessageBuilderResult { return createSignedMessageBuilderResult(context, MessageType.DialogEnd, listOf( - Dialogende(generator.resetSegmentNumber(2), context.dialog) + Dialogende(SignedMessagePayloadFirstSegmentNumber, context.dialog) )) } @@ -178,13 +180,15 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg parameter.isSettingMaxCountEntriesAllowedByBank = determineIsSettingMaxCountEntriesAllowed(context.bank, InstituteSegmentId.AccountTransactionsMt940Parameters, listOf(5, 6, 7)) } - val transactionsJob = if (result.isAllowed(7)) KontoumsaetzeZeitraumMt940Version7(generator.resetSegmentNumber(2), parameter, context.bank) - else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(generator.resetSegmentNumber(2), parameter) - else KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), parameter) + val segmentNumber = SignedMessagePayloadFirstSegmentNumber + + val transactionsJob = if (result.isAllowed(7)) KontoumsaetzeZeitraumMt940Version7(segmentNumber, parameter, context.bank) + else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(segmentNumber, parameter) + else KontoumsaetzeZeitraumMt940Version5(segmentNumber, parameter) val segments = mutableListOf(transactionsJob) - addTanSegmentIfRequired(context, CustomerSegmentId.AccountTransactionsMt940, segments) + addTanSegmentIfRequired(context, CustomerSegmentId.AccountTransactionsMt940, segments, segmentNumber + 1) return createSignedMessageBuilderResult(context, MessageType.GetTransactions, segments) } @@ -198,9 +202,9 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg protected open fun createGetCreditCardTransactionsMessage(context: JobContext, result: MessageBuilderResult, parameter: GetAccountTransactionsParameter): MessageBuilderResult { - val segments = mutableListOf(KreditkartenUmsaetze(generator.resetSegmentNumber(2), parameter)) + val segments = mutableListOf(KreditkartenUmsaetze(SignedMessagePayloadFirstSegmentNumber, parameter)) - addTanSegmentIfRequired(context, CustomerSegmentId.CreditCardTransactions, segments) + addTanSegmentIfRequired(context, CustomerSegmentId.CreditCardTransactions, segments, SignedMessagePayloadFirstSegmentNumber + 1) return createSignedMessageBuilderResult(context, MessageType.GetCreditCardTransactions, segments) } @@ -224,12 +228,15 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg val result = supportsGetBalanceMessage(account) if (result.isJobVersionSupported) { - val balanceJob = if (result.isAllowed(5)) SaldenabfrageVersion5(generator.resetSegmentNumber(2), account) - else SaldenabfrageVersion7(generator.resetSegmentNumber(2), account, context.bank) + val segmentNumber = SignedMessagePayloadFirstSegmentNumber + + val balanceJob = if (result.isAllowed(5)) SaldenabfrageVersion5(segmentNumber, account) + // TODO: what about HKSAL6? + else SaldenabfrageVersion7(segmentNumber, account, context.bank) val segments = mutableListOf(balanceJob) - addTanSegmentIfRequired(context, CustomerSegmentId.Balance, segments) + addTanSegmentIfRequired(context, CustomerSegmentId.Balance, segments, segmentNumber + 1) return createSignedMessageBuilderResult(context, MessageType.GetBalance, segments) } @@ -254,8 +261,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg if (result.isJobVersionSupported) { val segments = listOf( - TanGeneratorListeAnzeigen(result.getHighestAllowedVersion!!, - generator.resetSegmentNumber(2), tanMediaKind, tanMediumClass) + TanGeneratorListeAnzeigen(result.getHighestAllowedVersion!!, SignedMessagePayloadFirstSegmentNumber, tanMediaKind, tanMediumClass) ) return createSignedMessageBuilderResult(context, MessageType.GetTanMedia, segments) @@ -272,7 +278,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg if (result.isJobVersionSupported) { val segments = listOf( - TanGeneratorTanMediumAnOderUmmelden(result.getHighestAllowedVersion!!, generator.resetSegmentNumber(2), + TanGeneratorTanMediumAnOderUmmelden(result.getHighestAllowedVersion!!, SignedMessagePayloadFirstSegmentNumber, context.bank, newActiveTanMedium, tan, atc) ) @@ -287,7 +293,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg val tanProcess = if (tanResponse.tanProcess == TanProcess.TanProcess1) TanProcess.TanProcess1 else TanProcess.TanProcess2 val segments = listOf( - ZweiSchrittTanEinreichung(generator.resetSegmentNumber(2), tanProcess, null, + ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, tanProcess, null, tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier) ) @@ -302,10 +308,10 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg val (result, urn) = supportsBankTransferAndSepaVersion(context.bank, account, segmentId) if (result.isJobVersionSupported && urn != null) { - val segments = mutableListOf(SepaBankTransferBase(segmentId, generator.resetSegmentNumber(2), + val segments = mutableListOf(SepaBankTransferBase(segmentId, SignedMessagePayloadFirstSegmentNumber, urn, context.bank.customerName, account, context.bank.bic, data)) - addTanSegmentIfRequired(context, segmentId, segments) + addTanSegmentIfRequired(context, segmentId, segments, SignedMessagePayloadFirstSegmentNumber + 1) return createSignedMessageBuilderResult(context, MessageType.TransferMoney, segments) } @@ -387,32 +393,32 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg return createSignedMessage(context, null, payloadSegments) } - open fun createSignedMessage(context: JobContext, tan: String? = null, - payloadSegments: List): String { + open fun createSignedMessage(context: JobContext, tan: String? = null, payloadSegments: List): String { val date = utils.formatDateTodayAsInt() val time = utils.formatTimeNowAsInt() - val signedPayload = signPayload(2, context, date, time, tan, payloadSegments) + val signedPayload = signPayload(context, date, time, tan, payloadSegments) val encryptedPayload = encryptPayload(context, date, time, signedPayload) - return createMessage(context, encryptedPayload) + return createMessage(context, encryptedPayload, payloadSegments.size) } - open fun createMessage(context: JobContext, payloadSegments: List): String { + open fun createMessage(context: JobContext, payloadSegments: List, countWrappedSegments: Int = 0): String { val dialog = context.dialog dialog.increaseMessageNumber() val formattedPayload = formatPayload(payloadSegments) - val ending = Nachrichtenabschluss(generator.getNextSegmentNumber(), dialog) + // if there are segments wrapped like in signed message body, we have to add these segments to segment count; +2 for Message Header and -Ending + val ending = Nachrichtenabschluss(payloadSegments.size + countWrappedSegments + 2, dialog) val formattedEnding = ending.format() val messageSize = calculateMessageSize(formattedPayload, formattedEnding, dialog) - val header = Nachrichtenkopf(ISegmentNumberGenerator.FirstSegmentNumber, messageSize, dialog) + val header = Nachrichtenkopf(MessageHeaderSegmentNumber, messageSize, dialog) return listOf(header.format(), formattedPayload, formattedEnding) .joinToString(Separators.SegmentSeparator, postfix = Separators.SegmentSeparator) @@ -422,19 +428,19 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg // we don't know Header's length yet - but already have to know its length in order to calculate message length. // -> generate header with a known minimum header length added to message body length to calculate header length val minMessageSize = formattedPayload.length + MessageHeaderMinLength + formattedEnding.length + AddedSeparatorsLength - val headerWithMinMessageSize = Nachrichtenkopf(ISegmentNumberGenerator.FirstSegmentNumber, minMessageSize, dialogContext).format() + val headerWithMinMessageSize = Nachrichtenkopf(MessageHeaderSegmentNumber, minMessageSize, dialogContext).format() return formattedPayload.length + headerWithMinMessageSize.length + formattedEnding.length + AddedSeparatorsLength } - protected open fun signPayload(headerSegmentNumber: Int, context: JobContext, date: Int, time: Int, + protected open fun signPayload(context: JobContext, date: Int, time: Int, tan: String? = null, payloadSegments: List): List { val controlReference = createControlReference() val signatureHeader = PinTanSignaturkopf( - headerSegmentNumber, + SignatureHeaderSegmentNumber, // is always 2 context, controlReference, date, @@ -442,7 +448,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg ) val signatureEnding = Signaturabschluss( - generator.getNextSegmentNumber(), + payloadSegments.size + 3, // +3: Message Header (1), Signatur Header (2), Signature Ending controlReference, context.bank.pin, tan @@ -507,14 +513,14 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg return MessageBuilderResult(false) } - protected open fun addTanSegmentIfRequired(context: JobContext, segmentId: CustomerSegmentId, segments: MutableList) { + protected open fun addTanSegmentIfRequired(context: JobContext, segmentId: CustomerSegmentId, segments: MutableList, segmentNumber: Int) { if (isTanRequiredForJob(context, segmentId)) { - segments.add(createTwoStepTanSegment(context, segmentId)) + segments.add(createTwoStepTanSegment(context, segmentId, segmentNumber)) } } - protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId): ZweiSchrittTanEinreichung { - return ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, segmentId, + protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId, segmentNumber: Int): ZweiSchrittTanEinreichung { + return ZweiSchrittTanEinreichung(segmentNumber, TanProcess.TanProcess4, segmentId, tanMediaIdentifier = getTanMediaIdentifierIfRequired(context)) } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/ISegmentNumberGenerator.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/ISegmentNumberGenerator.kt deleted file mode 100644 index fa7777a2..00000000 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/ISegmentNumberGenerator.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.dankito.banking.fints.messages.segmente - - -interface ISegmentNumberGenerator { - - companion object { - const val FirstSegmentNumber = 1 - } - - - fun resetSegmentNumber(countNumberToSkipForHeader: Int): Int - - fun getNextSegmentNumber(): Int - -} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/SegmentNumberGenerator.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/SegmentNumberGenerator.kt deleted file mode 100644 index 5834d3c6..00000000 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/segmente/SegmentNumberGenerator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package net.dankito.banking.fints.messages.segmente - -import net.dankito.banking.fints.messages.segmente.ISegmentNumberGenerator.Companion.FirstSegmentNumber - - -open class SegmentNumberGenerator : ISegmentNumberGenerator { - - protected var currentSegmentNumber = 0 - - - override fun resetSegmentNumber(countNumbersToSkipForHeaders: Int): Int { - currentSegmentNumber = FirstSegmentNumber + countNumbersToSkipForHeaders - - return currentSegmentNumber - } - - override fun getNextSegmentNumber(): Int { - return ++currentSegmentNumber - } - - - override fun toString(): String { - return "Current segment number = $currentSegmentNumber" - } - -} \ No newline at end of file diff --git a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/FinTsTestBase.kt b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/FinTsTestBase.kt index 405a2464..2b0e81d0 100644 --- a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/FinTsTestBase.kt +++ b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/FinTsTestBase.kt @@ -9,6 +9,7 @@ import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Datum import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen import net.dankito.banking.fints.messages.datenelemente.implementierte.Dialogsprache import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion +import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.model.* import net.dankito.banking.fints.response.segments.AccountType import net.dankito.banking.fints.response.segments.ChangeTanMediaParameters @@ -86,6 +87,10 @@ abstract class FinTsTestBase { return randomWithSeed().nextInt(1000000, 9999999).toString() } + protected open fun createAllowedJob(segmentId: CustomerSegmentId, version: Int): JobParameters = JobParameters( + segmentId.id, 1, 1, null, "${segmentId.id.replace("HK", "HI")}S:1:$version" + ) + protected open fun convertDate(date: LocalDate): String { return Datum.format(date) } diff --git a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/messages/MessageBuilderTest.kt b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/messages/MessageBuilderTest.kt index 8fac47ea..71037a18 100644 --- a/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/messages/MessageBuilderTest.kt +++ b/fints4k/src/commonTest/kotlin/net/dankito/banking/fints/messages/MessageBuilderTest.kt @@ -17,7 +17,7 @@ import kotlin.test.* class MessageBuilderTest : FinTsTestBase() { - private val underTest = object : MessageBuilder(utils = object : FinTsUtils() { + private val underTest = object : MessageBuilder(object : FinTsUtils() { override fun formatDate(date: LocalDate): String { return Date.toString() } diff --git a/fints4k/src/jvmTest/kotlin/net/dankito/banking/fints/messages/MultiThreadedMessageBuilderTest.kt b/fints4k/src/jvmTest/kotlin/net/dankito/banking/fints/messages/MultiThreadedMessageBuilderTest.kt new file mode 100644 index 00000000..bb39fe9f --- /dev/null +++ b/fints4k/src/jvmTest/kotlin/net/dankito/banking/fints/messages/MultiThreadedMessageBuilderTest.kt @@ -0,0 +1,141 @@ +package net.dankito.banking.fints.messages + +import kotlinx.coroutines.* +import kotlinx.coroutines.test.runTest +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalTime +import net.dankito.banking.fints.FinTsTestBase +import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.* +import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId +import net.dankito.banking.fints.model.* +import net.dankito.banking.fints.response.segments.* +import net.dankito.banking.fints.util.FinTsUtils +import java.util.concurrent.Executors +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class MultiThreadedMessageBuilderTest : FinTsTestBase() { + + private val underTest = object : MessageBuilder(object : FinTsUtils() { + override fun formatDate(date: LocalDate): String { + return Date.toString() + } + + override fun formatTime(time: LocalTime): String { + return Time.toString() + } + }) { + + override fun createControlReference(): String { + return ControlReference + } + + } + + + private val bank = createTestBank() + + @AfterTest + fun tearDown() { + bank.supportedJobs = listOf() + } + + + @Test + fun testSegmentNumberOrderForParallelMessageCreation(): Unit = runTest { + // in previous version when FinTsClient has been used in multi-threaded environments, e.g. on web servers were + // messages are created for multiple parallel users, the segment numbers were wrong and not incrementally ordered + + val bank = createBankWithAllFeatures() + + val context = createContext(bank) + + val dispatcher = Executors.newFixedThreadPool(24).asCoroutineDispatcher() + val coroutineScope = CoroutineScope(dispatcher) + + IntRange(0, 10_000).map { index -> + coroutineScope.async { +// context.startNewDialog() + val result = createRandomMessage(index, context) + + val (segments, segmentNumbers) = extractSegmentNumbers(result) + + // assert that segment numbers are in ascending order in steps of one + segmentNumbers.dropLast(1).forEachIndexed { index, segmentNumber -> + val nextSegmentNumber = segmentNumbers[index + 1] + + assertEquals(segmentNumber + 1, nextSegmentNumber, + "Message numbers should be in ascending order with step one:\n${segments[index]}\n${segments[index + 1]}") + } + + assertEquals(1, segmentNumbers.first()) + assertEquals(segmentNumbers.size, segmentNumbers.last()) + + segments + } + }.awaitAll() + } + + private fun extractSegmentNumbers(result: MessageBuilderResult): Pair, List> { + val segments = result.createdMessage!!.split("'").filter { it.isNotBlank() && it.startsWith("HNVSK") == false }.map { segment -> + if (segment.startsWith("HNVSD")) segment.substring(segment.indexOf("HNSHK")) + else segment + } + val segmentNumbers = segments.map { segment -> + val indexOfFirstSeparator = segment.indexOf(':') + val indexOfSecondSeparator = segment.indexOf(':', indexOfFirstSeparator + 1) + segment.substring(indexOfFirstSeparator + 1, indexOfSecondSeparator).toInt() + } + + return Pair(segments, segmentNumbers) + } + + private fun createRandomMessage(index: Int, context: JobContext, account: AccountData = bank.accounts.first()): MessageBuilderResult = when (index % 14) { + 0 -> underTest.createAnonymousDialogInitMessage(context) + 2 -> underTest.createInitDialogMessage(context) + 3 -> underTest.createInitDialogMessageWithoutStrongCustomerAuthentication(context, null) + 4 -> underTest.createSynchronizeCustomerSystemIdMessage(context) + 5 -> underTest.createGetTanMediaListMessage(context) + 6 -> underTest.createChangeTanMediumMessage(context, TanGeneratorTanMedium(TanMediumKlasse.TanGenerator, TanMediumStatus.Aktiv, "", null, null, null, null, null), null, null) + 7 -> underTest.createGetBalanceMessage(context, account) + 8 -> underTest.createGetTransactionsMessage(context, GetAccountTransactionsParameter(bank, account, true)) + 9 -> underTest.createGetTransactionsMessage(context, GetAccountTransactionsParameter(bank, bank.accounts[1], true)) + 10 -> underTest.createBankTransferMessage(context, BankTransferData("", "", "", Money.Zero, null), account) + 11 -> underTest.createBankTransferMessage(context, BankTransferData("", "", "", Money.Zero, null, true), account) + 12 -> underTest.createSendEnteredTanMessage(context, "", TanResponse(TanProcess.TanProcess2, null, null, null, null, null, null, "HITAN:5:6:4+4++4937-10-13-02.30.03.700259+Sie möchten eine \"Umsatzabfrage\" freigeben?: Bitte bestätigen Sie den \"Startcode 80085335\" mit der Taste \"OK\".+@12@100880085335++Kartennummer ******0892")) + 13 -> underTest.createDialogEndMessage(context) + else -> underTest.createAnonymousDialogEndMessage(context) + } + + + private fun createBankWithAllFeatures(): BankData { + val getTransactionsJob = RetrieveAccountTransactionsParameters(JobParameters(CustomerSegmentId.AccountTransactionsMt940.id, 1, 1, null, "HIKAZS:73:5"), 180, true, false) + val changeTanMediumJob = createAllowedJob(CustomerSegmentId.ChangeTanMedium, 3) + bank.supportedJobs = listOf( + getTransactionsJob, + createAllowedJob(CustomerSegmentId.TanMediaList, 5), changeTanMediumJob, + createAllowedJob(CustomerSegmentId.Balance, 7), + createAllowedJob(CustomerSegmentId.CreditCardTransactions, 2), + SepaAccountInfoParameters(createAllowedJob(CustomerSegmentId.SepaBankTransfer, 1), true, true, true, true, 35, listOf("pain.001.001.03")), + SepaAccountInfoParameters(createAllowedJob(CustomerSegmentId.SepaRealTimeTransfer, 1), true, true, true, true, 35, listOf("pain.001.001.03")), + ) + bank.pinInfo = PinInfo(getTransactionsJob, null, null, null, null, null, listOf( + JobTanConfiguration(CustomerSegmentId.Balance.id, true), + JobTanConfiguration(CustomerSegmentId.AccountTransactionsMt940.id, true), + JobTanConfiguration(CustomerSegmentId.CreditCardTransactions.id, true), + JobTanConfiguration(CustomerSegmentId.SepaBankTransfer.id, true), + JobTanConfiguration(CustomerSegmentId.SepaRealTimeTransfer.id, true) + )) + bank.changeTanMediumParameters = ChangeTanMediaParameters(changeTanMediumJob, false, false, false, false, false, listOf()) + + val checkingAccount = AccountData(CustomerId, null, BankCountryCode, BankCode, "ABCDDEBBXXX", CustomerId, AccountType.Girokonto, "EUR", "", null, null, bank.supportedJobs.map { it.jobName }, bank.supportedJobs) + bank.addAccount(checkingAccount) + + val creditCardAccountJobs = bank.supportedJobs.filterNot { it.jobName == CustomerSegmentId.AccountTransactionsMt940.id } + val creditCardAccount = AccountData(CustomerId + "_CreditCard", null, BankCountryCode, BankCode, "ABCDDEBBXXX", CustomerId, AccountType.Kreditkartenkonto, "EUR", "", null, null, creditCardAccountJobs.map { it.jobName }, creditCardAccountJobs) + bank.addAccount(creditCardAccount) + + return bank + } +} \ No newline at end of file