summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-02-13 07:47:46 +0100
committerAntoine A <>2024-02-13 07:47:46 +0100
commitf83a22f243a315fbc4b4086155c2401845cf334c (patch)
treeaf264e686c5fc657fba060e210bdfdb3454cbf72
parent7f84324c0712097513becd7c4675e50d4194833a (diff)
downloadlibeufin-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.kt10
-rw-r--r--ebics/src/main/kotlin/EbicsOrderUtil.kt4
-rw-r--r--ebics/src/main/kotlin/XMLUtil.kt56
-rw-r--r--ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt9
-rw-r--r--ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt7
-rw-r--r--ebics/src/test/kotlin/EbicsMessagesTest.kt50
-rw-r--r--ebics/src/test/kotlin/EbicsOrderUtilTest.kt4
-rw-r--r--ebics/src/test/kotlin/XmlUtilTest.kt22
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt31
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt7
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt38
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt31
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt78
-rw-r--r--nexus/src/test/kotlin/Ebics.kt22
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.