Migrating from 0.15

Every 0.15 to 0.16 wire change and API replacement, grouped by task.

On this page

0.16 replaces the public API in one step, without deprecation shims. The new shape: a Profile preset is the only source of security settings; As4 and As2 clients (built with LocalParty, Partners and trust anchors) return Verified<Receipt>; one Inbound tower Service receives, verifies, deduplicates and answers; keys sit behind the async Signer and Decrypter traits; there is one Error.

0.15 and 0.16 cannot exchange encrypted AS4 messages: upgrade every asx instance in a trading relationship together.

Wire changes a 0.15 peer will see

Change0.150.16Consequence
SwA encryption shapea standalone xenc:EncryptedData document per MIME part, outside wsse:SecurityWSS SwA 1.1 §5.5: xenc:EncryptedKey and one xenc:EncryptedData per part (Attachment-Content-Only, CipherReference to the cid:) in wsse:Security; parts are application/octet-stream IV ‖ ciphertext ‖ taglockstep: neither side decrypts the other (0.16 answers EBMS:0103); WSS4J peers (phase4, Holodeck, Domibus) interoperate
Ordercompress → encrypt → sign (the cid: digests covered ciphertext)compress → sign → encrypt (digests cover the compressed plaintext); inbound decrypts only after verifyingresends reproduce every digest
Every AS4 preset encryptssign-only possible (default encrypt: false)always sign and encrypt (eDelivery 1.15 §3.2.6, BDEW §2.2.6.2.2)sign-only partners have no equivalent
ECDSA ds:SignatureValueASN.1 DERXMLDSig 1.1 r ‖ s (64 octets on P-256 and BrainpoolP256r1); DER is refusedlockstep for EC (BDEW) peers; RSA unchanged
eb:MessageIdcarried wsu:Id="as4-message-id" (invalid per the ebMS3 schema)no attributeeb:Messaging digest changes; phase4 no longer answers EBMS:0009
PKIPath token referenceSTR to the #X509PKIPathv1 token without a token typewsse11:TokenType="…#X509PKIPathv1" (WS-I BSP R5215); the last certificate of the path is the signerWSS4J accepts BDEW signatures
eb:Propertyvalue in a value attribute; compression properties read from eb:Propertiesvalue as element text; compression properties from the signed eb:PartProperties only; a value attribute or a part property repeated in one eb:PartInfo is refused
Message idsAS2 sent Message-ID: <id@asx>AS2 <id> with your RFC 2822 id-left@id-right; AS4 ids are RFC 2822 msg-ids toobuild ids as "{id}@{your-domain}"
Unsigned AS4 messageanswered EBMS:0101answered EBMS:0103 PolicyNoncompliance
BDEW unconfigured sendersrefused unless pinnedaccepted when the verified SM-PKI certificate carries the claimed MP-ID as subject OU (BDEW 1.2 §2.3.2, §2.3.4.4; RzÜ AS4 2.6 §5.2)MP-ID only in the CN is refused, also at build()
eb:Timestamp window300 s both ways300 s ahead, 7 days behind (the dedup retention)a retry within 7 days (BDEW: 72 h) is a duplicate, not a replay
BDEW headercaller-set action, roles, conversation id, agreementalways gzip; fixed Action, roles and AgreementRef (profiles); empty ConversationId; no eb:MessageProperties; PKIPath tokensetting a conversation id is refused
Content-IDsrandompart<N>.<hash>@asx from message id and part index; deterministic gzipidentical resends
Duplicatesreturned to the embedderAS4: fresh signed receipt; AS2: processed/warning: duplicate-document
In flightembedder-chosen statusHTTP 503
Handler refusalembedder-chosenAS4 EBMS:0202 DeliveryFailure (retried), or EBMS:0004 (not retried) for Reject::permanent; AS2 processed/error: unexpected-processing-errordead-lettered messages: return Reject::permanent
Test servicedelivered like a business messagereceipted, never delivered
AS2 inboundunsigned or unencrypted accepted, verifier-dependentrefused: insufficient-message-security; RFC 5402-compressed content that is not a MIME entity: decompression-failed
AS2 MDN requestconfigurablealways a signed MDN with sha-256, synchronous unless send_async_mdn
Peppol file namesContent-Disposition allowednot written; Payload::named refused (Peppol AS4 §4.10)
Decryption failuresdistinguishable errorsone indistinguishable answer (EBMS:0102 / decryption-failed)
Inbound XML Encryption parametersKDF, curve and OAEP parameters ignoredrequired and checked before the key is used; rsa-1_5, rsa-oaep-mgf1p, mgf1sha1, CBC refused
BDEW path switchnot implementedrequestSwitch/confirmSwitch sent and accepted (BDEW 1.2 §2.2.8)
RevocationOCSP modes, failure modes, static CRL lists (revocation_crl_pems); AS2 checked the signer onlythe profile's rule (BDEW: CRL-DP with the RzÜ §5.4 three-day rule; others: CRL-DP then OCSP) over the signer's whole path, via the client's HttpClient; Builder::crl_signer for indirect CRLs (security)no revocation settings, no stapled responses

Cargo

0.150.16
asx-rs = { version = "0.15", features = ["as4", "client", "server", "interop-strict"] }asx-rs = "0.16" (default as2, as4, http-client), or default-features = false, features = ["as4", "http-client"]
use asx_rs::…use asx::… (the package is still asx-rs)
feature crypto, compressionpart of as2/as4
feature client, async-ocsphttp-client
feature server (and the axum dependency)removed: Inbound is a tower Service; mount it with Router::route_service
feature dnspart of peppol
feature trace, prometheus, opentelemetryspans are always on; metrics for counters, an exporter in your application
feature testingremoved: the additive test-util feature (asx::testkit) in dev-dependencies
feature interop-strict, interop-relaxedremoved: strict only; per-partner Quirks

Configure

0.150.16
SessionContext, SessionContextBuilder, SessionMetadata, CorrelationScope, CertHandle, PartnerCredentials (credentials)LocalParty (id, party type, signer, decrypters, TLS identity), Partner (id, type, endpoint, signing and encryption certificates), Builder::trust_anchor; the internal session is built by the client
As4SendPolicy, As4PushPolicy, As4ReceiptPolicy, As4PullPolicy, As2SendPolicy, As2ReceivePolicy, As2SignaturePolicy, As2MicAlgorithm, SmimeCipher, WsSecOutboundKeyInfoProfile, FragmentScopePolicy, OcspMode, OcspFailureModeProfile::bdew(), peppol(), edelivery_1_15(), entsog(), as2(); Profile::max_message_bytes to tighten
pmode (PMode, PModeRegistry, PModeSecurity, MepType, PayloadPackagingMode)Profile + Partner + Message
interop (ProfileStack and the profile-stack types), InteropMode, InteropExceptionPolicy, InteropExceptionCode, InteropDecision, enforce_exception, evaluate_exception_guardrailProfile; Partner::quirk(Quirk::…) for one absent element (see quirks)
As4ReceiptPolicy::relaxed()Partner::quirk(Quirk::ReceiptWithoutNonRepudiation); an unsigned receipt is never accepted
relaxed AS2 MDN acceptancePartner::quirk(Quirk::UnsignedMdn)
presets (StrictRuntimeBootstrap, StrictRuntimeBootstrapToken, DeploymentTopology)build(): every configuration is validated once against the profile
As4SignerPins, As4SignerPinMap, signer_pinsPartner::signing_cert, repeated for rollover
DEFAULT_MAX_BODY_BYTES, enforce_payload_limit, RouterConfig (body limit)Profile::max_message_bytes; a body timeout is a tower_http layer on the route
CryptoAdmissionControl, ASX_BLOCKING_CRYPTO_CONCURRENCYremoved: the blocking pool is sized from available parallelism

Keys

0.150.16
PEM fields on CertHandle, with_signing_material(cert, key)KeyPair::from_pem(chain, key) as LocalParty::signer
decryption key PEM on the push policyLocalParty::decrypter(KeyPair), repeatable for rollover; mandatory (every preset encrypts)
signing (SigningKeyProvider, PemSigningKeyProvider)the async Signer trait (ECDSA returns DER; the library converts)
no public decryption seamthe async Decrypter trait with KeyOp
ClientIdentity, TransportConfig::with_client_identityLocalParty::tls_identity(KeyPair)
crypto, wssec, soap_builder, as2_smime, ocsp_client, ocsp_discovery, fingerprint, compression modulescrate-private; Certificate, KeyPair, Signer, Decrypter are the public surface

See keys and certificates.

Send

0.150.16
as4::send_sync, send_async, send_sync_fragmented, As4SendRequest, As4SendCredentials, As4AdditionalPayload, As4SendOutput, package_as_mime, MimePackageBuilder, MimeAttachment, mime_packagingAs4::send(Message::to(&partner).service(..).payload(Payload::new(..)))
As4HttpTransport, TransportConfig, HttpSendOutcome, As4SendAndVerifyOutcome, verify_sync_response, As4SyncSignal, As4VerifiedReceipt, As4NonRepudiation, As4NriReference, As4ReceivedError, As4ErrorSignal, As4ErrorCode, As4ErrorSeverity, ParsedAs4Receipt, DEFAULT_MAX_RECEIPT_BYTESinside As4::send, which returns Verified<Receipt>; a partner eb:Error is ErrorKind::PartnerError with ebms_code()
PayloadFilename, PayloadFilenameError, PayloadInputPayload::named; an invalid name now fails the send
as2::send_sync, send_async, As2SendRequest, As2SendCredentials, As2SendOutput, MimeEnvelope, payload_content_type, As2HttpTransportAs2::send(as2::Message::to(&partner, payload)) → Verified<Receipt>
receive_with_mdn_with_reliability, As2ReceiveMdnRequest, As2ReceiveMdnOutput, As2MdnMode, As2MdnDedup, ParsedMdn, correlate_async_mdnsynchronous MDNs: inside As2::send; asynchronous: send_async_mdn → PendingMdn, then verify_async_mdn
trace_context, output.traceparentremoved: propagate from your tracing layer
RetryScheduler, DeliveryOutcome, RetryDecision, reliabilitythe application schedules retries from Error::kind() and is_retryable(), resending the same Message (same id and timestamp)
smp (SmpClient, SmpConfig, SmpEndpoint, SmpLookupRequest, SmpSignaturePolicy), bdxl (SmlDiscovery, NaptrRecord, BdxlResolver, StaticBdxlResolver, HickoryBdxlResolver, bdxl_dns_name, legacy_cname_host, select_smp_base_url)peppol::Smp::{production, test}(http, anchor).lookup(..) → Partner; the SMP signature is always verified
sbdh, mlscrate-private: put the complete document in the Payload

Receive

0.150.16
receive_push, receive_push_sync, receive_push_fragment_aware, receive_push_sync_fragment_aware, As4ReceivePush, As4ReceivePushSync, As4ReceivePushRequest, As4ReceivePushOutput, As4ReceivePushProgress, As4ReceiveOutcome, As4ReceivedPayload, As4Ordered, ParsedAs4UserMessage, ParsedWsAddressingHeaders, SoapEnvelopeAs4::inbound(dedup, handler); the handler receives Verified<UserMessage> (message_id, from, service, action, payloads, path_switch)
generate_receipt, generate_receipt_with_nri, generate_signed_receipt_with_nri, generate_receipt_for_output, generate_signed_receipt_for_output, generate_error_signal, As4ReceiptCredentialsautomatic: the Inbound answers with a receipt signed by the local signer in the profile's token form, or the error of the response table
As4Verifier, As4WsSecVerifier, InsecureBypassAs4Verifier, *_with_custom_verifier, verifier_sealremoved: there is one verifier, and it cannot be replaced or bypassed
test_service helpersMessage::ping; inbound pings are answered automatically
receive_sync, receive_async, receive_from_ingress, As2IngressReceiveRequest, As2InboundResult, validate_as2_version_headerAs2::inbound(dedup, handler) with Verified<Received>
generate_mdn, As2GeneratedMdn, As2MdnSigningCredentialsautomatic: the Inbound answers with a signed MDN
As2TrustVerifier, AsyncAs2TrustVerifier, CmsSmimeTrustVerifier, SyncToAsyncTrustVerifier, TrustResult, TrustVerifierSealPartner::signing_cert + Builder::trust_anchor
receive_stream, receive_stream_with_metrics, receive_stream_with_metrics_and_audit, stream_receive, spool_key (SpoolEncryptionKeyProvider, StaticSpoolKey), spool_policy, spool_runtime_utils (profile_name), SpoolEncryption, SpoolLifecyclePolicy, ReceivedBodyHandle, wire (StreamBodyPolicy, StreamLimits, StreamReadMetrics)removed: the Inbound reads the body up to max_message_bytes
transport (As2HttpIngress, As4HttpIngress, as2_ingress_from_http, as4_ingress_from_http, As4IngressReceivePushSyncRequest, HttpRequest, HttpHeaders)Inbound takes an http::Request<B>
server (as2_router, as4_router, RouterConfig, As2AxumHandler, As4AxumHandler, HandlerOutcome)axum::Router::new().route_service(path, inbound); handler Ok/Err(Reject)
lifecycle (UntrustedBytes, StructurallyParsed, CryptographicallyVerified, ContentDecrypted, DomainReady, TrustEvidence, SignatureVerification, DecryptionMaterial)Verified<T>, which only the library constructs
pull: pull, pull_store, As4PullStore, As4PullAuthorization, As4PullRequestCredentials, As4GeneratePullRequestPolicy, As4ReceivePullRequest, As4ReceivePullOutput, As4QueuedPullMessage, enqueue_pull_with_reliability, receive_pull_with_reliability, As4PullEnqueueOutcome, As4PullQueueOverflowPolicy, generate_pull_requestremoved: no One-Way/Pull
split/join: large_message, As4FragmentJoiner, As4JoinProgress, As4JoinedLargeMessage, As4SplitFragmentOutputremoved: no split/join
ordering: conversation_gate, coordination, As4ConversationOrderGate, ConversationGuard, ConversationTurnHandle, ConversationOrderGate, As4TopologyCoordinationremoved: order in the application if your process needs it

Store

0.150.16
storage (DedupStorage with is_durable/cluster_safe, claim → DedupVerdict, accept/abandon(key)), DedupClaimDedupStore: claim(key) → Claim::{Claimed(ClaimToken), Duplicate, InFlight}, accept(key, &token), abandon(key, &token); settle only when the token still matches (compare-and-set)
TtlDedupStorage, reliability::InMemoryDedupBackendMemoryDedup (not durable)
drive_dedup_future, drive_sync_future, BoxFuture in storageasx::BoxFuture; settlement is internal
ReconciliationStorage and the reconciliation types, DeadLetterSinkremoved: the application's outbox decides
dedup keys of your own format64-hex digests of a stable format; existing rows do not carry over

See persistence.

Observe

0.150.16
observability (EventBus, subscriptions, AsxEvent, MetricsSink, PrometheusMetricsSink, OtelMetricsSink)tracing spans under target asx; feature metrics with an exporter in your application (observability)
DurableAuditSink, AuditEvent, replay cursorsAuditSink (durability, async record(&AuditRecord)) installed with Builder::audit
fail_closed_audit_eventsthe sink's declared AuditDurability
AsxError, ErrorCode, ErrorContextError: kind(), is_retryable(), ebms_code(), mdn_disposition(), evidence()

Test

0.150.16
features = ["testing"]features = ["test-util"] in dev-dependencies (testing)
fixtures::generate_self_signed_*asx::testkit::pki::TestCa (sm_pki().bdew_party(mp_id) puts the MP-ID in the OU)
as4::mock_endpoint::MockAs4Endpoint, As4HttpTransport::new_for_localhost_testingasx::testkit::loopback::Loopback mounting a real partner client's Inbound
storage::conformance, DurableInMemoryDedupBackendasx::testkit::dedup::conformance, asx::testkit::audit::conformance
EventBus::new_for_testingasx::testkit::trace::TraceCapture, asx::testkit::audit::MemoryAuditSink

Internal helpers that became private

escape_xml, constant_time_eq and crypto_permit: crate-private, no replacement needed.

Recipes and tooling

Removed: just interop-matrix, just fuzz-gate, profile-lint, profile-diff, profile-coverage, fixtures-validate. cargo run -p xtask -- example-pki examples/pki writes the README examples' key material.