Skip to content

Fix TypeError on null TransactionKey in multi-segment BTD download (v3.2.x regression from v3.0.1) - #247

Open
noppel wants to merge 1 commit into
ebics-api:3.xfrom
noppel:fix/null-transaction-key-and-base64-limit
Open

noppel wants to merge 1 commit into
ebics-api:3.xfrom
noppel:fix/null-transaction-key-and-base64-limit

Conversation

@noppel

@noppel noppel commented Oct 11, 2026

Copy link
Copy Markdown

Multi-segment BTD download fails with TypeError on null TransactionKey (v3.2.x regression from v3.0.1)

Affected versions: v3.2.0, v3.2.1 (and HEAD of 3.x).
Last known good: v3.0.1.
EBICS protocol: H005 (EBICS 3.0), service BTD (also DAR).

Summary

In EBICS downloads with multiple segments (<NumSegments> ≥ 2), the Transfer-phase response for segment #2 onwards does not contain a <TransactionKey> element — that key is only transmitted in the Initialization response and is reused by the client for all subsequent segments. This is EBICS-spec-compliant behaviour from the bank server.

ResponseHandler::extractDownloadSegment() still calls retrieveH00XTransactionKey() on every segment response, and in v3.2.x hands the result (null) straight to Base64Encoder::decode(string $data): string — which throws

TypeError: EbicsApi\Ebics\Services\Processor\Base64Encoder::decode():
Argument #1 ($data) must be of type string, null given,
called in Handlers/ResponseHandler.php on line 157

v3.0.1 handled this transparently because it used the native base64_decode() (which accepts null and returns ''). The switch to the stricter Base64Encoder service in v3.2.0 regressed the Transfer-phase path.

Reproduce

Any executeDownloadOrder against a UBS Keyport host that returns ≥ 2 segments. Example with a BTD/DAR/documents service that has multiple PDFs pending:

$ctx = new BTDContext();
$ctx->setServiceName('DAR'); $ctx->setScope('CH');
$ctx->setContainerType('ZIP'); $ctx->setMsgName('documents');
$ctx->setParserFormat('zip_files');
$order = new BTD($ctx, new DateTime('-30 days'), new DateTime());
$client->executeDownloadOrder($order);  // TypeError

Impact

Observed behaviour at UBS (Schweiz):

  • Initialization response arrives with <NumSegments>34</NumSegments> and the first <OrderData> segment.
  • Transfer request for segment 2 goes out, arrives at UBS, UBS returns a valid 000000 / EBICS_OK response — but without <TransactionKey>.
  • extractDownloadSegment crashes before any user data can be used.
  • Downstream effect: the download transaction is left in an unclean state. UBS considers the segments as delivered (because the HTTP transport finished), so retrying doesn't re-fetch the same documents on their side.

For callers who have swallowed the TypeError in a generic catch (as our daemon unfortunately did), this manifests as silent "no data" for weeks while the bank register fills up.

Related second issue (same root cause area)

Once the TypeError is fixed, large aggregated downloads next hit:

RuntimeException: Encoded data exceeds maximum length of 10485760 bytes,
got 34897344 bytes. Use buffered analog.

The hard limit Base64Encoder::MAX_ENCODED_LENGTH = 10 MB (and MAX_RAW_LENGTH ≈ 7.5 MB) is new in v3.2.x. A single DAR response bundling ~30 PDFs easily exceeds that. The Use buffered analog hint in the error message points to a buffered variant that doesn't appear to exist in the current Services/Processor/ tree (only Base64Encoder.php is shipped). v3.0.1 had no such limit.

Suggested fix

// Handlers/ResponseHandler.php — extractInitializationSegment & extractDownloadSegment
$transactionKeyEncoded = $this->retrieveH00XTransactionKey($response);
$transactionKey = $transactionKeyEncoded !== null
    ? $this->base64Service->decode($transactionKeyEncoded)
    : '';

The empty-string fallback matches v3.0.1 behaviour exactly. For the Base64 size limit, either raise the constants to accommodate typical DAR-sized responses (≥ 64 MB), or document and ship the buffered variant that Use buffered analog promises.

Environment

  • PHP 8.5.10
  • Bank: UBS Switzerland AG (Keyport, EBXUBSCH)
  • Our composer-patches workaround for anyone else hit by this: both patches in a single file, applied via cweagans/composer-patches.

Happy to send a PR if preferred.

@noppel
noppel force-pushed the fix/null-transaction-key-and-base64-limit branch from e15d469 to 74b6ca8 Compare October 11, 2026 21:03
…64 limits

In EBICS downloads with multiple segments the Transfer-phase response
does not carry a <TransactionKey> element - the key is only sent in the
Initialization response and reused on the client side. v3.0.1 tolerated
the null via native base64_decode(); v3.2.0 switched to Base64Encoder
with strict 'string $data' type-hint, causing a TypeError on every
multi-segment download.

The second fix raises MAX_ENCODED_LENGTH / MAX_RAW_LENGTH to 512 MB /
384 MB respectively, so aggregated DAR responses (dozens of PDFs in one
response) do not hit the ceiling until a true buffered variant exists.
@noppel
noppel force-pushed the fix/null-transaction-key-and-base64-limit branch from 74b6ca8 to db41207 Compare October 11, 2026 21:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant