ebics.rs (24084B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use std::{borrow::Cow, io::Write as _}; 21 22 use aws_lc_rs::{ 23 digest::Digest, 24 encoding::AsDer, 25 error::KeyRejected, 26 rsa::{self, PrivateDecryptingKey, PublicKey}, 27 }; 28 use compact_str::CompactString; 29 use flate2::write::ZlibDecoder; 30 use jiff::Timestamp; 31 use rand::{RngExt as _, distr::Alphanumeric, seq::IndexedRandom as _}; 32 use reqwest::{ 33 Client, ClientBuilder, StatusCode, 34 header::{CONTENT_TYPE, HeaderValue}, 35 }; 36 use roxmltree::Document; 37 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 38 use sqlx::PgPool; 39 use taler_common::encoding::base64; 40 use thiserror::Error; 41 use tracing::{debug, info, trace}; 42 43 use crate::{ 44 cli::EbicsLogs, 45 config::EbicsHostCfg, 46 crypto::{ 47 decrypt_ebics_e002, decrypt_ebics_e002_key, digest_ebics_order_a006, encrypt_ebics_e002, 48 gen_ebics_e002_key, sign_ebics_a006, 49 }, 50 db::{ebics_first, ebics_register, ebics_remove}, 51 ebics::{ 52 administrative::{HAA, HKD, VersionNumber, hev_msg, parse_haa, parse_hev, parse_hkd}, 53 bts::{ 54 DInit, DTransfer, DataEncryptionInfo, UInit, d_init, d_transfer, parse_d_init, 55 parse_d_transfer, parse_receipt, parse_u_init, parse_u_transfer, receipt, u_init, 56 u_transfer, 57 }, 58 ebics_code::EbicsReturnCode, 59 logger::EbicsLogger, 60 order::Order, 61 }, 62 keys::{BankKeys, ClientKeys}, 63 utils::deflate, 64 xml, 65 xml_sign::{VerifyError, verify_ebics}, 66 }; 67 68 pub mod administrative; 69 pub mod bts; 70 pub mod ebics_code; 71 pub mod key_management; 72 pub mod logger; 73 pub mod order; 74 75 const EBICS_ID_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 76 77 pub fn rand_ebics_id() -> CompactString { 78 let mut rng = rand::rng(); 79 (0..34) 80 .map(|_| *EBICS_ID_ALPHABET.choose(&mut rng).unwrap() as char) 81 .collect() 82 } 83 84 #[derive(Debug, Clone, Copy)] 85 pub enum Phase { 86 Interrupt, 87 Init, 88 Transfer(usize), 89 Process, 90 Receipt, 91 } 92 93 impl std::fmt::Display for Phase { 94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 95 match self { 96 Phase::Interrupt => f.write_str("interrupt"), 97 Phase::Init => f.write_str("init"), 98 Phase::Transfer(i) => write!(f, "transfer{i}"), 99 Phase::Process => f.write_str("process"), 100 Phase::Receipt => f.write_str("receipt"), 101 } 102 } 103 } 104 105 #[derive(Debug)] 106 pub struct EbicsCtx<'a> { 107 pub now: Timestamp, 108 pub order: Cow<'a, Order>, 109 pub phase: Option<Phase>, 110 pub tx_id: Option<CompactString>, 111 } 112 113 impl<'a> EbicsCtx<'a> { 114 pub fn new(order: &'a Order) -> Self { 115 Self { 116 now: Timestamp::now(), 117 order: Cow::Borrowed(order), 118 phase: None, 119 tx_id: None, 120 } 121 } 122 123 pub fn init(self) -> Self { 124 Self { 125 phase: Some(Phase::Init), 126 tx_id: None, 127 ..self 128 } 129 } 130 131 pub fn interrupt(self, id: &str) -> Self { 132 Self { 133 phase: Some(Phase::Interrupt), 134 tx_id: Some( 135 self.tx_id 136 .filter(|it| it != id) 137 .unwrap_or_else(|| id.into()), 138 ), 139 ..self 140 } 141 } 142 143 pub fn transfer(self, id: &str, segment: usize) -> Self { 144 Self { 145 phase: Some(Phase::Transfer(segment)), 146 tx_id: Some( 147 self.tx_id 148 .filter(|it| it != id) 149 .unwrap_or_else(|| id.into()), 150 ), 151 ..self 152 } 153 } 154 155 pub fn process(self, id: &str) -> Self { 156 Self { 157 phase: Some(Phase::Process), 158 tx_id: Some( 159 self.tx_id 160 .filter(|it| it != id) 161 .unwrap_or_else(|| id.into()), 162 ), 163 ..self 164 } 165 } 166 167 pub fn receipt(self, id: &str) -> Self { 168 Self { 169 phase: Some(Phase::Receipt), 170 tx_id: Some( 171 self.tx_id 172 .filter(|it| it != id) 173 .unwrap_or_else(|| id.into()), 174 ), 175 ..self 176 } 177 } 178 } 179 180 impl std::fmt::Display for EbicsCtx<'_> { 181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 182 let Self { 183 order, 184 phase, 185 tx_id, 186 .. 187 } = self; 188 write!(f, "{order}")?; 189 if let Some(phase) = phase { 190 write!(f, " {phase}")?; 191 } 192 if let Some(tx_id) = tx_id { 193 write!(f, " {tx_id}")?; 194 } 195 Ok(()) 196 } 197 } 198 199 #[derive(Debug, thiserror::Error)] 200 pub struct EbicsError { 201 pub ctx: Box<EbicsCtx<'static>>, 202 pub kind: EbicsErrKind, 203 } 204 205 impl std::fmt::Display for EbicsError { 206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 207 let Self { ctx, kind } = self; 208 write!(f, "{ctx}: {kind}") 209 } 210 } 211 212 fn fmt_code( 213 f: &mut std::fmt::Formatter<'_>, 214 technical: &EbicsReturnCode, 215 bank: &EbicsReturnCode, 216 ) -> std::fmt::Result { 217 if technical.is_error() { 218 write!(f, "technical error: {technical}") 219 } else { 220 write!(f, "bank error: {bank}") 221 } 222 } 223 224 pub trait EbicsErrorHelper<T> { 225 fn ctx(self, ctx: &EbicsCtx<'_>) -> Result<T, EbicsError>; 226 } 227 228 impl<T, E: Into<EbicsErrKind>> EbicsErrorHelper<T> for Result<T, E> { 229 fn ctx(self, ctx: &EbicsCtx<'_>) -> Result<T, EbicsError> { 230 self.map_err(|e| e.into().ctx(ctx)) 231 } 232 } 233 234 #[derive(Debug, thiserror::Error)] 235 pub enum EbicsErrKind { 236 #[error(transparent)] 237 Network(#[from] reqwest::Error), 238 239 #[error(transparent)] 240 IO(#[from] std::io::Error), 241 242 #[error("ebics HTTP error {0}")] 243 HTTP(StatusCode), 244 245 #[error(transparent)] 246 XML(#[from] xml::Error), 247 248 #[error("{}", std::fmt::from_fn(|f| fmt_code(f, technical, bank)))] 249 Code { 250 technical: EbicsReturnCode, 251 bank: EbicsReturnCode, 252 }, 253 254 #[error(transparent)] 255 Db(#[from] sqlx::Error), 256 257 #[error(transparent)] 258 Zip(#[from] zip::result::ZipError), 259 260 #[error(transparent)] 261 Signature(#[from] VerifyError), 262 263 #[error(transparent)] 264 DecDe(#[from] DecDeErr), 265 266 #[error("{0}")] 267 Custom(Cow<'static, str>), 268 } 269 270 impl EbicsErrKind { 271 pub fn ctx(self, ctx: &EbicsCtx<'_>) -> EbicsError { 272 EbicsError { 273 ctx: Box::new(EbicsCtx { 274 now: ctx.now, 275 phase: ctx.phase, 276 tx_id: ctx.tx_id.clone(), 277 order: Cow::Owned(ctx.order.as_ref().clone()), 278 }), 279 kind: self, 280 } 281 } 282 } 283 pub struct EbicsResponse<T> { 284 pub technical_code: EbicsReturnCode, 285 pub bank_code: EbicsReturnCode, 286 pub technical_text: CompactString, 287 pub content: Option<T>, 288 } 289 290 impl<T> EbicsResponse<T> { 291 fn ok_or_fail(self) -> Result<T, EbicsErrKind> { 292 if let Some(content) = self.content 293 && !self.technical_code.is_error() 294 && !self.bank_code.is_error() 295 { 296 Ok(content) 297 } else { 298 Err(EbicsErrKind::Code { 299 technical: self.technical_code, 300 bank: self.bank_code, 301 }) 302 } 303 } 304 } 305 306 pub struct EbicsClient<'a> { 307 cfg: EbicsHostCfg<'a>, 308 pub http: Client, 309 logger: EbicsLogger, 310 } 311 312 impl<'a> EbicsClient<'a> { 313 pub fn new(cfg: EbicsHostCfg<'a>, log: EbicsLogs) -> anyhow::Result<Self> { 314 let mut builder = ClientBuilder::new(); 315 if let Some(unix_path) = cfg.unix_path { 316 builder = builder.unix_socket(unix_path); 317 } 318 Ok(Self { 319 cfg, 320 http: builder.build()?, 321 logger: EbicsLogger::new(log.dir)?, 322 }) 323 } 324 325 async fn post_to_bank(&self, xml: String, ctx: &EbicsCtx<'_>) -> Result<Vec<u8>, EbicsError> { 326 self.logger.log_request(ctx, &xml)?; 327 let res = self 328 .http 329 .post(self.cfg.base_url) 330 .header(CONTENT_TYPE, HeaderValue::from_static("application/xml")) 331 .body(xml) 332 .send() 333 .await 334 .ctx(ctx)?; 335 let status = res.status(); 336 if status != StatusCode::OK { 337 self.logger.log_failure(ctx, res).await?; 338 return Err(EbicsErrKind::HTTP(status).ctx(ctx)); 339 } 340 let xml = res.bytes().await.ctx(ctx)?; 341 self.logger.log_response(ctx, &xml)?; 342 Ok(xml.into()) 343 } 344 345 /** POST an EBICS BTS request [xmlReq] using [client] returning a validated and parsed XML response */ 346 pub async fn post_bts<T>( 347 &self, 348 xml: String, 349 ctx: &EbicsCtx<'_>, 350 key: &PublicKey, 351 parse: impl FnOnce(Document) -> xml::Result<EbicsResponse<T>>, 352 ) -> Result<T, EbicsError> { 353 let xml = self.post_to_bank(xml, ctx).await?; 354 let doc = verify_ebics(&xml, key).ctx(ctx)?; 355 let res = parse(doc).ctx(ctx)?; 356 trace!(target: "ebics", 357 "{ctx}: {} {} - {}", 358 res.technical_code, 359 res.bank_code, 360 res.technical_text 361 ); 362 res.ok_or_fail().ctx(ctx) 363 } 364 365 pub async fn hev(&self) -> Result<Box<[VersionNumber]>, EbicsError> { 366 let order = Order::HEV; 367 info!(target: "ebics", "Doing administrative request {order}"); 368 let msg = hev_msg(&self.cfg); 369 let ctx = EbicsCtx::new(&order); 370 let res = self.post_to_bank(msg, &ctx).await?; 371 parse_hev(&res).ctx(&ctx)?.ok_or_fail().ctx(&ctx) 372 } 373 374 pub async fn haa( 375 &self, 376 db: &PgPool, 377 client: &ClientKeys, 378 bank: &BankKeys, 379 peek: bool, 380 ) -> Result<HAA, EbicsError> { 381 self.download( 382 db, 383 client, 384 bank, 385 &Order::HAA, 386 &None, 387 peek, 388 async |content| Ok(parse_haa(&content)?), 389 ) 390 .await 391 } 392 393 pub async fn hkd( 394 &self, 395 db: &PgPool, 396 client: &ClientKeys, 397 bank: &BankKeys, 398 peek: bool, 399 ) -> Result<HKD, EbicsError> { 400 self.download( 401 db, 402 client, 403 bank, 404 &Order::HKD, 405 &None, 406 peek, 407 async |content| Ok(parse_hkd(&content)?), 408 ) 409 .await 410 } 411 412 /** 413 * Performs an EBICS download transaction of [order] between [startDate] and [endDate]. 414 * Download content is passed to [processing] 415 * 416 * It conducts init -> transfer -> processing -> receipt phases. 417 * 418 * Cancellations and failures are handled. 419 */ 420 pub async fn download<T>( 421 &self, 422 db: &PgPool, 423 client: &ClientKeys, 424 bank: &BankKeys, 425 order: &Order, 426 range: &Option<(Timestamp, Timestamp)>, 427 peek: bool, 428 processing: impl AsyncFnOnce(Vec<u8>) -> Result<T, EbicsErrKind>, 429 ) -> Result<T, EbicsError> { 430 let mut ctx = EbicsCtx::new(order); 431 debug!(target: "ebics", "Downloading order {order}{}", std::fmt::from_fn(|f| { 432 if let Some((start, end)) = range { 433 write!(f, " from {start} to {end}")? 434 } 435 Ok(()) 436 })); 437 438 // Close interrupted 439 while let Some(tx_id) = ebics_first(db).await.ctx(&ctx)? { 440 let ctx = EbicsCtx::new(order).interrupt(&tx_id); 441 let xml = receipt(&self.cfg, client, order, &tx_id, false); 442 if let Err(e) = self.post_bts(xml, &ctx, &bank.auth, parse_receipt).await { 443 if !matches!( 444 e.kind, 445 // Transaction already closed or expired - EBICS protocol error 446 EbicsErrKind::Code { 447 technical: EbicsReturnCode::EBICS_TX_UNKNOWN_TXID, 448 .. 449 } | 450 // Transaction already closed or expired - HTTP protocol error for non compliant banks 451 EbicsErrKind::HTTP(StatusCode::BAD_REQUEST) 452 ) { 453 return Err(e); 454 } else { 455 debug!(target: "ebics", "{e}") 456 } 457 } 458 ebics_remove(db, &tx_id).await.ctx(&ctx)?; 459 } 460 461 // Init phase 462 ctx = ctx.init(); 463 let xml = d_init(&self.cfg, bank, client, order, range); 464 let DInit { 465 tx_id, 466 nb_segments, 467 segment, 468 data_encryption_info, 469 } = self.post_bts(xml, &ctx, &bank.auth, parse_d_init).await?; 470 ebics_register(db, &tx_id).await.ctx(&ctx)?; 471 472 // Transfer phase 473 let mut payload = segment; 474 for segment_nb in 2..=nb_segments { 475 ctx = ctx.transfer(&tx_id, segment_nb); 476 let xml = d_transfer(&self.cfg, client, order, nb_segments, segment_nb, &tx_id); 477 let DTransfer { segment, .. } = self 478 .post_bts(xml, &ctx, &bank.auth, parse_d_transfer) 479 .await?; 480 payload.extend(segment); 481 } 482 483 // Processing phase 484 ctx = ctx.process(&tx_id); 485 let payload = decrypt_and_decompress_payload(&client.enc, data_encryption_info, payload) 486 .map_err(|e| { 487 EbicsErrKind::Custom(Cow::Owned(format!("decrypt & decompress: {e}"))).ctx(&ctx) 488 })?; 489 self.logger.log_payload(&ctx, &payload, order.file_type())?; 490 let res = processing(payload).await.ctx(&ctx); 491 492 // Receipt phase 493 ctx = ctx.receipt(&tx_id); 494 let xml = receipt(&self.cfg, client, order, &tx_id, res.is_ok() && !peek); 495 self.post_bts(xml, &ctx, &bank.auth, parse_receipt).await?; 496 ebics_remove(db, &tx_id).await.ctx(&ctx)?; 497 498 res 499 } 500 501 /** 502 * Performs an EBICS upload transaction of [order] using [payload]. 503 * 504 * It conducts init -> upload phases. 505 * 506 * Returns upload orderID 507 */ 508 pub async fn upload( 509 &self, 510 client: &ClientKeys, 511 bank: &BankKeys, 512 order: &Order, 513 payload: &[u8], 514 ) -> Result<CompactString, EbicsError> { 515 debug!(target: "ebics", "Uploading order {order}"); 516 let mut ctx = EbicsCtx::new(order); 517 518 self.logger.log_payload(&ctx, payload, "xml")?; 519 let payload = prepare_upload_payload(&self.cfg, client, bank, payload); 520 521 // Init phase 522 ctx = ctx.init(); 523 let xml = u_init(&self.cfg, bank, client, order, &payload); 524 let UInit { tx_id, order_id } = self.post_bts(xml, &ctx, &bank.auth, parse_u_init).await?; 525 526 // Transfer phase 527 for segment_nb in 1..=payload.nb_segments() { 528 ctx = ctx.transfer(&tx_id, segment_nb); 529 let xml = u_transfer(&self.cfg, client, order, &tx_id, &payload, segment_nb); 530 self.post_bts(xml, &ctx, &bank.auth, parse_u_transfer) 531 .await?; 532 } 533 534 Ok(order_id) 535 } 536 } 537 538 pub struct PreparedUploadData { 539 encrypted_key: Vec<u8>, 540 signature_data: String, 541 digest: Digest, 542 payload: String, 543 } 544 545 impl PreparedUploadData { 546 const CHUNK_SIZE: usize = 1000000; 547 548 pub fn nb_segments(&self) -> usize { 549 self.payload.len().div_ceil(Self::CHUNK_SIZE) 550 } 551 552 pub fn segment(&self, nb: usize) -> &str { 553 let start = (nb - 1) * Self::CHUNK_SIZE; 554 let end = (start + Self::CHUNK_SIZE).min(self.payload.len()); 555 &self.payload[start..end] 556 } 557 } 558 559 #[derive(Debug, Error)] 560 pub enum DecDeErr { 561 #[error(transparent)] 562 Zlib(#[from] std::io::Error), 563 #[error("bank E002 encryption public-key digest mismatch")] 564 Digest, 565 #[error(transparent)] 566 Key(#[from] KeyRejected), 567 #[error("E002 decryption failed")] 568 Decrypt, 569 } 570 571 /** Decrypts and decompresses EBICS BTS payload */ 572 fn decrypt_and_decompress_payload( 573 key_pair: &rsa::KeyPair, 574 encryption_info: DataEncryptionInfo, 575 payload: Vec<u8>, 576 ) -> Result<Vec<u8>, DecDeErr> { 577 let enc_key = PrivateDecryptingKey::from_pkcs8(key_pair.as_der().unwrap().as_ref())?; 578 let tx_key = decrypt_ebics_e002_key(enc_key, &encryption_info.tx_key); 579 let mut decoder = ZlibDecoder::new(Vec::new()); 580 let decrypted = decrypt_ebics_e002(&tx_key, payload).map_err(|_| DecDeErr::Decrypt)?; 581 decoder.write_all(&decrypted)?; 582 Ok(decoder.finish()?) 583 } 584 585 /** Signs, encrypts and format EBICS BTS payload */ 586 fn prepare_upload_payload( 587 cfg: &EbicsHostCfg, 588 client: &ClientKeys, 589 bank: &BankKeys, 590 payload: &[u8], 591 ) -> PreparedUploadData { 592 let digest = digest_ebics_order_a006(payload); 593 594 // Generate ephemeral transaction key 595 let (tx_key, encrypted_key) = gen_ebics_e002_key(&bank.enc); 596 597 // Compress and encrypt order signature 598 let signature_data = { 599 let signed = sign_ebics_a006(digest.as_ref(), &client.sign); 600 let inner_signed_xml = xml!( 601 "UserSignatureData" "xmlns"="http://www.ebics.org/S002" { 602 "OrderSignatureData" { 603 "SignatureVersion": "A006", 604 "SignatureValue": base64::fmt(signed), 605 "PartnerID": cfg.partner_id, 606 "UserID": cfg.user_id 607 } 608 } 609 ); 610 let deflated = deflate(inner_signed_xml.as_bytes()); 611 let encrypted = encrypt_ebics_e002(&tx_key, deflated); 612 base64::encode(encrypted) 613 }; 614 615 // Compress and encrypt payload 616 let payload = { 617 let deflated = deflate(payload); 618 let encrypted = encrypt_ebics_e002(&tx_key, deflated); 619 base64::encode(encrypted) 620 }; 621 PreparedUploadData { 622 encrypted_key, 623 signature_data, 624 digest, 625 payload, 626 } 627 } 628 629 #[derive(Debug)] 630 pub struct TxCheckResult { 631 pub concurrent_fetch_and_fetch: bool, 632 pub concurrent_fetch_and_submit: bool, 633 pub concurrent_submit_and_submit: bool, 634 pub idempotent_close: bool, 635 } 636 637 /** 638 * Test EBICS implementation's transactions semantic: 639 * - Can two fetch transactions run concurrently ? 640 * - Can a fetch & submit transactions run concurrently ? 641 * - Can two submit transactions run concurrently ? 642 * - Is closing a submit transaction idempotent 643 */ 644 pub async fn tx_check( 645 ebics: &EbicsClient<'_>, 646 db: &PgPool, 647 client: &ClientKeys, 648 bank: &BankKeys, 649 fetch: &Order, 650 submit: &Order, 651 ) -> anyhow::Result<TxCheckResult> { 652 let mut result = TxCheckResult { 653 concurrent_fetch_and_fetch: false, 654 concurrent_fetch_and_submit: false, 655 concurrent_submit_and_submit: false, 656 idempotent_close: false, 657 }; 658 659 let ctx = EbicsCtx::new(fetch).init(); 660 let DInit { tx_id, .. } = ebics 661 .post_bts( 662 d_init(&ebics.cfg, bank, client, fetch, &None), 663 &ctx, 664 &bank.auth, 665 parse_d_init, 666 ) 667 .await?; 668 ebics_register(db, &tx_id).await?; 669 { 670 let ctx = EbicsCtx::new(fetch).init(); 671 match ebics 672 .post_bts( 673 d_init(&ebics.cfg, bank, client, fetch, &None), 674 &ctx, 675 &bank.auth, 676 parse_d_init, 677 ) 678 .await 679 { 680 Ok(DInit { tx_id, .. }) => { 681 ebics_register(db, &tx_id).await?; 682 result.concurrent_fetch_and_fetch = true; 683 let ctx = ctx.receipt(&tx_id); 684 ebics 685 .post_bts( 686 receipt(&ebics.cfg, client, fetch, &tx_id, false), 687 &ctx, 688 &bank.auth, 689 parse_receipt, 690 ) 691 .await?; 692 ebics_remove(db, &tx_id).await?; 693 } 694 Err(e) => { 695 if !matches!(e.kind, EbicsErrKind::Code { .. }) { 696 return Err(e.into()); 697 } else { 698 debug!(target: "testing", "concurrent_fetch_and_fetch {e}") 699 } 700 } 701 } 702 } 703 704 { 705 let ctx = EbicsCtx::new(submit).init(); 706 let random_string: String = rand::rng() 707 .sample_iter(&Alphanumeric) 708 .take(2000000) 709 .map(char::from) 710 .collect(); 711 let payload = prepare_upload_payload(&ebics.cfg, client, bank, random_string.as_bytes()); 712 match ebics 713 .post_bts( 714 u_init(&ebics.cfg, bank, client, submit, &payload), 715 &ctx, 716 &bank.auth, 717 parse_u_init, 718 ) 719 .await 720 { 721 Ok(UInit { tx_id, .. }) => { 722 result.concurrent_fetch_and_submit = true; 723 let ctx = ctx.transfer(&tx_id, 1); 724 ebics 725 .post_bts( 726 u_transfer(&ebics.cfg, client, fetch, &tx_id, &payload, 1), 727 &ctx, 728 &bank.auth, 729 parse_u_transfer, 730 ) 731 .await?; 732 let ctx = EbicsCtx::new(submit).init(); 733 if let Err(e) = ebics 734 .post_bts( 735 u_init(&ebics.cfg, bank, client, submit, &payload), 736 &ctx, 737 &bank.auth, 738 parse_u_init, 739 ) 740 .await 741 { 742 if !matches!(e.kind, EbicsErrKind::Code { .. }) { 743 return Err(e.into()); 744 } else { 745 debug!(target: "testing", "concurrent_submit_and_submit {e}") 746 } 747 } else { 748 result.concurrent_submit_and_submit = true; 749 } 750 } 751 Err(e) => { 752 if !matches!(e.kind, EbicsErrKind::Code { .. }) { 753 return Err(e.into()); 754 } else { 755 debug!(target: "testing", "concurrent_fetch_and_submit {e}") 756 } 757 } 758 } 759 } 760 761 // Close first fetch 762 let ctx = ctx.receipt(&tx_id); 763 ebics 764 .post_bts( 765 receipt(&ebics.cfg, client, fetch, &tx_id, false), 766 &ctx, 767 &bank.auth, 768 parse_receipt, 769 ) 770 .await?; 771 772 ebics_remove(db, &tx_id).await?; 773 774 // Close first fetch again 775 let ctx = ctx.interrupt(&tx_id); 776 if let Err(e) = ebics 777 .post_bts( 778 receipt(&ebics.cfg, client, fetch, &tx_id, false), 779 &ctx, 780 &bank.auth, 781 parse_receipt, 782 ) 783 .await 784 { 785 debug!(target: "testing", "idempotent_close {e}") 786 } else { 787 result.idempotent_close = true 788 } 789 790 Ok(result) 791 } 792 793 #[derive(Debug, Serialize, Deserialize, Clone, Default)] 794 pub struct TaskStatus { 795 #[serde(serialize_with = "ser_micros", deserialize_with = "de_micros", default)] 796 pub last_successfull: Option<Timestamp>, 797 #[serde(serialize_with = "ser_micros", deserialize_with = "de_micros", default)] 798 pub last_trial: Option<Timestamp>, 799 } 800 801 fn ser_micros<S: Serializer>(key: &Option<Timestamp>, serializer: S) -> Result<S::Ok, S::Error> { 802 key.map(|it| it.as_microsecond()).serialize(serializer) 803 } 804 805 fn de_micros<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Timestamp>, D::Error> { 806 Option::<i64>::deserialize(deserializer)? 807 .map(Timestamp::from_microsecond) 808 .transpose() 809 .map_err(|e| serde::de::Error::custom(e.to_string())) 810 }