diff options
author | Antoine A <> | 2024-02-13 07:47:46 +0100 |
---|---|---|
committer | Antoine A <> | 2024-02-13 07:47:46 +0100 |
commit | f83a22f243a315fbc4b4086155c2401845cf334c (patch) | |
tree | af264e686c5fc657fba060e210bdfdb3454cbf72 | |
parent | 7f84324c0712097513becd7c4675e50d4194833a (diff) | |
download | libeufin-f83a22f243a315fbc4b4086155c2401845cf334c.tar.gz libeufin-f83a22f243a315fbc4b4086155c2401845cf334c.tar.bz2 libeufin-f83a22f243a315fbc4b4086155c2401845cf334c.zip |
Optimize memory usage and performance by using ByteArray when possible and improve logging
-rw-r--r-- | ebics/src/main/kotlin/Ebics.kt | 10 | ||||
-rw-r--r-- | ebics/src/main/kotlin/EbicsOrderUtil.kt | 4 | ||||
-rw-r--r-- | ebics/src/main/kotlin/XMLUtil.kt | 56 | ||||
-rw-r--r-- | ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt | 9 | ||||
-rw-r--r-- | ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt | 7 | ||||
-rw-r--r-- | ebics/src/test/kotlin/EbicsMessagesTest.kt | 50 | ||||
-rw-r--r-- | ebics/src/test/kotlin/EbicsOrderUtilTest.kt | 4 | ||||
-rw-r--r-- | ebics/src/test/kotlin/XmlUtilTest.kt | 22 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 31 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 7 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt | 38 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt | 31 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt | 78 | ||||
-rw-r--r-- | nexus/src/test/kotlin/Ebics.kt | 22 |
14 files changed, 168 insertions, 201 deletions
diff --git a/ebics/src/main/kotlin/Ebics.kt b/ebics/src/main/kotlin/Ebics.kt index 406da44c..91a41022 100644 --- a/ebics/src/main/kotlin/Ebics.kt +++ b/ebics/src/main/kotlin/Ebics.kt @@ -310,7 +310,7 @@ class HpbResponseData( fun parseEbicsHpbOrder(orderDataRaw: ByteArray): HpbResponseData { val resp = try { - XMLUtil.convertStringToJaxb<HPBResponseOrderData>(orderDataRaw.toString(Charsets.UTF_8)) + XMLUtil.convertBytesToJaxb<HPBResponseOrderData>(orderDataRaw) } catch (e: Exception) { throw EbicsProtocolError(HttpStatusCode.InternalServerError, "Invalid XML (as HPB response) received from bank") } @@ -331,10 +331,10 @@ fun parseEbicsHpbOrder(orderDataRaw: ByteArray): HpbResponseData { ) } -fun ebics3toInternalRepr(response: String): EbicsResponseContent { +fun ebics3toInternalRepr(response: ByteArray): EbicsResponseContent { // logger.debug("Converting bank resp to internal repr.: $response") val resp: JAXBElement<Ebics3Response> = try { - XMLUtil.convertStringToJaxb(response) + XMLUtil.convertBytesToJaxb(response) } catch (e: Exception) { throw EbicsProtocolError( HttpStatusCode.InternalServerError, @@ -368,9 +368,9 @@ fun ebics3toInternalRepr(response: String): EbicsResponseContent { ) } -fun ebics25toInternalRepr(response: String): EbicsResponseContent { +fun ebics25toInternalRepr(response: ByteArray): EbicsResponseContent { val resp: JAXBElement<EbicsResponse> = try { - XMLUtil.convertStringToJaxb(response) + XMLUtil.convertBytesToJaxb(response) } catch (e: Exception) { throw EbicsProtocolError( HttpStatusCode.InternalServerError, diff --git a/ebics/src/main/kotlin/EbicsOrderUtil.kt b/ebics/src/main/kotlin/EbicsOrderUtil.kt index ef7ed1f7..c056ddde 100644 --- a/ebics/src/main/kotlin/EbicsOrderUtil.kt +++ b/ebics/src/main/kotlin/EbicsOrderUtil.kt @@ -39,12 +39,12 @@ object EbicsOrderUtil { inline fun <reified T> decodeOrderDataXml(encodedOrderData: ByteArray): T { return InflaterInputStream(encodedOrderData.inputStream()).use { val bytes = it.readAllBytes() - XMLUtil.convertStringToJaxb<T>(bytes.toString(Charsets.UTF_8)).value + XMLUtil.convertBytesToJaxb<T>(bytes).value } } inline fun <reified T> encodeOrderDataXml(obj: T): ByteArray { - val bytes = XMLUtil.convertJaxbToString(obj).toByteArray() + val bytes = XMLUtil.convertJaxbToBytes(obj) return DeflaterInputStream(bytes.inputStream()).use { it.readAllBytes() } diff --git a/ebics/src/main/kotlin/XMLUtil.kt b/ebics/src/main/kotlin/XMLUtil.kt index 63dbf35b..5947341a 100644 --- a/ebics/src/main/kotlin/XMLUtil.kt +++ b/ebics/src/main/kotlin/XMLUtil.kt @@ -287,17 +287,17 @@ class XMLUtil private constructor() { * @param xmlString XML body, as read from the POST body. * @return InputStream object, as wanted by the validator. */ - fun validateFromString(xmlString: String): Boolean { - val xmlInputStream: InputStream = ByteArrayInputStream(xmlString.toByteArray()) + fun validateFromBytes(xml: ByteArray): Boolean { + val xmlInputStream: InputStream = ByteArrayInputStream(xml) val xmlSource = StreamSource(xmlInputStream) return validate(xmlSource) } - inline fun <reified T> convertJaxbToString( + inline fun <reified T> convertJaxbToBytes( obj: T, withSchemaLocation: String? = null - ): String { - val sw = StringWriter() + ): ByteArray { + val w = ByteArrayOutputStream() val jc = JAXBContext.newInstance(T::class.java) val m = jc.createMarshaller() m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) @@ -305,8 +305,8 @@ class XMLUtil private constructor() { m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, withSchemaLocation) } m.setProperty("com.sun.xml.bind.namespacePrefixMapper", DefaultNamespaces()) - m.marshal(obj, sw) - return sw.toString() + m.marshal(obj, w) + return w.toByteArray() } inline fun <reified T> convertJaxbToDocument( @@ -328,37 +328,31 @@ class XMLUtil private constructor() { } /** - * Convert a XML string to the JAXB representation. + * Convert XML bytes to the JAXB representation. * - * @param documentString the string to convert into JAXB. + * @param documentBytes the bytes to convert into JAXB. * @return the JAXB object reflecting the original XML document. */ - inline fun <reified T> convertStringToJaxb(documentString: String): JAXBElement<T> { + inline fun <reified T> convertBytesToJaxb(documentBytes: ByteArray): JAXBElement<T> { val jc = JAXBContext.newInstance(T::class.java) val u = jc.createUnmarshaller() return u.unmarshal( /* Marshalling the object into the document. */ - StreamSource(StringReader(documentString)), + StreamSource(ByteArrayInputStream(documentBytes)), T::class.java ) } - /** - * Extract String from DOM. - * - * @param document the DOM to extract the string from. - * @return the final String, or null if errors occur. - */ - fun convertDomToString(document: Document): String { + fun convertDomToBytes(document: Document): ByteArray { /* Make Transformer. */ val tf = TransformerFactory.newInstance() val t = tf.newTransformer() - /* Make string writer. */ - val sw = StringWriter() + /* Make bytes writer. */ + val w = ByteArrayOutputStream() /* Extract string. */ - t.transform(DOMSource(document), StreamResult(sw)) - return sw.toString() + t.transform(DOMSource(document), StreamResult(w)) + return w.toByteArray() } /** @@ -391,20 +385,6 @@ class XMLUtil private constructor() { return m.unmarshal(document, finalType) // document "went" into Jaxb } - /** - * Parse string into XML DOM. - * @param xmlString the string to parse. - * @return the DOM representing @a xmlString - */ - fun parseStringIntoDom(xmlString: String): Document { - val factory = DocumentBuilderFactory.newInstance().apply { - isNamespaceAware = true - } - val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray()) - val builder = factory.newDocumentBuilder() - return builder.parse(InputSource(xmlInputStream)) - } - /** Parse [xml] into a XML DOM */ fun parseBytesIntoDom(xml: ByteArray): Document { val factory = DocumentBuilderFactory.newInstance().apply { @@ -415,10 +395,10 @@ class XMLUtil private constructor() { return builder.parse(InputSource(xmlInputStream)) } - fun signEbicsResponse(ebicsResponse: EbicsResponse, privateKey: RSAPrivateCrtKey): String { + fun signEbicsResponse(ebicsResponse: EbicsResponse, privateKey: RSAPrivateCrtKey): ByteArray { val doc = convertJaxbToDocument(ebicsResponse) signEbicsDocument(doc, privateKey) - val signedDoc = XMLUtil.convertDomToString(doc) + val signedDoc = XMLUtil.convertDomToBytes(doc) // logger.debug("response: $signedDoc") return signedDoc } diff --git a/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt b/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt index 263fb71a..2b89e963 100644 --- a/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt +++ b/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt @@ -286,11 +286,10 @@ class EbicsRequest { } companion object { - fun createForDownloadReceiptPhase( - transactionId: String?, - hostId: String - + transactionId: String, + hostId: String, + success: Boolean ): EbicsRequest { return EbicsRequest().apply { header = Header().apply { @@ -310,7 +309,7 @@ class EbicsRequest { body = Body().apply { transferReceipt = TransferReceipt().apply { authenticate = true - receiptCode = 0 // always true at this point. + receiptCode = if (success) 1 else 0 } } } diff --git a/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt b/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt index 54987c8b..ab710690 100644 --- a/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt +++ b/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt @@ -368,8 +368,9 @@ class Ebics3Request { companion object { fun createForDownloadReceiptPhase( - transactionId: String?, - hostId: String + transactionId: String, + hostId: String, + success: Boolean ): Ebics3Request { return Ebics3Request().apply { header = Header().apply { @@ -389,7 +390,7 @@ class Ebics3Request { body = Body().apply { transferReceipt = TransferReceipt().apply { authenticate = true - receiptCode = 0 // always true at this point. + receiptCode = if (success) 1 else 0 } } } diff --git a/ebics/src/test/kotlin/EbicsMessagesTest.kt b/ebics/src/test/kotlin/EbicsMessagesTest.kt index 5d0f8f5d..2b8d63ca 100644 --- a/ebics/src/test/kotlin/EbicsMessagesTest.kt +++ b/ebics/src/test/kotlin/EbicsMessagesTest.kt @@ -17,8 +17,6 @@ * <http://www.gnu.org/licenses/> */ -package tech.libeufin.sandbox - import junit.framework.TestCase.assertEquals import org.apache.xml.security.binding.xmldsig.SignatureType import org.junit.Test @@ -43,7 +41,7 @@ class EbicsMessagesTest { fun testImportNonRoot() { val classLoader = ClassLoader.getSystemClassLoader() val ini = classLoader.getResource("ebics_ini_inner_key.xml") - val jaxb = XMLUtil.convertStringToJaxb<SignatureTypes.SignaturePubKeyOrderData>(ini.readText()) + val jaxb = XMLUtil.convertBytesToJaxb<SignatureTypes.SignaturePubKeyOrderData>(ini.readBytes()) assertEquals("A006", jaxb.value.signaturePubKeyInfo.signatureVersion) } @@ -54,7 +52,7 @@ class EbicsMessagesTest { fun testStringToJaxb() { val classLoader = ClassLoader.getSystemClassLoader() val ini = classLoader.getResource("ebics_ini_request_sample.xml") - val jaxb = XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(ini.readText()) + val jaxb = XMLUtil.convertBytesToJaxb<EbicsUnsecuredRequest>(ini.readBytes()) println("jaxb loaded") assertEquals( "INI", @@ -74,7 +72,7 @@ class EbicsMessagesTest { } this.versionNumber = listOf(HEVResponse.VersionNumber.create("H004", "02.50")) } - XMLUtil.convertJaxbToString(hevResponseJaxb) + XMLUtil.convertJaxbToBytes(hevResponseJaxb) } /** @@ -84,7 +82,7 @@ class EbicsMessagesTest { fun testDomToJaxb() { val classLoader = ClassLoader.getSystemClassLoader() val ini = classLoader.getResource("ebics_ini_request_sample.xml")!! - val iniDom = XMLUtil.parseStringIntoDom(ini.readText()) + val iniDom = XMLUtil.parseBytesIntoDom(ini.readBytes()) XMLUtil.convertDomToJaxb<EbicsUnsecuredRequest>( EbicsUnsecuredRequest::class.java, iniDom @@ -109,22 +107,22 @@ class EbicsMessagesTest { } } } - val text = XMLUtil.convertJaxbToString(responseXml) - assertTrue(text.isNotEmpty()) + val bytes = XMLUtil.convertJaxbToBytes(responseXml) + assertTrue(bytes.isNotEmpty()) } @Test fun testParseHiaRequestOrderData() { val classLoader = ClassLoader.getSystemClassLoader() - val hia = classLoader.getResource("hia_request_order_data.xml")!!.readText() - XMLUtil.convertStringToJaxb<HIARequestOrderData>(hia) + val hia = classLoader.getResource("hia_request_order_data.xml")!!.readBytes() + XMLUtil.convertBytesToJaxb<HIARequestOrderData>(hia) } @Test fun testHiaLoad() { val classLoader = ClassLoader.getSystemClassLoader() val hia = classLoader.getResource("hia_request.xml")!! - val hiaDom = XMLUtil.parseStringIntoDom(hia.readText()) + val hiaDom = XMLUtil.parseBytesIntoDom(hia.readBytes()) val x: Element = hiaDom.getElementsByTagNameNS( "urn:org:ebics:H004", "OrderDetails" @@ -150,7 +148,7 @@ class EbicsMessagesTest { "ebics_ini_inner_key.xml" ) assertNotNull(file) - XMLUtil.convertStringToJaxb<SignatureTypes.SignaturePubKeyOrderData>(file.readText()) + XMLUtil.convertBytesToJaxb<SignatureTypes.SignaturePubKeyOrderData>(file.readBytes()) } val modulus = jaxbKey.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus @@ -161,8 +159,8 @@ class EbicsMessagesTest { @Test fun testLoadIniMessage() { val classLoader = ClassLoader.getSystemClassLoader() - val text = classLoader.getResource("ebics_ini_request_sample.xml")!!.readText() - XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(text) + val text = classLoader.getResource("ebics_ini_request_sample.xml")!!.readBytes() + XMLUtil.convertBytesToJaxb<EbicsUnsecuredRequest>(text) } @Test @@ -185,14 +183,14 @@ class EbicsMessagesTest { } } } - print(XMLUtil.convertJaxbToString(response)) + print(XMLUtil.convertJaxbToBytes(response).toString()) } @Test fun testLoadHpb() { val classLoader = ClassLoader.getSystemClassLoader() - val text = classLoader.getResource("hpb_request.xml")!!.readText() - XMLUtil.convertStringToJaxb<EbicsNpkdRequest>(text) + val text = classLoader.getResource("hpb_request.xml")!!.readBytes() + XMLUtil.convertBytesToJaxb<EbicsNpkdRequest>(text) } @Test @@ -248,9 +246,8 @@ class EbicsMessagesTest { } } - val str = XMLUtil.convertJaxbToString(htd) - println(str) - assert(XMLUtil.validateFromString(str)) + val bytes = XMLUtil.convertJaxbToBytes(htd) + assert(XMLUtil.validateFromBytes(bytes)) } @@ -308,9 +305,8 @@ class EbicsMessagesTest { }) } - val str = XMLUtil.convertJaxbToString(hkd) - println(str) - assert(XMLUtil.validateFromString(str)) + val bytes = XMLUtil.convertJaxbToBytes(hkd) + assert(XMLUtil.validateFromBytes(bytes)) } @Test @@ -361,11 +357,11 @@ class EbicsMessagesTest { } } - val str = XMLUtil.convertJaxbToString(ebicsRequestObj) - val doc = XMLUtil.parseStringIntoDom(str) + val str = XMLUtil.convertJaxbToBytes(ebicsRequestObj) + val doc = XMLUtil.parseBytesIntoDom(str) val pair = CryptoUtil.generateRsaKeyPair(1024) XMLUtil.signEbicsDocument(doc, pair.private) - val finalStr = XMLUtil.convertDomToString(doc) - assert(XMLUtil.validateFromString(finalStr)) + val bytes = XMLUtil.convertDomToBytes(doc) + assert(XMLUtil.validateFromBytes(bytes)) } }
\ No newline at end of file diff --git a/ebics/src/test/kotlin/EbicsOrderUtilTest.kt b/ebics/src/test/kotlin/EbicsOrderUtilTest.kt index 05f7f72d..c78da738 100644 --- a/ebics/src/test/kotlin/EbicsOrderUtilTest.kt +++ b/ebics/src/test/kotlin/EbicsOrderUtilTest.kt @@ -302,7 +302,7 @@ class EbicsOrderUtilTest { </Permission> </UserInfo> </HTDResponseOrderData> - """.trimIndent() - XMLUtil.convertStringToJaxb<HTDResponseOrderData>(orderDataXml); + """.trimIndent().toByteArray() + XMLUtil.convertBytesToJaxb<HTDResponseOrderData>(orderDataXml); } }
\ No newline at end of file diff --git a/ebics/src/test/kotlin/XmlUtilTest.kt b/ebics/src/test/kotlin/XmlUtilTest.kt index b8639d7a..93ca8bf2 100644 --- a/ebics/src/test/kotlin/XmlUtilTest.kt +++ b/ebics/src/test/kotlin/XmlUtilTest.kt @@ -37,8 +37,7 @@ class XmlUtilTest { @Test fun deserializeConsecutiveLists() { - - val tmp = XMLUtil.convertStringToJaxb<HTDResponseOrderData>(""" + val tmp = XMLUtil.convertBytesToJaxb<HTDResponseOrderData>(""" <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <HTDResponseOrderData xmlns="urn:org:ebics:H004"> <PartnerInfo> @@ -81,7 +80,7 @@ class XmlUtilTest { <OrderTypes>C54 C53 C52 CCC</OrderTypes> </Permission> </UserInfo> - </HTDResponseOrderData>""".trimIndent() + </HTDResponseOrderData>""".trimIndent().toByteArray() ) println(tmp.value.partnerInfo.orderInfoList[0].description) @@ -90,7 +89,7 @@ class XmlUtilTest { @Test fun exceptionOnConversion() { try { - XMLUtil.convertStringToJaxb<EbicsKeyManagementResponse>("<malformed xml>") + XMLUtil.convertBytesToJaxb<EbicsKeyManagementResponse>("<malformed xml>".toByteArray()) } catch (e: javax.xml.bind.UnmarshalException) { // just ensuring this is the exception println("caught") @@ -115,12 +114,12 @@ class XmlUtilTest { @Test fun basicSigningTest() { - val doc = XMLUtil.parseStringIntoDom(""" + val doc = XMLUtil.parseBytesIntoDom(""" <myMessage xmlns:ebics="urn:org:ebics:H004"> <ebics:AuthSignature /> <foo authenticate="true">Hello World</foo> </myMessage> - """.trimIndent()) + """.trimIndent().toByteArray()) val kpg = KeyPairGenerator.getInstance("RSA") kpg.initialize(2048) val pair = kpg.genKeyPair() @@ -155,10 +154,9 @@ class XmlUtilTest { } val signature = signEbicsResponse(response, pair.private) - val signatureJaxb = XMLUtil.convertStringToJaxb<EbicsResponse>(signature) + val signatureJaxb = XMLUtil.convertBytesToJaxb<EbicsResponse>(signature) assertTrue( - XMLUtil.verifyEbicsDocument( XMLUtil.convertJaxbToDocument(signatureJaxb.value), pair.public @@ -168,13 +166,13 @@ class XmlUtilTest { @Test fun multiAuthSigningTest() { - val doc = XMLUtil.parseStringIntoDom(""" + val doc = XMLUtil.parseBytesIntoDom(""" <myMessage xmlns:ebics="urn:org:ebics:H004"> <ebics:AuthSignature /> <foo authenticate="true">Hello World</foo> <bar authenticate="true">Another one!</bar> </myMessage> - """.trimIndent()) + """.trimIndent().toByteArray()) val kpg = KeyPairGenerator.getInstance("RSA") kpg.initialize(2048) val pair = kpg.genKeyPair() @@ -185,8 +183,8 @@ class XmlUtilTest { @Test fun testRefSignature() { val classLoader = ClassLoader.getSystemClassLoader() - val docText = classLoader.getResourceAsStream("signature1/doc.xml")!!.readAllBytes().toString(Charsets.UTF_8) - val doc = XMLUtil.parseStringIntoDom(docText) + val docText = classLoader.getResourceAsStream("signature1/doc.xml")!!.readAllBytes() + val doc = XMLUtil.parseBytesIntoDom(docText) val keyText = classLoader.getResourceAsStream("signature1/public_key.txt")!!.readAllBytes() val keyBytes = Base64.getDecoder().decode(keyText) val key = CryptoUtil.loadRsaPublicKey(keyBytes) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt index 407e2818..0bca13c5 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -101,26 +101,15 @@ private suspend inline fun downloadHelper( ebics2Req.orderParams ) } - logger.trace(initXml) - try { - return doEbicsDownload( - ctx.httpClient, - ctx.cfg, - ctx.clientKeys, - ctx.bankKeys, - initXml, - isEbics3, - tolerateEmptyResult = true - ) - } catch (e: EbicsSideException) { - logger.error(e.message) - /** - * Failing regardless of the error being at the client or at the - * bank side. A client with an unreliable bank is not useful, hence - * failing here. - */ - throw e - } + return doEbicsDownload( + ctx.httpClient, + ctx.cfg, + ctx.clientKeys, + ctx.bankKeys, + initXml, + isEbics3, + tolerateEmptyResult = true + ) } /** @@ -282,7 +271,7 @@ private fun ingestDocument( val acks = parseCustomerAck(xml) for (ack in acks) { when (ack.actionType) { - HacAction.FILE_DOWNLOAD -> logger.trace("$ack") + HacAction.FILE_DOWNLOAD -> logger.debug("$ack") HacAction.ORDER_HAC_FINAL_POS -> { // TODO update pending transaction status logger.debug("$ack") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt index 75b70c42..bd95543b 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -156,9 +156,10 @@ suspend fun doKeysRequestAndUpdateState( KeysOrderType.HIA -> generateHiaMessage(cfg, privs) KeysOrderType.HPB -> generateHpbMessage(cfg, privs) } - val xml = client.postToBank(cfg.hostBaseUrl, req) - if (xml == null) { - throw Exception("Could not POST the ${orderType.name} message to the bank") + val xml = try { + client.postToBank(cfg.hostBaseUrl, req) + } catch (e: Exception) { + throw Exception("Could not POST the ${orderType.name} message to the bank at '${cfg.hostBaseUrl}'", e) } val ebics = parseKeysMgmtResponse(privs.encryption_private_key, xml) if (ebics == null) { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt index cc9da79f..d5ed2795 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -84,10 +84,9 @@ suspend fun fetchBankAccounts( ): HTDResponseOrderData? { val xmlReq = createEbics25DownloadInit(cfg, clientKeys, bankKeys, "HTD") val bytesResp = doEbicsDownload(client, cfg, clientKeys, bankKeys, xmlReq, false) - val xmlResp = bytesResp.toString(Charsets.UTF_8) return try { logger.debug("Fetched accounts: $bytesResp") - XMLUtil.convertStringToJaxb<HTDResponseOrderData>(xmlResp).value + XMLUtil.convertBytesToJaxb<HTDResponseOrderData>(bytesResp).value } catch (e: Exception) { logger.error("Could not parse the HTD payload, detail: ${e.message}") return null @@ -104,7 +103,7 @@ fun createEbics25DownloadInit( bankKeys: BankPublicKeysFile, orderType: String, orderParams: EbicsOrderParams = EbicsStandardOrderParams() -): String { +): ByteArray { val nonce = getNonce(128) val req = EbicsRequest.createForDownloadInitializationPhase( cfg.ebicsUserId, @@ -127,7 +126,7 @@ fun createEbics25DownloadInit( clientKeys.authentication_private_key, withEbics3 = false ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -137,16 +136,19 @@ fun createEbics25DownloadInit( * @param clientKeys user EBICS private keys. * @param transactionId transaction ID of the EBICS communication that * should receive this receipt. + * @param success was the download sucessfully processed * @return receipt request in XML. */ fun createEbics25DownloadReceiptPhase( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, - transactionId: String -): String { + transactionId: String, + success: Boolean +): ByteArray { val req = EbicsRequest.createForDownloadReceiptPhase( transactionId, - cfg.ebicsHostId + cfg.ebicsHostId, + success ) val doc = XMLUtil.convertJaxbToDocument(req) XMLUtil.signEbicsDocument( @@ -154,7 +156,7 @@ fun createEbics25DownloadReceiptPhase( clientKeys.authentication_private_key, withEbics3 = false ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -173,7 +175,7 @@ fun createEbics25DownloadTransferPhase( segNumber: Int, totalSegments: Int, transactionId: String -): String { +): ByteArray { val req = EbicsRequest.createForDownloadTransferPhase( hostID = cfg.ebicsHostId, segmentNumber = segNumber, @@ -186,7 +188,7 @@ fun createEbics25DownloadTransferPhase( clientKeys.authentication_private_key, withEbics3 = false ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -202,10 +204,10 @@ fun createEbics25DownloadTransferPhase( */ fun parseKeysMgmtResponse( clientEncryptionKey: RSAPrivateCrtKey, - xml: String + xml: ByteArray ): EbicsKeyManagementResponseContent? { val jaxb = try { - XMLUtil.convertStringToJaxb<EbicsKeyManagementResponse>(xml) + XMLUtil.convertBytesToJaxb<EbicsKeyManagementResponse>(xml) } catch (e: Exception) { tech.libeufin.nexus.logger.error("Could not parse the raw response from bank into JAXB.") return null @@ -238,7 +240,7 @@ fun parseKeysMgmtResponse( * @param clientKeys set of all the client keys. * @return the raw EBICS INI message. */ -fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): String { +fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { val iniRequest = EbicsUnsecuredRequest.createIni( cfg.ebicsHostId, cfg.ebicsUserId, @@ -246,7 +248,7 @@ fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) clientKeys.signature_private_key ) val doc = XMLUtil.convertJaxbToDocument(iniRequest) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -257,7 +259,7 @@ fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) * @param clientKeys set of all the client keys. * @return the raw EBICS HIA message. */ -fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): String { +fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { val hiaRequest = EbicsUnsecuredRequest.createHia( cfg.ebicsHostId, cfg.ebicsUserId, @@ -266,7 +268,7 @@ fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) clientKeys.encryption_private_key ) val doc = XMLUtil.convertJaxbToDocument(hiaRequest) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -276,7 +278,7 @@ fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) * @param clientKeys set of all the client keys. * @return the raw EBICS HPB message. */ -fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): String { +fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { val hpbRequest = EbicsNpkdRequest.createRequest( cfg.ebicsHostId, cfg.ebicsPartnerId, @@ -286,7 +288,7 @@ fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) ) val doc = XMLUtil.convertJaxbToDocument(hpbRequest) XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt index 16e5daba..796e7809 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -41,16 +41,19 @@ import javax.xml.datatype.DatatypeFactory * @param clientKeys subscriber private keys. * @param transactionId EBICS transaction ID as assigned by the * bank to any successful transaction. + * @param success was the download sucessfully processed * @return the raw XML of the EBICS request. */ fun createEbics3DownloadReceiptPhase( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, - transactionId: String -): String { + transactionId: String, + success: Boolean +): ByteArray { val req = Ebics3Request.createForDownloadReceiptPhase( transactionId, - cfg.ebicsHostId + cfg.ebicsHostId, + success ) val doc = XMLUtil.convertJaxbToDocument(req) XMLUtil.signEbicsDocument( @@ -58,7 +61,7 @@ fun createEbics3DownloadReceiptPhase( clientKeys.authentication_private_key, withEbics3 = true ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -78,7 +81,7 @@ fun createEbics3DownloadTransferPhase( howManySegments: Int, segmentNumber: Int, transactionId: String -): String { +): ByteArray { val req = Ebics3Request.createForDownloadTransferPhase( cfg.ebicsHostId, transactionId, @@ -91,7 +94,7 @@ fun createEbics3DownloadTransferPhase( clientKeys.authentication_private_key, withEbics3 = true ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -108,7 +111,7 @@ fun createEbics3DownloadInitialization( bankkeys: BankPublicKeysFile, clientKeys: ClientPrivateKeysFile, orderParams: Ebics3Request.OrderDetails.BTDOrderParams -): String { +): ByteArray { val nonce = getNonce(128) val req = Ebics3Request.createForDownloadInitializationPhase( cfg.ebicsUserId, @@ -129,7 +132,7 @@ fun createEbics3DownloadInitialization( clientKeys.authentication_private_key, withEbics3 = true ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -149,7 +152,7 @@ fun createEbics3RequestForUploadInitialization( bankkeys: BankPublicKeysFile, clientKeys: ClientPrivateKeysFile, orderService: Ebics3Request.OrderDetails.Service -): String { +): ByteArray { val nonce = getNonce(128) val req = Ebics3Request.createForUploadInitializationPhase( preparedUploadData.transactionKey, @@ -174,7 +177,7 @@ fun createEbics3RequestForUploadInitialization( clientKeys.authentication_private_key, withEbics3 = true ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -193,7 +196,7 @@ fun createEbics3RequestForUploadTransferPhase( clientKeys: ClientPrivateKeysFile, transactionId: String, uploadData: PreparedUploadData -): String { +): ByteArray { val chunkIndex = 1 // only 1-chunk communication currently supported. val req = Ebics3Request.createForUploadTransferPhase( cfg.ebicsHostId, @@ -207,7 +210,7 @@ fun createEbics3RequestForUploadTransferPhase( clientKeys.authentication_private_key, withEbics3 = true ) - return XMLUtil.convertDomToString(doc) + return XMLUtil.convertDomToBytes(doc) } /** @@ -230,8 +233,7 @@ suspend fun submitPain001( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, bankkeys: BankPublicKeysFile, - httpClient: HttpClient, - ebicsExtraLog: Boolean = false + httpClient: HttpClient ) { val orderService: Ebics3Request.OrderDetails.Service = Ebics3Request.OrderDetails.Service().apply { serviceName = "MCT" @@ -248,7 +250,6 @@ suspend fun submitPain001( bankkeys, orderService, pain001xml.toByteArray(Charsets.UTF_8), - ebicsExtraLog ) logger.debug("Payment submitted, report text is: ${maybeUploaded.reportText}," + " EBICS technical code is: ${maybeUploaded.technicalReturnCode}," + diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt index 6e7eff28..9c07aadb 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -126,33 +126,19 @@ fun decryptAndDecompressPayload( * POSTs the EBICS message to the bank. * * @param URL where the bank serves EBICS requests. - * @param msg EBICS message as raw string. - * @return the raw bank response, if the request made it to the - * EBICS handler, or null otherwise. + * @param msg EBICS message as raw bytes. + * @return the raw bank response. */ -suspend fun HttpClient.postToBank(bankUrl: String, msg: String): String? { - logger.debug("POSTing EBICS to: $bankUrl") - val resp: HttpResponse = try { - this.post(urlString = bankUrl) { - expectSuccess = false // avoids exceptions on non-2xx statuses. - contentType(ContentType.Text.Xml) - setBody(msg) - } - } - catch (e: Exception) { - // hard error (network issue, invalid URL, ..) - logger.error("Could not POST to bank at: $bankUrl, detail: ${e.message}") - return null +suspend fun HttpClient.postToBank(bankUrl: String, msg: ByteArray): ByteArray { + logger.debug("POSTing EBICS to '$bankUrl'") + val res = post(urlString = bankUrl) { + contentType(ContentType.Text.Xml) + setBody(msg) } - // Bank was found, but the EBICS request wasn't served. - // Note: EBICS errors get _still_ 200 OK, so here the error - // _should_ not be related to EBICS. 404 for a wrong URL - // is one example. - if (resp.status != HttpStatusCode.OK) { - logger.error("Bank was found at $bankUrl, but EBICS wasn't served. Response status: ${resp.status}, body: ${resp.bodyAsText()}") - return null + if (res.status != HttpStatusCode.OK) { + throw Exception("Invalid response status: ${res.status}") } - return resp.bodyAsText() + return res.readBytes() // TODO input stream } /** @@ -258,14 +244,18 @@ suspend fun postEbics( client: HttpClient, cfg: EbicsSetupConfig, bankKeys: BankPublicKeysFile, - xmlReq: String, + xmlReq: ByteArray, isEbics3: Boolean ): EbicsResponseContent { - val respXml = client.postToBank(cfg.hostBaseUrl, xmlReq) - ?: throw EbicsSideException( + val respXml = try { + client.postToBank(cfg.hostBaseUrl, xmlReq) + } catch (e: Exception) { + throw EbicsSideException( "POSTing to ${cfg.hostBaseUrl} failed", - sideEc = EbicsSideError.HTTP_POST_FAILED + sideEc = EbicsSideError.HTTP_POST_FAILED, + e ) + } return parseAndValidateEbicsResponse( bankKeys, respXml, @@ -307,7 +297,7 @@ suspend fun doEbicsDownload( cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile, bankKeys: BankPublicKeysFile, - reqXml: String, + reqXml: ByteArray, isEbics3: Boolean, tolerateEmptyResult: Boolean = false ): ByteArray { @@ -374,9 +364,10 @@ suspend fun doEbicsDownload( ebicsChunks ) // payload reconstructed, receipt to the bank. + val success = true val receiptXml = if (isEbics3) - createEbics3DownloadReceiptPhase(cfg, clientKeys, tId) - else createEbics25DownloadReceiptPhase(cfg, clientKeys, tId) + createEbics3DownloadReceiptPhase(cfg, clientKeys, tId, success) + else createEbics25DownloadReceiptPhase(cfg, clientKeys, tId, success) // Sending the receipt to the bank. postEbics( @@ -419,8 +410,9 @@ enum class EbicsSideError { */ class EbicsSideException( msg: String, - val sideEc: EbicsSideError -) : Exception(msg) + val sideEc: EbicsSideError, + cause: Exception? = null +) : Exception(msg, cause) /** * Parses the bank response from the raw XML and verifies @@ -433,11 +425,11 @@ class EbicsSideException( */ fun parseAndValidateEbicsResponse( bankKeys: BankPublicKeysFile, - responseStr: String, + resp: ByteArray, withEbics3: Boolean ): EbicsResponseContent { val responseDocument = try { - XMLUtil.parseStringIntoDom(responseStr) + XMLUtil.parseBytesIntoDom(resp) } catch (e: Exception) { throw EbicsSideException( "Bank response apparently invalid", @@ -455,8 +447,8 @@ fun parseAndValidateEbicsResponse( ) } if (withEbics3) - return ebics3toInternalRepr(responseStr) - return ebics25toInternalRepr(responseStr) + return ebics3toInternalRepr(resp) + return ebics25toInternalRepr(resp) } /** @@ -567,7 +559,6 @@ suspend fun doEbicsUpload( bankKeys: BankPublicKeysFile, orderService: Ebics3Request.OrderDetails.Service, payload: ByteArray, - extraLog: Boolean = false ): EbicsResponseContent { val preparedPayload = prepareUploadPayload(cfg, clientKeys, bankKeys, payload, isEbics3 = true) val initXml = createEbics3RequestForUploadInitialization( @@ -577,13 +568,12 @@ suspend fun doEbicsUpload( clientKeys, orderService ) - if (extraLog) logger.debug(initXml) val initResp = postEbics( // may throw EbicsEarlyException - client, - cfg, - bankKeys, - initXml, - isEbics3 = true + client, + cfg, + bankKeys, + initXml, + isEbics3 = true ) if (!areCodesOk(initResp)) throw EbicsUploadException( "EBICS upload init failed", diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt index 9c95b118..507820ce 100644 --- a/nexus/src/test/kotlin/Ebics.kt +++ b/nexus/src/test/kotlin/Ebics.kt @@ -35,7 +35,7 @@ class Ebics { @Test fun iniMessage() = conf { config -> val msg = generateIniMessage(config, clientKeys) - val ini = XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid + val ini = XMLUtil.convertBytesToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid assertEquals(ini.value.header.static.orderDetails.orderType, "INI") // ensures is INI } @@ -43,7 +43,7 @@ class Ebics { @Test fun hiaMessage() = conf { config -> val msg = generateHiaMessage(config, clientKeys) - val ini = XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid + val ini = XMLUtil.convertBytesToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid assertEquals(ini.value.header.static.orderDetails.orderType, "HIA") // ensures is HIA } @@ -51,7 +51,7 @@ class Ebics { @Test fun hpbMessage() = conf { config -> val msg = generateHpbMessage(config, clientKeys) - val ini = XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid + val ini = XMLUtil.convertBytesToJaxb<EbicsUnsecuredRequest>(msg) // ensures is valid assertEquals(ini.value.header.static.orderDetails.orderType, "HPB") // ensures is HPB } // POSTs an EBICS message to the mock bank. Tests @@ -68,9 +68,19 @@ class Ebics { val clientOk = getMockedClient { respondOk("Not EBICS anyway.") } - assertNull(client404.postToBank("http://ignored.example.com/", "ignored")) - assertNull(clientNoResponse.postToBank("http://ignored.example.com/", "ignored")) - assertNotNull(clientOk.postToBank("http://ignored.example.com/", "ignored")) + runCatching { + client404.postToBank("http://ignored.example.com/", "ignored".toByteArray()) + }.run { + val exp = exceptionOrNull()!! + assertEquals(exp.message, "Invalid response status: 404 Not Found") + } + runCatching { + clientNoResponse.postToBank("http://ignored.example.com/", "ignored".toByteArray()) + }.run { + val exp = exceptionOrNull()!! + assertEquals(exp.message, "Network issue.") + } + clientOk.postToBank("http://ignored.example.com/", "ignored".toByteArray()) } // Tests that internal repr. of keys lead to valid PDF. |