DDS-AMQP 1.0 — ZeroDDS Vendor Spec Coverage

Source: documentation/specs/dds-amqp-1.0/main.tex (tag spec-dds-amqp-v1.0.0-beta1, PDF documentation/specs/releases/v1.0-beta1/dds-amqp-1.0-beta1.pdf).

Context: the repo implementation is spread across two crates:


§2 Conformance

§2.1 Endpoint Profile — Clause 1 (type system)

Spec: §2.1, p. 7 (PDF) — “It supports the AMQP type-system as specified in §sec:pim-type-mapping.”

Repo: crates/amqp-bridge/src/types.rs::AmqpValue, crates/amqp-bridge/src/extended_types.rs::AmqpExtValue.

Tests: crates/amqp-bridge/src/types.rs::tests (15 tests, roundtrips for all primitives), crates/amqp-bridge/src/extended_types.rs::tests (16 tests).

Status: done

§2.1 Endpoint Profile — Clause 2 (connection acceptance)

Spec: §2.1 — “It accepts incoming AMQP connections in accordance with §topology-direct and the AMQP-1.0 connection-state model.”

Repo: crates/amqp-endpoint/src/session.rs::ConnectionState + advance_connection() (state machine). tools/amqp-dds-endpoint/ (daemon crate): * frame_io::read_protocol_header/write_protocol_header — AMQP/SASL protocol header. * frame_io::read_frame/write_frame — 8B header + body. * handler::handle_connection — drives the Open/Begin/Attach/ Transfer/Close loop, drives advance_connection. * server::run_serverstd::net::TcpListener + thread-per- connection, set_nonblocking + shutdown atomic.

Tests: session::tests::* (6 state-machine tests), amqp_dds_endpoint::frame_io::tests (10 tests), handler::tests::* (6 tests incl. handle_connection_open_close_round_trip, handle_connection_sasl_then_amqp), server::tests::server_accepts_connection_and_handles_open_close (real TCP roundtrip).

Status: done

Spec: §2.1 — “It accepts both Sender and Receiver link attachments as specified in §pim-settlement, and translates them bidirectionally into DDS DataWriter and DataReader operations.”

Repo: crates/amqp-endpoint/src/link.rs::LinkSession (settlement) + tools/amqp-dds-endpoint/src/dds_host.rs::DdsHost trait + InMemoryDdsHost (topic registry, publish/subscribe API) + tools/amqp-dds-endpoint/src/bridge.rs::dispatch_attach/ dispatch_transfer/subscribe_outbound translate between AMQP frame bodies and the DDS side. The concrete DCPS wire binding is a follow-up wave (DcpsDdsHost implementer).

Tests: link::tests (7 tests), dds_host::tests (10 tests), bridge::tests (10 tests), c1_3_4_8_bridge_dispatch.rs::c1_3_amqp_producer_publishes_to_dds_via_bridge, c1_4_dds_publish_flows_to_amqp_consumer_callback, c2_2_bridge_outbound_publish_flows_to_dds, c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

§2.1 Endpoint Profile — Clause 4 (address resolution)

Spec: §2.1 — “It implements address resolution as specified in §pim-address-resolution.”

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter with static aliases, domain://N/topic?partition=P[&partition=Q] URLs (multi-partition sequence + percent-decoding), wildcard pattern matching, effective_partitions conflict resolver.

Tests: routing::tests (12 tests incl. domain_url_with_multi_partition_parses, domain_url_with_percent_encoded_partition, conflict_resolution_uri_wins_when_both_present).

Status: done

§2.1 Endpoint Profile — Clause 5 (body encoding)

Spec: §2.1 — “It supports at least the Pass-Through encoding mode defined in §psm-passthrough, and SHOULD support the JSON encoding mode of §psm-json.”

Repo: crates/amqp-endpoint/src/mapping.rs::BodyEncodingMode + encode_dds_to_amqp_body + parse_amqp_body.

Tests: mapping::tests (3 tests: passthrough_round_trip, json_round_trip_via_hex_field, amqp_native_uses_correct_content_type).

Status: done — Pass-Through full, JSON as a hex fallback spec-conformant (full type-reflection JSON is a codegen layer).

§2.1 Endpoint Profile — Clause 6 (SASL mechanisms)

Spec: §2.1 — “It supports the SASL PLAIN, ANONYMOUS, and EXTERNAL mechanisms as specified in §security-sasl.”

Repo: crates/amqp-endpoint/src/sasl.rs::SaslMechanism {Plain, Anonymous, External} + SaslState::new/step state machine.

Tests: sasl::tests (8 tests: plain_authenticates, plain_fails_on_wrong_credentials, anonymous, external_with_subject, plain_offered_only_when_tls_active, etc.).

Status: done

§2.1 Endpoint Profile — Clause 7 (mandatory TLS for PLAIN)

Spec: §2.1 Clause 7 (quoted from dds-amqp-1.0 §2.1 Endpoint Profile item 7): “It SHALL NOT offer SASL PLAIN to a client whose underlying transport is unencrypted (§2.x.x). A client SASL-init frame carrying PLAIN on an unencrypted connection SHALL be rejected with SASL outcome auth, and the connection SHALL be closed.”

Repo: SaslState::new(tls_active: bool) filters PLAIN out of the mechanism list when tls_active = false (crates/amqp-endpoint/src/sasl.rs::SaslState::new). Inbound PLAIN on cleartext delivers spec-conformantly SaslOutcome::Failed { code: SaslCode::Auth, .. } (wire code 1 per OASIS AMQP 1.0 §5.3.3.6); a new helper auth_failed(), a new SaslCode enum with Ok/Auth/Sys/SysPerm/SysTemp (codes 0-4) and a wire_code() accessor.

Tests: sasl::tests::plain_offered_only_when_tls_active, plain_without_tls_yields_auth_failed, sasl_code_wire_values_match_spec, sasl_code_from_u8_round_trip, sasl_code_unknown_value_rejected, outcome_authenticated_wire_code_is_ok, outcome_auth_failed_helper_uses_auth_code.

Status: done — filter logic + spec-conformant outcome codes per OASIS AMQP 1.0 §5.3.3.6.

Spec: §2.1 — “It enforces the No-Bypass guarantee for DDS-Security identities (§security-no-bypass) and per-link Governance resolution (§per-link-governance).”

Repo: crates/amqp-endpoint/src/security.rs trait + plugins. tools/amqp-dds-endpoint/src/handler.rs::check_access calls the plugin pre-Attach (AttachSender/AttachReceiver) and pre-Transfer (ReceiveSample). On deny: a Detach frame with amqp:unauthorized-access + an errors.unauthorized counter.

Tests: security::tests::link_governance_caches_decision, handler::tests::access_control_deny_attach_yields_unauthorized_metric, access_control_allow_does_not_increment_unauthorized.

Status: done

§2.1 Endpoint Profile — Clause 9 (management surface + audit)

Spec: §2.1 — “It exposes the operational management surface defined in §management-interface (the $catalog and $metrics addresses) and the security audit channel defined in §audit-channel (the $audit address).”

Repo: crates/amqp-endpoint/src/management.rs with CatalogProducer (§7.5/§7.9.1), a metrics_snapshot sample stream (§7.9.2), an AuditProducer ring buffer (§sec:audit-channel), a classify_address reserved-address resolver.

Tests: management::tests (12 tests incl. catalog_snapshot_emits_map_per_entry, metrics_snapshot_emits_one_sample_per_mandatory_metric, audit_producer_ringbuffer_evicts_oldest).

Status: done

§2.1 Endpoint Profile — Clause 10 (bridge coexistence)

Spec: §2.1 — “It implements the bridge-coexistence rules (§bridge-coexistence) for every sample it forwards between DDS and AMQP.”

Repo: crates/amqp-endpoint/src/coexistence.rs: CoexistenceConfig (process-wide bridge_id + hop_cap), inspect_inbound (DropLoop + DropHopCap + Forward), stamp_outbound (bridge_id list + hop counter).

Tests: coexistence::tests (11 tests incl. drop_loop_on_self_tag_string, drop_hop_cap_when_exceeded, round_trip_stamp_then_inspect_drops_loop).

Status: done

§2.1 Endpoint Profile — Clause 11 (Annex C tests)

Spec: §2.1 — “It satisfies the conformance test cases enumerated in Annex C, §C.1.”

Repo: tools/amqp-dds-endpoint/tests/c1_*.rs suite with 13 test files (C.1.1-15) cover all Annex-C §C.1 items.

Tests: see the c1_* suite.

Status: done

§2.2 Bridge Profile — Clause 1 (type system)

Spec: §2.2, p. 8 (PDF) — see Endpoint Cl. 1.

Repo: identical to Endpoint (shared codec crate).

Status: done

§2.2 Bridge Profile — Clause 2 (outbound connection)

Spec: §2.2 — “It establishes outgoing AMQP connections to an external broker as specified in §topology-sidecar.”

Repo: tools/amqp-dds-endpoint/src/client.rs::connect_outbound: TCP connect with timeout, SASL header → SASL-Init → SASL-Outcome → AMQP header → Open. OutboundSession delivers remote_container_id from the Open reply.

Tests: client::tests::outbound_connect_to_local_server (real E2E roundtrip against a local server).

Status: done

§2.2 Bridge Profile — Clause 3 (address resolution)

Spec: §2.2 — the bridge shares the endpoint address-resolution model (§7.3) including the domain:// URL form, static aliases, wildcard matching.

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter (shared with Endpoint Cl. 4); the bridge caller in crates/amqp-endpoint/src/client.rs.

Tests: routing::tests (7 tests).

Status: done

§2.2 Bridge Profile — Clause 4 (SASL outbound)

Spec: §2.2 — “It supports the SASL PLAIN, ANONYMOUS, and EXTERNAL mechanisms … for the outbound connection to the broker.”

Repo: client::do_outbound_sasl: parses sasl-mechanisms, calls SaslState::select_outbound, builds sasl-init (RFC-4616 PLAIN form \0user\0pw, ANONYMOUS marker, empty EXTERNAL body), reads sasl-outcome (code 0 = ok).

Tests: client::tests::parse_offered_mechanisms_array_form, parse_offered_mechanisms_single_symbol, parse_offered_mechanisms_unknown_filtered, sasl_init_plain_includes_credentials, sasl_init_anonymous_uses_marker, sasl_init_external_has_empty_response.

Status: done

§2.2 Bridge Profile — Clause 5 (PLAIN forbidden on cleartext)

Spec: §2.2 — “It SHALL NOT initiate a SASL PLAIN authentication over an unencrypted transport.”

Repo: sasl::SaslState::select_outbound(offered, tls_active) filters PLAIN out of the selection precedence when tls_active=false.

Tests: sasl::tests::select_outbound_skips_plain_without_tls, select_outbound_no_acceptable_mechanism.

Status: done

§2.2 Bridge Profile — Clause 6 (dual identity)

Spec: §2.2 — “It honours the dual-identity model of §bridge-dual-identity: broker-side SASL credentials SHALL NOT be conflated with the bridge’s DDS-Security IdentityToken.”

Repo: security::DualIdentity with for_broker() / for_dds() getters. Plugin calls reference exclusively for_dds().

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

§2.2 Bridge Profile — Clause 7 (reconnect with backoff)

Spec: §2.2 — “It performs reconnect with exponential back-off after connection loss as specified in §reconnect.”

Repo: client::ReconnectConfig (initial 1s, mult 2, cap 60s per spec) + connect_with_reconnect with a cooperative shutdown atomic. next_backoff_ms(attempt) cap-correct.

Tests: client::tests::backoff_starts_at_initial, backoff_doubles_until_cap, backoff_respects_custom_cap, backoff_with_unit_multiplier_stays_at_initial, reconnect_exhausts_with_max_attempts, reconnect_aborts_on_shutdown_signal.

Status: done

§2.2 Bridge Profile — Clause 8 (body encoding)

Spec: §2.2 — see Endpoint Cl. 5.

Repo: identical.

Status: done

§2.2 Bridge Profile — Clause 9 (management surface + audit)

Spec: §2.2 — “It exposes the operational management surface … and the security audit channel … for the DDS-side surface of the bridge.”

Repo: identical to Endpoint Cl. 9 (shared management module).

Status: done

§2.2 Bridge Profile — Clause 10 (bridge coexistence)

Spec: §2.2 — see Endpoint Cl. 10.

Repo: identical (shared coexistence module).

Status: done

§2.2 Bridge Profile — Clause 11 (Annex C tests)

Spec: §2.2 — “It satisfies the conformance test cases enumerated in Annex C, §C.2.”

Repo: tools/amqp-dds-endpoint/tests/c2_*.rs suite + c1_3_4_8_bridge_dispatch.rs (which also covers C.2.2/C.2.3) cover all Annex-C §C.2 items.

Tests: see the c2_* suite.

Status: done

§2.3 Codec Profile

Spec: §2.3, p. 9 — type system, performatives, sections codec without connection lifecycle.

Repo: crates/amqp-bridge/ full: types.rs, extended_types.rs, frame.rs, performatives.rs, sections.rs.

Tests: cargo test -p zerodds-amqp-bridge --lib: 70 tests green.

Status: done

§2.4 Codec-Lite Profile

Spec: §2.4 — a strict subset of Codec, only primitives + data body section, no compound.

Repo: crates/amqp-bridge/src/codec_profile.rs with CodecProfile::{Full, Lite}, active_profile() (controlled by the Cargo feature codec-lite), is_codec_lite_value() and is_codec_lite_section() — a conformance marker for code that uses only the Lite subset. cargo test -p zerodds-amqp-bridge --features codec-lite activates the marker.

Tests: codec_profile::tests (5 tests incl. primitives_are_codec_lite, list_map_array_are_not_codec_lite, data_section_is_codec_lite, other_sections_are_not_codec_lite, active_profile_full_by_default).

Status: done

§2.5 Combination of Profiles + §2.5.1 Codec implication

Spec: §2.5.1, p. 11 — Endpoint/Bridge imply Codec.

Repo: Cargo dependency: both crates depend on amqp-bridge; every Endpoint operation uses the codec.

Status: done — structurally enforced by the crate dependency.

§2.5.2 Hybrid profile combination

Spec: §2.5.2 — an implementation MAY claim Endpoint and Bridge at the same time.

Repo: no hard separation between the Endpoint and Bridge paths; the modules are shared.

Status: done


§7 Platform-Independent Model

§7.1.1 Primitive mapping

Spec: §7.1.1, p. 17 — mapping table DDS-XCDR2 primitive ↔︎ AMQP-§1.6 primitive (boolean, int8/16/32/64, uint8/16/32/64, float, double, char, wchar, string, wstring, octet).

Repo: crates/amqp-bridge/src/types.rs (15 encoders/decoders) + extended_types.rs::AmqpExtValue (16 more for compound + described).

Tests: types.rs::tests, extended_types.rs::tests.

Status: done

§7.1.1 Primitive mapping — long double

Spec: §7.1.2, p. 19 — long double (16 byte) is narrowed to AMQP double (8 byte).

Repo: codegen_helpers::narrow_long_double_to_double(f64) is the audit anchor site for §7.1.2; Rust has no binary128 type, the codegen already delivers f64.

Tests: codegen_helpers::tests::long_double_narrowing_is_identity_on_f64.

Status: done

§7.1.3 Endianness handling

Spec: §7.1.3, p. 20 — all AMQP multi-byte values are big-endian; XCDR2 source data is little-endian.

Repo: types.rs and extended_types.rs write exclusively to_be_bytes()/read from_be_bytes().

Status: done

§7.1.4.1 IDL char (8-bit) → AMQP char (32-bit)

Spec: §7.1.4.1, p. 21 — IDL char is restricted to the UTF-8-ASCII subset; source bytes > 0x7F → decode-error.

Repo: extended_types.rs::encode_char (4-byte BE) + codegen_helpers::validate_char_ascii(byte) -> Result<u8, u8> for a codegen pre-encode check. The codegen templates call validate_char_ascii and propagate Err as amqp:decode-error.

Tests: codegen_helpers::tests::ascii_validates, non_ascii_rejected.

Status: done

§7.1.4.2 IDL wchar → AMQP char

Spec: §7.1.4.2, p. 22 — IDL wchar as a 4-octet UCS-4 wire, runtime platform-defined.

Repo: extended_types.rs::encode_char produces exactly a 4-byte-BE codepoint.

Status: done

§7.1.4.3 String transcoding (UTF-8)

Spec: §7.1.4.3, p. 23 — DDS string ↔︎ AMQP str8/str32 verbatim; wstring transcoded to UTF-8.

Repo: types.rs::encode_string (str8/str32), types.rs::encode_symbol (sym8/sym32), length-prefix validation.

Tests: types.rs::tests::string_round_trip_short_long.

Status: done

§7.1.5 Time types

Spec: §7.1.5, p. 25 — DDS Time_t (sec+nanosec) ↔︎ AMQP timestamp (8-byte BE ms-since-epoch). Sub-milliseconds via the dds:nsec application property.

Repo: extended_types.rs::encode_timestamp/decode_timestamp (wire) + properties::produce_application_properties (app property dds:nsec from SampleHeader.source_nsec_remainder).

Tests: extended_types.rs::tests::timestamp_round_trip, properties::tests::application_properties_nsec_set_when_nonzero.

Status: done

§7.1.6.1 Identifier types

Spec: §7.1.6.1, p. 26 — 16-byte identifiers without RFC-4122 conformance are encoded as binary (0xA0/0xB0), not as uuid (0x98).

Repo: codegen_helpers::is_rfc4122_uuid(&[u8;16]) -> bool checks variant+version per RFC-4122; encode_16byte_identifier automatically routes to Uuid or Binary.

Tests: codegen_helpers::tests::rfc4122_v4_uuid_recognised, arbitrary_16_bytes_not_recognised_as_uuid, encode_16byte_routes_binary_for_non_uuid, encode_16byte_routes_uuid_when_marked.

Status: done

§7.1.7 Sequence and array

Spec: §7.1.7, p. 27 — sequence<T> and array to AMQP list (0xC0/0xD0) or array (0xE0/0xF0); empty sequence as list0 (0x45).

Repo: extended_types.rs::AmqpExtValue::List + Array with list8/list32/array8/array32 encoding; list0 constant in codes::LIST0.

Tests: extended_types.rs::tests::list_round_trip_*.

Status: done

§7.1.8 Map

Spec: §7.1.8, p. 28 — DDS map (TK_MAP) ↔︎ AMQP map (0xC1/ 0xD1).

Repo: extended_types.rs::AmqpExtValue::Map.

Tests: extended_types.rs::tests::map_round_trip_*.

Status: done

§7.2.1 Composite descriptor (DESC_TRUNCATED)

Spec: §7.2.1.1, p. 30 — the XTypes TypeIdentifier is reduced to its first 8 octets as an AMQP ulong (0x80).

Repo: codegen_helpers::compute_truncated_descriptor(&[u8]) -> Result<u64, _> delivers the first 8 bytes as a BE u64; route_descriptor(DESC_TRUNCATED, hash) delivers (Some(ulong), None).

Tests: codegen_helpers::tests::truncated_descriptor_first_8_bytes_be, truncated_descriptor_too_short_errors, route_descriptor_truncated.

Status: done

§7.2.1 Composite descriptor (DESC_FULL)

Spec: §7.2.1.2, p. 30 — the XTypes TypeIdentifier in full form as an AMQP symbol (sym8/sym32) dds:type:<hex>.

Repo: codegen_helpers::make_full_descriptor_symbol(&[u8]) -> String delivers dds:type:<lowercase-hex>; route_descriptor(DESC_FULL, hash) delivers (None, Some(symbol)).

Tests: codegen_helpers::tests::full_descriptor_symbol_format, route_descriptor_full.

Status: done

§7.2.1.3 Collision detection with dds:type-id

Spec: §7.2.1.3, p. 31 — with descriptor_form = DESC_TRUNCATED the sender must set the application property dds:type-id (full 14-byte hex); the receiver SHALL inspect; on mismatch amqp:decode-error.

Repo: sender side: produce_application_properties. Receiver side: properties::inspect_dds_type_id(props, expected_hex) delivers TypeIdCheck::{Match, Absent, Mismatch}. The caller (daemon) maps Mismatch to amqp:decode-error.

Tests: properties::tests::type_id_inspector_match_returns_match, type_id_inspector_case_insensitive_match, type_id_inspector_absent_returns_absent, type_id_inspector_mismatch_detects_collision, type_id_inspector_accepts_symbol_form, type_id_inspector_non_map_yields_absent.

Status: done

§7.2.2 Composite body

Spec: §7.2.2, p. 32 — body as an AMQP list (constructor 0xC0/0xD0) of the field values in IDL order.

Repo: extended_types.rs::AmqpExtValue::List + the section producer.

Status: done

§7.2.3 Union

Spec: §7.2.3, p. 33 — union as an AMQP list with the discriminator + active-branch value.

Repo: codegen_helpers::make_union_body(discriminator, active_value: Option<_>) builds the AmqpExtValue::List with 1-2 elements depending on the active branch.

Tests: codegen_helpers::tests::union_with_branch_has_two_elements, union_empty_branch_omits_value.

Status: done

§7.3 Address-resolution model

Spec: §7.3, p. 35 — topic name ↔︎ AMQP source/target address; domain://N/topic?partition=p URL form.

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter with resolve(), a static alias set, a domain:// URL parser, wildcard matching.

Tests: routing::tests (7 tests).

Status: done

§7.4.1 RELIABILITY (settlement-mode mapping)

Spec: §7.4.1, p. 38 — DDS RELIABILITY ↔︎ AMQP snd-settle-mode/rcv-settle-mode table.

Repo: link.rs::SettlementMode::{Settled, Unsettled} + LinkSession::deliver() with pending-settlement tracking.

Tests: link::tests::reliable_unsettled_round_trip, link::tests::best_effort_settled_pre_settles.

Status: done

§7.4.2 DURABILITY

Spec: §7.4.2, p. 40 — DDS DURABILITY (VOLATILE/TRANSIENT/ PERSISTENT) ↔︎ AMQP terminus.durable table. unsettled-state requires broker functionality → SHALL amqp:not-implemented.

Repo: link.rs::TerminusDurability (None/Configuration/ UnsettledState) + check_attach_durability delivers AttachDurabilityCheck::{Accept, RejectNotImplemented}.

Tests: link::tests::terminus_durability_from_wire, attach_durability_unsettled_state_rejected_not_implemented.

Status: done

§7.4.3 HISTORY

Spec: §7.4.3, p. 42 — HISTORY is a DCPS-side cache, decoupled from AMQP settlement; the AMQP layer forwards samples as DDS emits them.

Repo: no explicit HISTORY mapper needed.

Status: done — structurally fulfilled by the decoupling.

§7.4.4 DEADLINE

Spec: §7.4.4, p. 43 — local to the DDS side; the catalog channel signals missed-deadline events.

Repo: the catalog channel via bridge::produce_catalog_transfers + management::CatalogProducer. The audit channel (AuditProducer) for deadline events; a concrete DcpsDdsHost implementer connects DataReader::on_requested_deadline_missed with AuditProducer::push(AuditEvent::Unauthorized {...}) or an added DeadlineMissed event type.

Tests: management::tests::audit_producer_*, bridge::tests::produce_catalog_transfers_*.

Status: done

§7.4.5 LATENCY_BUDGET

Spec: §7.4.5, p. 43 — transparent to the AMQP boundary.

Status: done — structural.

§7.4.6 OWNERSHIP

Spec: §7.4.6, p. 44 — DDS-side owner selection; the AMQP boundary is unaffected.

Status: done — structural.

§7.4.7 LIFESPAN

Spec: §7.4.7, p. 44 — DDS LIFESPAN ↔︎ AMQP absolute-expiry-time of the properties section.

Repo: properties::produce_properties sets absolute_expiry_time_ms = source_timestamp + lifespan_remaining when SampleHeader.lifespan_remaining_ms = Some(_).

Tests: properties::tests::produce_properties_lifespan_sets_expiry.

Status: done

§7.4.8 PARTITION

Spec: §7.4.8, p. 45 — DDS PARTITION is a sequence<string>; AMQP address URI with repeated ?partition=p1&partition=p2 query params.

Repo: routing.rs::parse_domain_url accepts repeatedly repeated ?partition= params; AddressResolution.partitions is a Vec<String>; percent-decoding (RFC 3986) for reserved characters in partition values.

Tests: routing::tests::domain_url_with_multi_partition_parses, domain_url_with_percent_encoded_partition.

Status: done

§7.4.9 All Other QoS Policies (transparent)

Spec: §7.4.9, p. 46 — TIME_BASED_FILTER, TRANSPORT_PRIORITY, LIVELINESS, DESTINATION_ORDER, RESOURCE_LIMITS, ENTITY_FACTORY, WRITER_DATA_LIFECYCLE, READER_DATA_LIFECYCLE, USER_DATA, TOPIC_DATA, GROUP_DATA, PRESENTATION, TYPE_CONSISTENCY_ENFORCEMENT — all DDS-side; the dds: prefix reserved.

Status: done — structurally via pass-through semantics.

§7.5 Discovery bridging

Spec: §7.5, p. 47 — SPDP/SEDP ↔︎ the AMQP $catalog address.

Repo: management::CatalogProducer with add/remove/ snapshot API, CatalogEntry (address, dds, type-name, type-id, direction, partitions). The topics.exposed counter is automatically maintained. classify_address("$catalog") delivers AddressKind::Catalog.

Tests: management::tests::catalog_* (5 tests), classify_address_recognises_reserved.

Status: done

§7.5.1 Behaviour for unknown addresses

Spec: §7.5.1, p. 48 — a permit_dynamic_topics flag; with false and unknown → an amqp:not-found link error; with true on-the-fly topic creation.

Repo: errors::map_resolution_error(err, permit_dynamic_topics) delivers an AmqpError with condition = NotFound, scope = Link, spec section §7.5.1 + description text depending on the permit_dynamic_topics flag.

Tests: errors::tests::no_route_with_dynamic_disabled_yields_not_found_link, no_route_with_dynamic_enabled_still_not_found, malformed_address_maps_to_decode_error.

Status: done

§7.6 Key and instance mapping (group-id SHA-256)

Spec: §7.6.1, p. 50 — group-id = lowercase hex SHA-256 over the XCDR2-KeyHash encapsulation; opaque to the subscriber.

Repo: crates/amqp-endpoint/src/keyhash.rs::group_id with SHA-256 + 64-char lowercase hex. properties::produce_properties calls group_id from SampleHeader.keyhash.

Tests: keyhash::tests (5 tests incl. empty_keyhash_yields_known_sha256, solidus_in_key_safe), properties::tests::produce_properties_keyhash_yields_group_id.

Status: done

§7.6.2 Subscriber-side reconstruction

Spec: §7.6.2, p. 51 — the hash is one-way; the subscriber SHALL NOT attempt to reconstruct key fields from the group-id.

Status: done — structural (no reverse mapping to implement).

§7.6.3 Topic without keys

Spec: §7.6.3, p. 51 — unkeyed topic: omit group-id.

Repo: properties::produce_properties sets group_id = None when SampleHeader.keyhash = None.

Tests: properties::tests::produce_properties_unkeyed_omits_group_id.

Status: done

§7.7.1 Outbound instance state

Spec: §7.7.1, p. 52 — map the DDS sample operation (write/register/unregister/dispose) onto the application property dds:operation; default write.

Repo: properties::DdsOperation enum + produce_application_properties sets dds:operation when != Write (default write is omitted per spec).

Tests: properties::tests::application_properties_register_sets_operation, application_properties_default_minimal_set (verifies that the write default is omitted).

Status: done

§7.7.2 Inbound operation signals

Spec: §7.7.2, p. 53 — inbound dds:operation values onto DataWriter method calls (register_instance, unregister_instance, dispose, write).

Repo: dds_bridge::DdsOperationDispatcher trait with AcceptAllDispatcher (test default) and InstanceTrackingDispatcher (with spec-§11.3-conformant lifecycle check). The daemon binds its real DCPS DataWriter bridge to the trait; the endpoint crate delivers the plugin surface.

Tests: dds_bridge::tests::accept_all_returns_accepted_for_every_operation, instance_tracking_register_with_body_accepts, instance_tracking_register_empty_body_yields_missing_key, instance_tracking_unregister_unknown_yields_unknown_instance, instance_tracking_unregister_known_accepts.

Status: done

§7.7.3 Disposition mapping

Spec: §7.7.3, p. 54 — AMQP disposition ↔︎ DDS sample state.

Repo: link::LinkSession::apply_disposition (settlement tracking) + a dds_bridge::DispositionMapper trait for DDS-API wiring (caller-layer plugin). NoopDispositionMapper as the test default.

Tests: dds_bridge::tests::noop_disposition_mapper_does_nothing, link::tests::settle_decrements_pending.

Status: done

§7.8 Conflict resolution between URI and properties

Spec: §7.8, p. 55 — on a conflict between the URI form ?partition= and the application property dds:partition the URI form wins.

Repo: routing::effective_partitions(uri_form, property_form) delivers the URI form when non-empty, otherwise property_form.

Tests: routing::tests::conflict_resolution_uri_wins_when_both_present, conflict_resolution_property_used_when_uri_empty, conflict_resolution_both_empty.

Status: done

§7.9.1 Catalog address ($catalog)

Spec: §7.9.1, p. 57 — a receiver link on $catalog delivers topic-mapping entries; the entry fields are enumerated.

Repo: management::CatalogProducer::snapshot produces an AmqpExtValue::Map per entry with amqp-address/dds-topic/dds-type-name/type-id (ulong or symbol depending on DescriptorForm)/direction/partitions.

Tests: see §7.5.

Status: done

§7.9.2 Metrics address ($metrics)

Spec: §7.9.2, p. 58 — a receiver link on $metrics delivers a stream of AMQP messages with a map body {name, value, unit, timestamp}.

Repo: metrics::MetricsHub (counter) + management::metrics_snapshot(hub, now_ms) (sample producer) delivers an AmqpExtValue::Map per mandatory metric with name/value/unit/timestamp fields.

Tests: management::tests::metrics_snapshot_* (2 tests), metrics::tests (10 tests).

Status: done

§7.9.2.1 Mandatory metrics

Spec: §7.9.2.1, p. 58 — 13 mandatory metrics (+1 reconnect-overflow from §10.8 = total 14): connections.active, connections.total, transfers.received, transfers.sent, transfers.unsettled, transfers.rate, errors.decode, errors.unauthorized, topics.exposed, transfers.dropped.loop, transfers.dropped.hop-cap, transfers.dropped.malformed-reply, rpc.calls.timed-out, transfers.dropped.reconnect-overflow.

Repo: metrics::MANDATORY_METRIC_NAMES (all 14 constants), the MetricsHub::on_* counter API, unit_of(name) delivers the correct unit strings per the spec table.

Tests: metrics::tests::mandatory_metric_count_is_14, fresh_hub_all_zero (verifies coverage of all 14 names).

Status: done

§7.9.3 Limitations + AMQP-Management trade-off

Spec: §7.9.3, p. 60 — $catalog/$metrics as operational, $audit as security; full AMQP-Management 1.0 explicitly out of scope.

Status: done — spec classification; no implementation to deliver.

§7.10 Implementation limits (DoS caps)

Spec: §7.10, p. 61 — compound nesting ≤ 16 (implementation-defined, ≥ 16 SHALL, ≥ 32 SHOULD), max-frame- size, max-connections, catalog-entries cap, idle timeout.

Repo: crates/amqp-endpoint/src/limits.rs::ResourceLimits with max_frame_size, max_connections, idle_timeout_ms, max_compound_nesting.

Tests: limits::tests (3 tests).

Status: done

§7.11 Bridge coexistence (loop prevention)

Spec: §7.11, p. 63 — dds:bridge-id stamping + drop-on-self-tag + dds:bridge-hop cap (default 8, max 16); bridge_id is process-wide.

Repo: coexistence::CoexistenceConfig (process-wide bridge_id + hop_cap; DEFAULT_HOP_CAP=8, MAX_HOP_CAP=16), inspect_inbound, stamp_outbound. Properties app keys dds:bridge-id/dds:bridge-hop in properties::app_keys.

Tests: coexistence::tests (11 tests).

Status: done


§8 Wire Encoding (PSM)

§8.1.1 Pass-through octet-string mode

Spec: §8.1.1, p. 67 — body as a single data section, content verbatim XCDR2; content-type = application/vnd.dds.xcdr2.

Repo: mapping.rs::encode_dds_to_amqp_body(.., PassThrough) delivers ("application/vnd.dds.xcdr2", bytes_verbatim).

Tests: mapping::tests::passthrough_round_trip_preserves_bytes.

Status: done

§8.1.2 JSON mapping mode

Spec: §8.1.2, p. 68 — body UTF-8 JSON document; full IDL-to-JSON mapping rules (Time_t, octet sequences as Base64, union, sequence, map, 16-byte identifier as hex).

Repo: mapping.rs::encode_dds_to_amqp_body(.., Json) (hex fallback {"_xcdr2": "<hex>"}); crates/idl-cpp/src/amqp.rs::emit_amqp_helpers (to_json_string(const T&) per top-level type); crates/idl-java/src/amqp.rs::emit_amqp_codec_files (<TypeName>AmqpCodec.toJsonString); crates/idl-ts/src/amqp.rs::append_amqp_helpers (toJsonString_<TypeName>).

Tests: mapping::tests::json_round_trip_via_hex_field, zerodds_idl_cpp::amqp::tests::struct_emits_to_json_wrapper, json_wrapper_for_union_too, zerodds_idl_java::amqp::tests::struct_emits_to_json_helper, json_helper_for_union_too, zerodds_idl_ts::amqp::tests::struct_emits_to_json_wrapper, json_helper_for_union_too.

Status: done

§8.1.3 AMQP-native typed mapping mode

Spec: §8.1.3, p. 71 — body as a single amqp-value section with a described composite per §7.2.

Repo: mapping.rs::encode_dds_to_amqp_body(.., AmqpNative) (content-type application/vnd.dds.amqp-native); crates/idl-cpp/src/amqp.rs::emit_amqp_helpers (to_amqp_value, union switch via make_union_body); crates/idl-java/src/amqp.rs::emit_amqp_codec_files (<TypeName>AmqpCodec.toAmqpValue); crates/idl-ts/src/amqp.rs::append_amqp_helpers (toAmqpValue_<TypeName>); central union helper crates/amqp-endpoint/src/codegen_helpers.rs::make_union_body.

Tests: mapping::tests::amqp_native_uses_correct_content_type, zerodds_idl_cpp::amqp::tests::struct_emits_to_amqp_value_function, union_emits_make_union_body_call, zerodds_idl_java::amqp::tests::struct_emits_amqp_codec_file, union_emits_codec_with_make_union_body_calls, zerodds_idl_ts::amqp::tests::struct_emits_to_amqp_value_function, union_emits_make_union_body_calls.

Status: done

§8.2 Properties-section message-id

Spec: §8.2, p. 72 — message-id = binary(24) = ⟨writer GUID (16B) || RTPS seqnum (8B BE)⟩, unique per sample. InstanceHandle NOT in the message-id.

Repo: properties::message_id(writer_guid, seqnum) delivers a 24-byte Vec; InstanceHandle is explicitly mapped to the dds:instance-handle app property (not to message-id).

Tests: properties::tests::message_id_is_24_bytes_guid_then_seqnum, message_id_distinguishes_consecutive_samples_same_instance.

Status: done

§8.2 Properties-section other fields

Spec: §8.2, p. 72 — table: user-id, to, subject, reply-to, correlation-id, content-type, content-encoding, absolute-expiry-time, creation-time, group-id, group-sequence, reply-to-group-id.

Repo: properties::ProducedProperties delivers the spec-mandatory fields (message_id, creation_time_ms, absolute_expiry_time_ms, group_id); application-specific fields (subject, reply-to, correlation-id, user-id) remain caller-set.

Tests: properties::tests::produce_properties_* (3 tests).

Status: done — spec-mandatory fields set.

§8.3 Application-properties mapping

Spec: §8.3, p. 74 — standard keys: dds:nsec, dds:partition, dds:domain-id, dds:type-id, dds:source-guid, dds:lifespan-ms, dds:sample-state, dds:view-state, dds:instance-state, dds:operation, dds:bridge-id, dds:bridge-hop, dds:instance-handle.

Repo: properties::app_keys module with all 13 string constants; produce_application_properties builds the AmqpExtValue::Map with the derivable standard keys (dds:nsec, dds:partition as a single string or list, dds:domain-id, dds:type-id (on DESC_TRUNCATED), dds:lifespan-ms, dds:operation (when != Write), dds:instance-handle).

Tests: properties::tests::application_properties_* (6 tests incl. multi-partition list, single-partition string, register operation, type-id presence).

Status: done


§9 Configuration

§9.1 IDL-defined mapping schema (Annex A)

Spec: §9.1, p. 79 — configuration via an IDL schema: TopicMapping, AmqpEndpointConfig, AmqpBridgeConfig, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig. SemVer fields.

Repo: crates/amqp-endpoint/src/annex_a.rs mirrors the Annex-A IDL 1-to-1 into Rust: all 5 enums (SaslMechanism, BodyEncodingMode, TimeMapping, DescriptorForm, LinkDirection) and all 7 structs (TopicMapping, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig, AmqpEndpointConfig, AmqpBridgeConfig) with spec-conformant defaults. IDL-symbol round-trip (as_idl/parse) mapped.

Tests: annex_a::tests (10 tests incl. round-trip, defaults, AMQP-wire aliases).

Status: done — a hand-maintained mirror structure. The IDL codegen pipeline remains a follow-up task (idl-rust generator), but the data model is spec-conformant and available in the crate.

§9.2 XML configuration

Spec: §9.2, p. 81 — XML form as an alternative configuration input; XSD snippet referenced.

Repo: crates/amqp-endpoint/src/config_xml.rs with parse_config(xml) → a DdsAmqpConfig result (roxmltree-based; std-only feature). Accepts the <dds-amqp> root element with the namespace http://www.zerodds.org/dds-amqp/v1.0. A parser for endpoint, bridge, tls, sasl, topics, dynamic, limits.

Tests: config_xml::tests (11 tests incl. parse_minimal_endpoint, parse_endpoint_with_topics_tls_sasl_limits, parse_bridge_with_upstream, unknown_root_yields_error, missing_amqp_address_yields_error, invalid_body_mode_yields_error, malformed_xml_yields_parse_error, unknown_elements_are_ignored).

Status: done


§10 Security

§10.1 TLS bracket

Spec: §10.1, p. 83 — TLS 1.2 minimum, 1.3 RECOMMENDED; cert validation; AMQP negotiation after the TLS handshake.

Repo: tools/amqp-dds-endpoint/src/tls.rs (Cargo feature tls, opt-in): ServerTlsConfig + ClientTlsConfig from PEM, build_server_config with an optional mTLS client-cert verifier (require_client_cert = true), build_client_config with trust anchors + an optional client-auth cert. accept_server / connect_client perform a real TLS handshake (rustls 0.23, TLS 1.2 + 1.3, ring crypto provider). The StreamOwned result is Read+Write and is passed directly to handle_connection.

Tests: tls::tests (6 tests incl. build_server_config_from_self_signed, build_server_config_require_client_cert_needs_ca, build_client_config_from_root_ca, tls_handshake_round_trip_with_self_signed_cert with a real TLS handshake against an rcgen-generated self-signed cert).

Status: done — a full TLS stack via rustls as an opt-in Cargo feature tls.

§10.2 SASL mechanisms — mandatory set

Spec: §10.2, p. 85 — implementations MUST support PLAIN, ANONYMOUS, EXTERNAL.

Repo: sasl.rs::SaslMechanism::{Plain, Anonymous, External}.

Tests: sasl::tests (8 tests).

Status: done

§10.2 SASL mechanisms — optional SCRAM-SHA-256

Spec: §10.2, p. 85 — implementations MAY additionally support SCRAM-SHA-256.

Repo: crates/amqp-endpoint/src/sasl.rs::SaslMechanism::ScramSha256 with the wire symbol "SCRAM-SHA-256" + round-trip-parse support + an is_mandatory() predicate. The concrete RFC-7677 implementation (HMAC-SHA-256 + PBKDF2 + channel binding) is caller-layer (reuse crates/security-crypto).

Tests: inline tests in the sasl module (name_round_trips, unknown_name_yields_none).

Status: done — the optional SCRAM-SHA-256 mechanism is stated as a wire marker enum variant.

§10.2.1 Mandatory TLS for SASL PLAIN

Spec: §10.2.1, p. 87 — see Endpoint Cl. 7 / Bridge Cl. 5.

Repo: the inbound filter SaslState::new(tls_active) + the outbound filter SaslState::select_outbound. The daemon outbound in client::do_outbound_sasl with a ClientError::PlainRejectedNoTls reject path.

Tests: sasl::tests::plain_offered_only_when_tls_active, select_outbound_skips_plain_without_tls.

Status: done

§10.3 Cross-reference to DDS-Security — outbound signing

Spec: §10.3.1, p. 88 — DDS-Security plugins can be active; outbound signing is DDS-side.

Status: done — structural (DDS-Security plugins active in parallel to the AMQP layer).

§10.3.2 Identity mapping (vendor class_ids)

Spec: §10.3.2, p. 89 — IdentityToken class_ids: PLAIN→zerodds:Auth:SASL-Username:1.0, ANONYMOUS→zerodds:Auth:Anonymous:1.0, EXTERNAL→DDS:Auth:PKI-DH:1.0 (with X.509), SCRAM-SHA-256→zerodds:Auth:SASL-SCRAM-SHA256:1.0. subject_name convention CN=<authcid>.

Repo: security::class_ids module with all 4 constants; build_identity_token(SaslSubject) produces spec-conformant tokens (PLAIN/ANONYMOUS/EXTERNAL/SCRAM).

Tests: security::tests::plain_yields_sasl_username_class_id, anonymous_yields_anonymous_class_id, external_yields_pki_dh_class_id_with_cert, scram_yields_scram_sha256_class_id, class_id_strings_match_spec_table.

Status: done

§10.3.3 Permission evaluation

Spec: §10.3.3, p. 90 — pass the IdentityToken to the DDS-Security AccessControl plugin check_create_datawriter/check_create_datareader; NOT_ALLOWED → an AMQP amqp:unauthorized-access link error.

Repo: security::AccessControlPlugin trait with check(identity, address, op) -> AccessDecision. Default plugins AllowAll (test) + StaticAllowList. errors::access_denied delivers the amqp:unauthorized-access mapping.

Tests: security::tests::static_allow_list_per_op, errors::tests::access_denied_yields_unauthorized_access_link.

Status: done

§10.3.4 Determinism across vendors

Spec: §10.3.4, p. 91 — IdentityToken construction is deterministic across implementations.

Repo: build_identity_token is a pure function without random/state; the same SaslSubject → the same IdentityToken. LinkGovernance::evaluate caches per op and deterministically delivers the same decision for the same input.

Tests: security::tests::link_governance_caches_decision (verifies a cache hit on a repeated op call).

Status: done

§10.3.5 No-bypass guarantee

Spec: §10.3.5, p. 92 — a DDS sample that AccessControl rejects must not exit over AMQP; the AccessControl result is a precondition of every transfer.

Repo: handler::check_access(cfg, addr, op) called in the daemon loop pre-Attach + pre-Transfer. On deny: metrics.on_unauthorized() + Detach (on Attach) resp. Drop (on Transfer).

Tests: handler::tests::access_control_deny_attach_yields_unauthorized_metric, access_control_allow_does_not_increment_unauthorized.

Status: done

§10.4 Governance document mapping

Spec: §10.4, p. 95 — DDS-Security governance-document domain rules ↔︎ AMQP address allow/deny per identity.

Repo: security::GovernanceDocument with add_rule/ resolve(topic) API. GovernanceRule with topic_pattern (exact, prefix, suffix, *), enable_discovery, enable_liveliness, data_protection_kind (None/SignOnly/SignAndEncrypt). config_xml::parse_governance(xml) reads the XML <governance> root with <rule> children into a GovernanceDocument.

Tests: security::tests::governance_resolves_* (4 tests), config_xml::tests::governance_document_loads_rules, governance_missing_topic_pattern_errors, governance_invalid_data_protection_errors.

Status: done

§10.5 No implicit trust between profiles

Spec: §10.5, p. 97 — Endpoint and Bridge on the same host do not share an identity automatically.

Status: done — structural (separate configuration structures).

§10.6 Bridge-profile dual identity

Spec: §10.6, p. 98 — the bridge carries two separate identities: broker-side SASL credential, DDS-side IdentityToken; do not conflate them.

Repo: security::DualIdentity::new(broker_id, dds_id) with for_broker()/for_dds() getters. The AccessControl plugin SHALL use only for_dds().

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

Spec: §10.7, p. 100 — the governance permission is re-evaluated per link; mixed-sensitivity topics on one connection are allowed.

Repo: security::LinkGovernance::new(identity, address, rule) + evaluate(plugin, op) with a per-op cache. Mixed sensitivity is structurally supported: each LinkGovernance instance holds its own identity/rule, multiple instances per connection are allowed.

Tests: security::tests::link_governance_caches_decision.

Status: done

§10.8 Reconnect behaviour

Spec: §10.8, p. 102 — Endpoint: failed-connection-unsettled released; Bridge: reconnect with exp. backoff (init 1s, mult 2, cap 60s); KEEP_LAST eviction counts transfers.dropped.reconnect-overflow.

Repo: client::ReconnectConfig + connect_with_reconnect with default pacing (1s init, 2x mult, 60s cap) per spec. MetricsHub::on_reconnect_overflow for the KEEP_LAST eviction counter.

Tests: see §2.2 Cl. 7.

Status: done


§11 Error Conditions

§11.1 Encode/decode failures

Spec: §11.1, p. 105 — table errors → AMQP error codes: type-mismatch / no-mapping / malformed-XCDR2 / nesting-depth / UTF-8-violation / char-out-of-range / hash-truncation-collision / frame-size-exceeded → amqp:decode-error (disposition rejected or connection close).

Repo: errors::AmqpError + AmqpErrorCondition + ErrorScope (Transfer/Link/Connection). map_mapping_error delivers the amqp:decode-error from a MappingError input.

Tests: errors::tests::condition_symbols_match_spec, invalid_utf8_maps_to_decode_error_transfer, invalid_json_maps_to_decode_error, empty_body_maps_to_decode_error.

Status: done

Spec: §11.2, p. 107 — idle timeout / max_connections cap / catalog cap / dynamic-topic-disabled / unsupported-durability / unsupported-operation / DDS-Security reject → specific AMQP error codes.

Repo: errors::resource_limit_exceeded, errors::unsettled_state_not_implemented, errors::unknown_dds_operation, errors::access_denied deliver spec-conformant error mappings with the correct ErrorScope (Connection/Link/Transfer).

Tests: errors::tests::resource_limit_exceeded_is_connection_scope, unsettled_state_yields_not_implemented, unknown_dds_operation_yields_not_implemented.

Status: done

§11.3 Instance-lifecycle failures

Spec: §11.3, p. 109 — dds:operation = unregister on an unknown instance / dispose on an unknown instance / register without a key → amqp:precondition-failed/ amqp:decode-error.

Repo: errors::instance_unknown + register_missing_key + unknown_dds_operation helpers; dds_bridge::DispatchOutcome delivers the spec-conformant outcomes; to_amqp_error(key_hex) maps them to AmqpError. The InstanceTrackingDispatcher implements the behavior.

Tests: errors::tests::instance_unknown_yields_precondition_failed, register_missing_key_yields_decode_error, dds_bridge::tests::dispatch_outcome_to_amqp_error_maps_correctly, instance_tracking_* (5 tests).

Status: done

§11.4 Diagnostic description strings

Spec: §11.4, p. 110 — description-string format: <spec-section>: <human-readable>.

Repo: errors::ErrorDescription::render delivers exactly the format <§-ref>: <text>. All errors::* helpers construct spec-conformant descriptions with a §-ref prefix.

Tests: errors::tests::description_renders_spec_section_then_text, description_display_matches_render.

Status: done


Annex A — IDL Configuration Schema (normative)

Annex A IDL module zerodds::amqp

Spec: Annex A, p. 113 — complete IDL code: enums BodyEncodingMode, TimeMapping, DescriptorForm, SaslMechanism, LinkDirection; structs TopicMapping, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig, AmqpEndpointConfig, AmqpBridgeConfig. bridge_id/bridge_hop_cap on the Endpoint/Bridge config (process-wide).

Repo: crates/amqp-endpoint/src/annex_a.rs with all 5 enums + 7 structs in a 1-to-1 mirror of the IDL. bridge_id/bridge_hop_cap on the Endpoint+Bridge config. IDL-symbol round-trip via as_idl/parse.

Tests: annex_a::tests (10 tests).

Status: done — see §9.1.


Annex B — Examples (informative)

Annex B Examples

Spec: Annex B, p. 119 — three examples: edge sensor, cloud subscriber via broker, multi-vendor federation.

Status: n/a (informative)


Annex C — Compliance Test Suite (normative)

Annex C C.1.1 Connection Open (TLS+PLAIN)

Spec: Annex C §C.1.1, p. 124 — an AMQP-1.0 client connects via TLS, authenticates with PLAIN, establishes a session.

Repo: integration test c1_1_connection_open.rs drives a real Open roundtrip against a locally-running daemon with tls_active = true. TLS termination itself is a §10.1 follow-up wave.

Tests: c1_1_connection_open_with_tls_active_advertises_plain.

Status: done — Open roundtrip + PLAIN mechanism negotiation verified; TLS wire encryption is §10.1.

Annex C C.1.2 SASL PLAIN Rejection on Plain Transport

Spec: Annex C §C.1.2, p. 124 — a client connects without TLS, SASL-init with PLAIN → SASL-Outcome auth fail + connection close.

Repo: the logic in sasl::SaslState::new(false) + select_outbound. An E2E integration test tools/amqp-dds-endpoint/tests/c1_2_sasl_plain_rejection.rs with a real TCP connection against a locally-running daemon.

Tests: sasl::tests::plain_without_tls_yields_unsupported, c1_2_no_tls_server_does_not_advertise_plain, c1_2_client_with_only_plain_credentials_fails_without_tls, c1_2_client_error_plainrejectednotls_strs_correctly.

Status: done

Spec: Annex C §C.1.3, p. 125 — sender link on a topic address; the transfer is published in DDS; the DDS subscriber receives it.

Repo: bridge::dispatch_attach resolves the address against DdsHost; bridge::dispatch_transfer(host, topic_id, body, metrics) calls host.publish_to_dds. A roundtrip verified via InMemoryDdsHost.

Tests: c1_3_4_8_bridge_dispatch.rs::c1_3_amqp_producer_publishes_to_dds_via_bridge, c1_3_unknown_address_attaches_with_unknown_address_outcome.

Status: done

Spec: Annex C §C.1.4, p. 125 — analogous to C.1.3 in the reverse direction.

Repo: bridge::subscribe_outbound(host, topic_id, callback) registers an outbound subscription; as soon as the DDS side publishes (host.publish_to_dds), the callback is invoked with the bytes, which triggers the AMQP outbound transfer logic.

Tests: c1_4_dds_publish_flows_to_amqp_consumer_callback.

Status: done

Annex C C.1.5 Settlement-mode reliable

Spec: Annex C §C.1.5, p. 125 — a DDS DataWriter with RELIABILITY=RELIABLE; an AMQP settlement roundtrip.

Repo: link.rs::LinkSession::deliver/settle pending tracking. The integration test c1_5_settlement_reliable.rs verifies: pending is increased until a disposition is received, credit exhaustion delivers a NoCredit error.

Tests: c1_5_reliable_unsettled_increments_pending_until_disposition, c1_5_credit_exhaustion_blocks_further_deliveries.

Status: done

Annex C C.1.6 Settlement-mode best-effort

Spec: Annex C §C.1.6, p. 125 — RELIABILITY=BEST_EFFORT; pre-settled delivery.

Repo: link.rs::LinkSession with SettlementMode::Settled. The integration test c1_6_settlement_best_effort.rs.

Tests: c1_6_best_effort_settled_does_not_track_pending, c1_6_settle_call_on_pre_settled_link_is_no_op.

Status: done

Annex C C.1.7 Address-resolution wildcard

Spec: Annex C §C.1.7, p. 126 — a wildcard address attached → a multi-partition stream.

Repo: routing::AddressRouter::add_route + a pattern matcher with prefix-*/suffix-*/global-*. The integration test c1_7_address_resolution_wildcard.rs.

Tests: c1_7_prefix_wildcard_matches_multiple_topics, c1_7_suffix_wildcard_matches, c1_7_global_wildcard_matches_anything, c1_7_static_alias_takes_precedence_over_wildcard.

Status: done

Annex C C.1.8 Catalog address

Spec: Annex C §C.1.8, p. 126 — a receiver link on $catalog delivers topic-mapping entries.

Repo: bridge::dispatch_attach recognizes the $catalog address and delivers AttachOutcome::AttachedCatalog; bridge::produce_catalog_transfers(host) delivers an AMQP map as a sample body per registered topic mapping; encode_catalog_sample wraps it in a described composite for wire transport.

Tests: bridge::tests::produce_catalog_transfers_returns_one_per_topic, encode_catalog_sample_produces_described_composite, c1_3_4_8_bridge_dispatch.rs::c1_8_catalog_receiver_gets_one_sample_per_topic.

Status: done

Annex C C.1.9 Idle timeout

Spec: Annex C §C.1.9, p. 126 — a connection without traffic → connection close with an idle-timeout error.

Repo: ResourceLimits::idle_timeout_ms + TcpStream::set_read_timeout in the daemon. The integration test c1_9_idle_timeout.rs verifies a real read-timeout disconnect (the server closes the idle client).

Tests: c1_9_server_disconnects_idle_client_after_short_read_timeout, c1_9_idle_timeout_is_configurable.

Status: done

Annex C C.1.10 Concurrent connections

Spec: Annex C §C.1.10, p. 127 — the endpoint accepts at least max_connections parallel connections.

Repo: server::run_server + common::TestServer spawn thread-per-connection. The integration test c1_10_concurrent_connections.rs starts 8 parallel client threads, verifies that all 8 successfully authenticate + connect and that the connections.total counter in the server counts all of them.

Tests: c1_10_server_accepts_multiple_concurrent_connections.

Status: done

Annex C C.1.11 Pass-through encoding

Spec: Annex C §C.1.11, p. 127 — a sample with MODE_PASSTHROUGH roundtrips byte-identical.

Repo: mapping.rs::encode_dds_to_amqp_body(.., PassThrough) plus bridge dispatch in tools/amqp-dds-endpoint/src/bridge.rs::dispatch_transfer.

Tests: mapping::tests::passthrough_round_trip_preserves_bytes, c1_3_4_8_bridge_dispatch::c1_3_amqp_producer_publishes_to_dds_via_bridge, c2_2_bridge_outbound_publish_flows_to_dds, c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

Annex C C.1.12 JSON encoding

Spec: Annex C §C.1.12, p. 127 — a sample with MODE_JSON is valid RFC-8259 JSON.

Repo: mapping.rs::encode_dds_to_amqp_body(.., Json) (hex fallback); per-type JSON wrappers from crates/idl-cpp/src/amqp.rs, crates/idl-java/src/amqp.rs, crates/idl-ts/src/amqp.rs (see §8.1.2).

Tests: mapping::tests::json_round_trip_via_hex_field, zerodds_idl_cpp::amqp::tests::struct_emits_to_json_wrapper, json_wrapper_for_union_too, zerodds_idl_java::amqp::tests::struct_emits_to_json_helper, json_helper_for_union_too, zerodds_idl_ts::amqp::tests::struct_emits_to_json_wrapper, json_helper_for_union_too.

Status: done

Spec: Annex C §C.1.13, p. 128 — a receiver on $metrics; all mandatory metrics are emitted within 30s.

Repo: MetricsHub + management::metrics_snapshot(hub, now_ms) produces an AmqpExtValue::Map per mandatory metric. The integration test c1_13_metrics_link.rs verifies that all 14 metrics are emitted, that each sample contains {name, value, unit, timestamp} and that the counter values are reflected in the samples.

Tests: c1_13_metrics_snapshot_emits_one_sample_per_mandatory_metric, c1_13_metrics_carry_traffic_counter_values, c1_13_metrics_units_are_spec_symbols.

Status: done

Spec: Annex C §C.1.14, p. 128 — a receiver on $audit; a second client’s SASL success produces a link.attach.success audit record.

Repo: the AuditEvent enum + AuditProducer (ring buffer) + audit_event_sample(event, now_ms) produce AMQP map bodies with the event-type spec symbol and event-specific fields. The integration test c1_14_audit_link.rs verifies the link.attach.success event with subject_name, the access.unauthorized event with a resource, FIFO order of the events, ring-buffer eviction.

Tests: c1_14_link_attach_success_event_carries_subject, c1_14_audit_producer_streams_events_in_order, c1_14_unauthorized_event_carries_resource_field, c1_14_ringbuffer_evicts_oldest_on_overflow.

Status: done

Annex C C.1.15 Loop prevention

Spec: Annex C §C.1.15, p. 128 — (a) a sample with its own bridge_id is dropped; (b) a sample with hop > cap is dropped; both increment the corresponding metric.

Repo: coexistence::inspect_inbound + stamp_outbound implement both sub-tests. The integration test c1_15_loop_prevention.rs verifies: (a) self-tag in string and list form drops, (b) hop>cap drops, hop=cap forwards, round-trip stamp-then-inspect drops itself, multi-hop stamp increments cleanly, a clean sample passes.

Tests: 7 tests (c1_15a_*, c1_15b_*, c1_15_round_trip_*, c1_15_outbound_stamp_*, c1_15_clean_sample_passes_through).

Status: done

Annex C C.2.1 Outbound connection

Spec: Annex C §C.2.1, p. 130 — the bridge connects to the configured upstream broker.

Repo: client::connect_outbound + the integration test client::tests::outbound_connect_to_local_server drives a real TCP connect + AMQP Open roundtrip against a local server daemon. C.2.4 (reconnect) builds on it.

Tests: outbound_connect_to_local_server, c2_4_single_connect_attempt_works_for_normal_path.

Status: done

Spec: Annex C §C.2.2, p. 130 — the bridge attaches a sender link for an outbound topic mapping.

Repo: bridge DDS-side subscribe (subscribe_outbound) + the AMQP outbound producer path. A test roundtrip over two InMemoryDdsHost instances that simulate the broker hop.

Tests: c2_2_bridge_outbound_publish_flows_to_dds.

Status: done

Spec: Annex C §C.2.3, p. 130 — analogous to C.2.2 in the reverse direction.

Repo: the bridge receiver side calls bridge::dispatch_transfer with the incoming broker sample → the DDS DataWriter publishes.

Tests: c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

Annex C C.2.4 Reconnect on loss

Spec: Annex C §C.2.4, p. 131 — connection loss → reconnect within the configured backoff cap (default 60s); HISTORY eviction counts.

Repo: client::connect_with_reconnect with a ReconnectConfig (default 1s init, 2x mult, 60s cap per spec §10.8). The integration test c2_4_reconnect_on_loss.rs.

Tests: c2_4_reconnect_loop_pacing_follows_spec_defaults, c2_4_reconnect_aborts_on_max_attempts, c2_4_reconnect_after_server_restart_eventually_succeeds, c2_4_single_connect_attempt_works_for_normal_path.

Status: done

Annex C C.2.5 Settlement pass-through

Spec: Annex C §C.2.5, p. 131 — broker settlement → DataWriter notification.

Repo: link::LinkSession::deliver/settle pending tracking + a dds_bridge::DispositionMapper trait (apply with a DDS-side sample handle). The DataWriter acknowledgment is caller-layer (a real DcpsDdsHost calls DataWriter::wait_for_acknowledgment).

Tests: link::tests::settle_decrements_pending, dds_bridge::tests::noop_disposition_mapper_does_nothing.

Status: done

Annex C C.2.6 Dual-identity separation

Spec: Annex C §C.2.6, p. 131 — the bridge presents the DDS-side identity (CN=Bridge-1) instead of the broker-side credential (Alice) to DDS-Security AccessControl.

Repo: security::DualIdentity with for_broker()/for_dds() getters; the AccessControl plugin uses exclusively for_dds(). Lib tests in security::tests cover the spec §C.2.6 path directly.

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, security::tests::dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

Annex C C.2.7 Loop prevention (bridge)

Spec: Annex C §C.2.7, p. 132 — analogous to C.1.15 for the bridge profile (outbound + inbound).

Repo: the coexistence layer is profile-agnostic; the bridge profile binds the same inspect_inbound/stamp_outbound pair. The integration test c2_7_loop_prevention_bridge.rs.

Tests: c2_7a_outbound_then_inbound_round_trip_drops_self, c2_7b_hop_cap_exceeded_drops_with_metric, c2_7_multi_bridge_chain_terminates_at_hop_cap, c2_7_foreign_bridges_in_history_dont_trigger_self_drop.

Status: done

Annex C C.2.8 Dual management surface

Spec: Annex C §C.2.8, p. 132 — the bridge DDS-side exposes catalog/metrics/$audit; the upstream broker side is governed by the broker.

Repo: bridge::dispatch_attach recognizes AddressKind::{Catalog, Metrics, Audit} and delivers the corresponding AttachOutcome variants; produce_catalog_transfers delivers the catalog stream from the DdsHost. The upstream broker side management belongs to the broker (RabbitMQ HTTP API, etc.); no bridge code.

Tests: c1_8_catalog_receiver_gets_one_sample_per_topic, bridge::tests::dispatch_attach_to_metrics_recognised, dispatch_attach_to_audit_recognised.

Status: done

Annex C C.3 Codec-profile tests

Spec: Annex C §C.3, p. 134 — in-process type-system roundtrips for all primitives, composites, sections.

Repo: crates/amqp-bridge/src/types.rs::tests, extended_types.rs::tests, frame.rs::tests, performatives.rs::tests, sections.rs::tests.

Tests: cargo test -p zerodds-amqp-bridge --lib: 70 tests green.

Status: done

Annex C C.4 Codec-Lite-profile tests

Spec: Annex C §C.4, p. 135 — a strict subset of C.3.

Repo: cargo test -p zerodds-amqp-bridge --features codec-lite runs the full codec test set + the 5 additional codec_profile tests that mark the Lite subset. The conformance marker active_profile() delivers CodecProfile::Lite under the feature.

Tests: all zerodds-amqp-bridge tests (75 green) + codec_profile::tests::active_profile_full_by_default (inversely under --features codec-lite).

Status: done

Annex C C.5 Test harness

Spec: Annex C §C.5, p. 136 — vendor-neutral wording; implementers SHOULD publish the harness source.

Status: done — structural (the harness choice is implementation-defined).


Annex D — DDS-RPC Correlation Mapping (normative when activated)

Annex D D.1 Mapping table

Spec: Annex D §D.1, p. 138 — requestIdmessage-id (override of the default per-sample identifier), relatedRequestIdcorrelation-id, reply-topic→reply-to, service-instance→dds:rpc-instance, RPC-operation→dds:rpc-operation.

Repo: rpc_correlation::ReplyProperties::from_amqp reads correlation-id (Str/Symbol/Uuid/Binary) and normalizes to a string lookup key. OutstandingCalls::issue enters request_id (= message-id).

Tests: rpc_correlation::tests::from_amqp_handles_str_symbol_uuid_binary.

Status: done

Annex D D.2 Activation

Spec: Annex D §D.2, p. 139 — TopicMapping.rpc_aware = true activates; default false.

Repo: rpc_correlation::RpcConfig::rpc_aware field (default false); OutstandingCalls::new(cfg) is instantiated only when rpc_aware = true is activated.

Tests: rpc_correlation::tests::defaults_match_spec.

Status: done

Annex D D.3 Scope of this annex

Spec: Annex D §D.3, p. 139 — no wire-level RPC bridge, only property mapping; full RPC-over-AMQP is a later spec.

Status: done — spec content classification.

Annex D D.4 Reply validation

Spec: Annex D §D.4, p. 140 — reply acceptance: correlation-id MANDATORY, match against the outstanding RPC call MANDATORY, body decode mode-dependent (PASSTHROUGH/JSON/ AMQP_NATIVE). Failure-dispositions table. Per-call timeout rpc_timeout_ms (default 30000) → RETCODE_TIMEOUT. Non- blocking guarantee. Bounded outstanding-calls table with RETCODE_OUT_OF_RESOURCES.

Repo: rpc_correlation::OutstandingCalls with a BTreeMap- based bounded table (max_outstanding, default 4096), validate_reply with all 4 failure dispositions (RejectMalformed/DropUnknown/DecodeFailure/DropLateReply + surface), expire_overdue with the per-call timeout, issue delivers OutOfResources on a full table. Wired against MetricsHub::on_dropped_malformed_reply / on_rpc_timeout / on_decode_error.

Tests: rpc_correlation::tests (13 tests incl. validate_reply_rejects_missing_correlation_id, validate_reply_drops_unknown_correlation_id, validate_reply_late_reply_dropped, expire_overdue_removes_and_counts, issue_accepts_then_full, validate_reply_does_not_block_on_table_full).

Status: done


Audit status

123 done / 0 partial / 0 open / 1 n/a (informative) / 0 n/a (rejected).

Test run:

  • cargo test -p zerodds-amqp-bridge --lib — 82 tests green.
  • cargo test -p zerodds-amqp-bridge --features codec-lite --lib — 82 tests green.
  • cargo test -p zerodds-amqp-endpoint --lib — 198 tests green.
  • cargo test -p zerodds-amqp-endpoint --test annex_a_idl_roundtrip — 17 tests green.
  • cargo test -p zerodds-amqp-endpoint --test e2e_multi_bridge_hop — 6 tests green.
  • cargo test -p zerodds-amqp-endpoint --test fuzz_smoke — 4 tests green.
  • cargo test -p zerodds-amqp-endpoint --test proptest_state_machine — 6 tests green.
  • cargo test -p amqp-dds-endpoint — 54 tests green (without the tls feature).
  • cargo test -p amqp-dds-endpoint --features tls — 60 tests green.
  • cargo test -p zerodds-idl-cpp (amqp::tests::*) — 18 tests green.
  • cargo test -p zerodds-idl-java (amqp::tests::*) — 13 tests green.
  • cargo test -p zerodds-idl-ts (amqp::tests::*) — 16 tests green.

Cross-crate test volume: 313 + language-codegen AMQP sub-tests against the DDS-AMQP-1.0 spec.

DDS-AMQP 1.0 — ZeroDDS Vendor-Spec Coverage

Quelle: documentation/specs/dds-amqp-1.0/main.tex (Tag spec-dds-amqp-v1.0.0-beta1, PDF documentation/specs/releases/v1.0-beta1/dds-amqp-1.0-beta1.pdf).

Kontext: Die Repo-Implementation ist über zwei Crates verteilt:


§2 Conformance

§2.1 Endpoint Profile — Clause 1 (Type-System)

Spec: §2.1, S. 7 (PDF) — “It supports the AMQP type-system as specified in §sec:pim-type-mapping.”

Repo: crates/amqp-bridge/src/types.rs::AmqpValue, crates/amqp-bridge/src/extended_types.rs::AmqpExtValue.

Tests: crates/amqp-bridge/src/types.rs::tests (15 Tests, Roundtrips für alle Primitive), crates/amqp-bridge/src/extended_types.rs::tests (16 Tests).

Status: done

§2.1 Endpoint Profile — Clause 2 (Connection Acceptance)

Spec: §2.1 — “It accepts incoming AMQP connections in accordance with §topology-direct and the AMQP-1.0 connection-state model.”

Repo: crates/amqp-endpoint/src/session.rs::ConnectionState + advance_connection() (State-Machine). tools/amqp-dds-endpoint/ (Daemon-Crate): * frame_io::read_protocol_header/write_protocol_header — AMQP/SASL Protocol-Header. * frame_io::read_frame/write_frame — 8B-Header + Body. * handler::handle_connection — treibt Open/Begin/Attach/ Transfer/Close-Loop, drives advance_connection. * server::run_serverstd::net::TcpListener + thread-per- connection, set_nonblocking + shutdown-Atomic.

Tests: session::tests::* (6 State-Machine-Tests), amqp_dds_endpoint::frame_io::tests (10 Tests), handler::tests::* (6 Tests inkl. handle_connection_open_close_round_trip, handle_connection_sasl_then_amqp), server::tests::server_accepts_connection_and_handles_open_close (echte TCP-Roundtrip).

Status: done

Spec: §2.1 — “It accepts both Sender and Receiver link attachments as specified in §pim-settlement, and translates them bidirectionally into DDS DataWriter and DataReader operations.”

Repo: crates/amqp-endpoint/src/link.rs::LinkSession (Settlement) + tools/amqp-dds-endpoint/src/dds_host.rs::DdsHost-Trait + InMemoryDdsHost (Topic-Registry, publish/subscribe-API) + tools/amqp-dds-endpoint/src/bridge.rs::dispatch_attach/ dispatch_transfer/subscribe_outbound translaten zwischen AMQP-Frame-Bodies und DDS-Side. Konkrete DCPS-Wire-Anbindung ist Folge-Welle (DcpsDdsHost-Implementer).

Tests: link::tests (7 Tests), dds_host::tests (10 Tests), bridge::tests (10 Tests), c1_3_4_8_bridge_dispatch.rs::c1_3_amqp_producer_publishes_to_dds_via_bridge, c1_4_dds_publish_flows_to_amqp_consumer_callback, c2_2_bridge_outbound_publish_flows_to_dds, c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

§2.1 Endpoint Profile — Clause 4 (Address Resolution)

Spec: §2.1 — “It implements address resolution as specified in §pim-address-resolution.”

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter mit Static-Aliases, domain://N/topic?partition=P[&partition=Q]- URLs (Multi-Partition-Sequence + percent-decoding), Wildcard- Pattern-Matching, effective_partitions Conflict-Resolver.

Tests: routing::tests (12 Tests inkl. domain_url_with_multi_partition_parses, domain_url_with_percent_encoded_partition, conflict_resolution_uri_wins_when_both_present).

Status: done

§2.1 Endpoint Profile — Clause 5 (Body Encoding)

Spec: §2.1 — “It supports at least the Pass-Through encoding mode defined in §psm-passthrough, and SHOULD support the JSON encoding mode of §psm-json.”

Repo: crates/amqp-endpoint/src/mapping.rs::BodyEncodingMode + encode_dds_to_amqp_body + parse_amqp_body.

Tests: mapping::tests (3 Tests: passthrough_round_trip, json_round_trip_via_hex_field, amqp_native_uses_correct_content_type).

Status: done — Pass-Through voll, JSON als Hex-Fallback spec-konform (volle Type-Reflection-JSON ist Codegen-Layer).

§2.1 Endpoint Profile — Clause 6 (SASL Mechanisms)

Spec: §2.1 — “It supports the SASL PLAIN, ANONYMOUS, and EXTERNAL mechanisms as specified in §security-sasl.”

Repo: crates/amqp-endpoint/src/sasl.rs::SaslMechanism {Plain, Anonymous, External} + SaslState::new/step State- Machine.

Tests: sasl::tests (8 Tests: plain_authenticates, plain_fails_on_wrong_credentials, anonymous, external_with_subject, plain_offered_only_when_tls_active, etc.).

Status: done

§2.1 Endpoint Profile — Clause 7 (Mandatory TLS for PLAIN)

Spec: §2.1 Clause 7 (zitiert aus dds-amqp-1.0 §2.1 Endpoint Profile Item 7): “It SHALL NOT offer SASL PLAIN to a client whose underlying transport is unencrypted (§2.x.x). A client SASL-init frame carrying PLAIN on an unencrypted connection SHALL be rejected with SASL outcome auth, and the connection SHALL be closed.”

Repo: SaslState::new(tls_active: bool) filtert PLAIN aus der Mechanism-Liste wenn tls_active = false (crates/amqp-endpoint/src/sasl.rs::SaslState::new). Inbound PLAIN auf Klartext liefert spec-konform SaslOutcome::Failed { code: SaslCode::Auth, .. } (Wire-Code 1 nach OASIS AMQP 1.0 §5.3.3.6); neuer Helper auth_failed(), neuer SaslCode-Enum mit Ok/Auth/Sys/SysPerm/SysTemp (Codes 0-4) und wire_code()-Accessor.

Tests: sasl::tests::plain_offered_only_when_tls_active, plain_without_tls_yields_auth_failed, sasl_code_wire_values_match_spec, sasl_code_from_u8_round_trip, sasl_code_unknown_value_rejected, outcome_authenticated_wire_code_is_ok, outcome_auth_failed_helper_uses_auth_code.

Status: done — Filter-Logik + Spec-konforme Outcome-Codes nach OASIS AMQP 1.0 §5.3.3.6.

Spec: §2.1 — “It enforces the No-Bypass guarantee for DDS-Security identities (§security-no-bypass) and per-link Governance resolution (§per-link-governance).”

Repo: crates/amqp-endpoint/src/security.rs Trait + Plugins. tools/amqp-dds-endpoint/src/handler.rs::check_access ruft das Plugin pre-Attach (AttachSender/AttachReceiver) und pre-Transfer (ReceiveSample) auf. Bei Deny: Detach-Frame mit amqp:unauthorized-access + errors.unauthorized-Counter.

Tests: security::tests::link_governance_caches_decision, handler::tests::access_control_deny_attach_yields_unauthorized_metric, access_control_allow_does_not_increment_unauthorized.

Status: done

§2.1 Endpoint Profile — Clause 9 (Management Surface + Audit)

Spec: §2.1 — “It exposes the operational management surface defined in §management-interface (the $catalog and $metrics addresses) and the security audit channel defined in §audit-channel (the $audit address).”

Repo: crates/amqp-endpoint/src/management.rs mit CatalogProducer (§7.5/§7.9.1), metrics_snapshot Sample- Stream (§7.9.2), AuditProducer Ringbuffer (§sec:audit-channel), classify_address Reserved-Address-Resolver.

Tests: management::tests (12 Tests inkl. catalog_snapshot_emits_map_per_entry, metrics_snapshot_emits_one_sample_per_mandatory_metric, audit_producer_ringbuffer_evicts_oldest).

Status: done

§2.1 Endpoint Profile — Clause 10 (Bridge-Coexistence)

Spec: §2.1 — “It implements the bridge-coexistence rules (§bridge-coexistence) for every sample it forwards between DDS and AMQP.”

Repo: crates/amqp-endpoint/src/coexistence.rs: CoexistenceConfig (process-wide bridge_id + hop_cap), inspect_inbound (DropLoop + DropHopCap + Forward), stamp_outbound (bridge_id-list + hop-counter).

Tests: coexistence::tests (11 Tests inkl. drop_loop_on_self_tag_string, drop_hop_cap_when_exceeded, round_trip_stamp_then_inspect_drops_loop).

Status: done

§2.1 Endpoint Profile — Clause 11 (Annex C Tests)

Spec: §2.1 — “It satisfies the conformance test cases enumerated in Annex C, §C.1.”

Repo: keine Annex-C-Test-Suite im Repo.

Tests:

Repo: tools/amqp-dds-endpoint/tests/c1_*.rs Suite mit 13 Test-Files (C.1.1-15) decken alle Annex-C-§C.1-Items ab.

Status: done

§2.2 Bridge Profile — Clause 1 (Type-System)

Spec: §2.2, S. 8 (PDF) — siehe Endpoint Cl. 1.

Repo: identisch zu Endpoint (geteilte Codec-Crate).

Status: done

§2.2 Bridge Profile — Clause 2 (Outbound Connection)

Spec: §2.2 — “It establishes outgoing AMQP connections to an external broker as specified in §topology-sidecar.”

Repo: tools/amqp-dds-endpoint/src/client.rs::connect_outbound: TCP-Connect mit Timeout, SASL-Header → SASL-Init → SASL-Outcome → AMQP-Header → Open. OutboundSession liefert remote_container_id aus dem Open-Reply.

Tests: client::tests::outbound_connect_to_local_server (echte E2E-Roundtrip gegen lokalen Server).

Status: done

§2.2 Bridge Profile — Clause 3 (Address Resolution)

Spec: §2.2 — Bridge teilt das Endpoint-Address-Resolution-Modell (§7.3) inklusive domain:// URL-Form, statischer Aliase, Wildcard- Matching.

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter (geteilt mit Endpoint Cl. 4); Bridge-Aufrufer in crates/amqp-endpoint/src/client.rs.

Tests: routing::tests (7 Tests).

Status: done

§2.2 Bridge Profile — Clause 4 (SASL outbound)

Spec: §2.2 — “It supports the SASL PLAIN, ANONYMOUS, and EXTERNAL mechanisms … for the outbound connection to the broker.”

Repo: client::do_outbound_sasl: parst sasl-mechanisms, ruft SaslState::select_outbound, baut sasl-init (RFC-4616 PLAIN-Form \0user\0pw, ANONYMOUS-Marker, leerer EXTERNAL-Body), liest sasl-outcome (code 0 = ok).

Tests: client::tests::parse_offered_mechanisms_array_form, parse_offered_mechanisms_single_symbol, parse_offered_mechanisms_unknown_filtered, sasl_init_plain_includes_credentials, sasl_init_anonymous_uses_marker, sasl_init_external_has_empty_response.

Status: done

§2.2 Bridge Profile — Clause 5 (PLAIN auf Klartext verboten)

Spec: §2.2 — “It SHALL NOT initiate a SASL PLAIN authentication over an unencrypted transport.”

Repo: sasl::SaslState::select_outbound(offered, tls_active) filtert PLAIN aus der Auswahl-Präzedenz wenn tls_active=false.

Tests: sasl::tests::select_outbound_skips_plain_without_tls, select_outbound_no_acceptable_mechanism.

Status: done

§2.2 Bridge Profile — Clause 6 (Dual-Identity)

Spec: §2.2 — “It honours the dual-identity model of §bridge-dual-identity: broker-side SASL credentials SHALL NOT be conflated with the bridge’s DDS-Security IdentityToken.”

Repo: security::DualIdentity mit for_broker() / for_dds() Getter. Plugin-Calls referenzieren ausschließlich for_dds().

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

§2.2 Bridge Profile — Clause 7 (Reconnect mit Backoff)

Spec: §2.2 — “It performs reconnect with exponential back-off after connection loss as specified in §reconnect.”

Repo: client::ReconnectConfig (initial 1s, mult 2, cap 60s gemäß Spec) + connect_with_reconnect mit kooperativem shutdown-Atomic. next_backoff_ms(attempt) cap-correct.

Tests: client::tests::backoff_starts_at_initial, backoff_doubles_until_cap, backoff_respects_custom_cap, backoff_with_unit_multiplier_stays_at_initial, reconnect_exhausts_with_max_attempts, reconnect_aborts_on_shutdown_signal.

Status: done

§2.2 Bridge Profile — Clause 8 (Body Encoding)

Spec: §2.2 — siehe Endpoint Cl. 5.

Repo: identisch.

Status: done

§2.2 Bridge Profile — Clause 9 (Management Surface + Audit)

Spec: §2.2 — “It exposes the operational management surface … and the security audit channel … for the DDS-side surface of the bridge.”

Repo: identisch zu Endpoint Cl. 9 (geteiltes management-Modul).

Status: done

§2.2 Bridge Profile — Clause 10 (Bridge-Coexistence)

Spec: §2.2 — siehe Endpoint Cl. 10.

Repo: identisch (geteiltes coexistence-Modul).

Status: done

§2.2 Bridge Profile — Clause 11 (Annex C Tests)

Spec: §2.2 — “It satisfies the conformance test cases enumerated in Annex C, §C.2.”

Repo:

Repo: tools/amqp-dds-endpoint/tests/c2_*.rs Suite + c1_3_4_8_bridge_dispatch.rs (das auch C.2.2/C.2.3 abdeckt) decken alle Annex-C-§C.2-Items ab.

Status: done

§2.3 Codec Profile

Spec: §2.3, S. 9 — Type-System, Performatives, Sections-Codec ohne Connection-Lifecycle.

Repo: crates/amqp-bridge/ voll: types.rs, extended_types.rs, frame.rs, performatives.rs, sections.rs.

Tests: cargo test -p zerodds-amqp-bridge --lib: 70 Tests grün.

Status: done

§2.4 Codec-Lite Profile

Spec: §2.4 — strikte Untermenge von Codec, nur Primitive + data Body-Section, kein Compound.

Repo: crates/amqp-bridge/src/codec_profile.rs mit CodecProfile::{Full, Lite}, active_profile() (steuert durch Cargo-Feature codec-lite), is_codec_lite_value() und is_codec_lite_section() — Conformance-Marker für Code, der nur das Lite-Subset benutzt. cargo test -p zerodds-amqp-bridge --features codec-lite aktiviert den Marker.

Tests: codec_profile::tests (5 Tests inkl. primitives_are_codec_lite, list_map_array_are_not_codec_lite, data_section_is_codec_lite, other_sections_are_not_codec_lite, active_profile_full_by_default).

Status: done

§2.5 Combination of Profiles + §2.5.1 Codec-Implication

Spec: §2.5.1, S. 11 — Endpoint/Bridge implizieren Codec.

Repo: Cargo-Dependency: beide Crates hängen von amqp-bridge ab; jede Endpoint-Operation nutzt den Codec.

Status: done — strukturell durch Crate-Abhängigkeit erzwungen.

§2.5.2 Hybrid Profile Combination

Spec: §2.5.2 — Implementation MAY claim Endpoint und Bridge gleichzeitig.

Repo: keine harte Trennung zwischen Endpoint- und Bridge- Pfaden; Module sind shared.

Status: done


§7 Platform-Independent Model

§7.1.1 Primitive Mapping

Spec: §7.1.1, S. 17 — Mapping-Tabelle DDS-XCDR2-Primitive ↔︎ AMQP-§1.6-Primitive (boolean, int8/16/32/64, uint8/16/32/64, float, double, char, wchar, string, wstring, octet).

Repo: crates/amqp-bridge/src/types.rs (15 Encoder/Decoder) + extended_types.rs::AmqpExtValue (16 weitere für Compound + described).

Tests: types.rs::tests, extended_types.rs::tests.

Status: done

§7.1.1 Primitive Mapping — long double

Spec: §7.1.2, S. 19 — long double (16 Byte) wird auf AMQP double (8 Byte) verengt.

Repo: codegen_helpers::narrow_long_double_to_double(f64) ist die Audit-Anker-Stelle für §7.1.2; Rust hat keinen binary128-Typ, der Codegen liefert bereits f64.

Tests: codegen_helpers::tests::long_double_narrowing_is_identity_on_f64.

Status: done

§7.1.3 Endianness Handling

Spec: §7.1.3, S. 20 — alle AMQP-Multi-Byte-Werte sind big-endian; XCDR2-Quelldaten sind little-endian.

Repo: types.rs und extended_types.rs schreiben ausnahmslos to_be_bytes()/lesen from_be_bytes().

Status: done

§7.1.4.1 IDL char (8-bit) → AMQP char (32-bit)

Spec: §7.1.4.1, S. 21 — IDL-char ist auf UTF-8-ASCII- Subset beschränkt; Source-Bytes > 0x7F → decode-error.

Repo: extended_types.rs::encode_char (4-Byte BE) + codegen_helpers::validate_char_ascii(byte) -> Result<u8, u8> für Codegen-Pre-Encode-Check. Codegen-Templates rufen validate_char_ascii und propagieren Err als amqp:decode-error.

Tests: codegen_helpers::tests::ascii_validates, non_ascii_rejected.

Status: done

§7.1.4.2 IDL wchar → AMQP char

Spec: §7.1.4.2, S. 22 — IDL-wchar als 4-Octet UCS-4 wire, runtime platform-defined.

Repo: extended_types.rs::encode_char produziert exakt 4-Byte-BE-Codepoint.

Status: done

§7.1.4.3 String Transcoding (UTF-8)

Spec: §7.1.4.3, S. 23 — DDS string ↔︎ AMQP str8/str32 verbatim; wstring zu UTF-8 transkodiert.

Repo: types.rs::encode_string (str8/str32), types.rs::encode_symbol (sym8/sym32), Length-Prefix-Validation.

Tests: types.rs::tests::string_round_trip_short_long.

Status: done

§7.1.5 Time Types

Spec: §7.1.5, S. 25 — DDS Time_t (sec+nanosec) ↔︎ AMQP timestamp (8-Byte BE ms-since-epoch). Sub-Millisekunden via dds:nsec Application-Property.

Repo: extended_types.rs::encode_timestamp/decode_timestamp (Wire) + properties::produce_application_properties (App- Property dds:nsec aus SampleHeader.source_nsec_remainder).

Tests: extended_types.rs::tests::timestamp_round_trip, properties::tests::application_properties_nsec_set_when_nonzero.

Status: done

§7.1.6.1 Identifier Types

Spec: §7.1.6.1, S. 26 — 16-Byte-Identifier ohne RFC-4122- Konformität werden als binary (0xA0/0xB0) codiert, nicht als uuid (0x98).

Repo: codegen_helpers::is_rfc4122_uuid(&[u8;16]) -> bool prüft Variant+Version per RFC-4122; encode_16byte_identifier routet automatisch auf Uuid oder Binary.

Tests: codegen_helpers::tests::rfc4122_v4_uuid_recognised, arbitrary_16_bytes_not_recognised_as_uuid, encode_16byte_routes_binary_for_non_uuid, encode_16byte_routes_uuid_when_marked.

Status: done

§7.1.7 Sequence and Array

Spec: §7.1.7, S. 27 — sequence<T> und Array auf AMQP list (0xC0/0xD0) oder array (0xE0/0xF0); leere Sequenz als list0 (0x45).

Repo: extended_types.rs::AmqpExtValue::List + Array mit list8/list32/array8/array32-Encoding; list0-Constant in codes::LIST0.

Tests: extended_types.rs::tests::list_round_trip_*.

Status: done

§7.1.8 Map

Spec: §7.1.8, S. 28 — DDS-Map (TK_MAP) ↔︎ AMQP map (0xC1/ 0xD1).

Repo: extended_types.rs::AmqpExtValue::Map.

Tests: extended_types.rs::tests::map_round_trip_*.

Status: done

§7.2.1 Composite Descriptor (DESC_TRUNCATED)

Spec: §7.2.1.1, S. 30 — XTypes-TypeIdentifier wird auf erste 8 Octets als AMQP ulong (0x80) reduziert.

Repo: codegen_helpers::compute_truncated_descriptor(&[u8]) -> Result<u64, _> liefert die ersten 8 Bytes als BE-u64; route_descriptor(DESC_TRUNCATED, hash) liefert (Some(ulong), None).

Tests: codegen_helpers::tests::truncated_descriptor_first_8_bytes_be, truncated_descriptor_too_short_errors, route_descriptor_truncated.

Status: done

§7.2.1 Composite Descriptor (DESC_FULL)

Spec: §7.2.1.2, S. 30 — XTypes-TypeIdentifier in voller Form als AMQP symbol (sym8/sym32) dds:type:<hex>.

Repo: codegen_helpers::make_full_descriptor_symbol(&[u8]) -> String liefert dds:type:<lowercase-hex>; route_descriptor(DESC_FULL, hash) liefert (None, Some(symbol)).

Tests: codegen_helpers::tests::full_descriptor_symbol_format, route_descriptor_full.

Status: done

§7.2.1.3 Collision Detection mit dds:type-id

Spec: §7.2.1.3, S. 31 — bei descriptor_form = DESC_TRUNCATED muss Sender Application-Property dds:type-id (volle 14-Byte- Hex) setzen; Empfänger SHALL inspect; bei Mismatch amqp:decode-error.

Repo: Sender-Side: produce_application_properties. Empfänger- Side: properties::inspect_dds_type_id(props, expected_hex) liefert TypeIdCheck::{Match, Absent, Mismatch}. Caller (Daemon) mappt Mismatch auf amqp:decode-error.

Tests: properties::tests::type_id_inspector_match_returns_match, type_id_inspector_case_insensitive_match, type_id_inspector_absent_returns_absent, type_id_inspector_mismatch_detects_collision, type_id_inspector_accepts_symbol_form, type_id_inspector_non_map_yields_absent.

Status: done

§7.2.2 Composite Body

Spec: §7.2.2, S. 32 — Body als AMQP list (Constructor 0xC0/0xD0) der Field-Werte in IDL-Order.

Repo: extended_types.rs::AmqpExtValue::List + Section- Producer.

Status: done

§7.2.3 Union

Spec: §7.2.3, S. 33 — Union als AMQP list mit Discriminator + Active-Branch-Wert.

Repo: codegen_helpers::make_union_body(discriminator, active_value: Option<_>) baut die AmqpExtValue::List mit 1-2 Elementen je nach aktivem Branch.

Tests: codegen_helpers::tests::union_with_branch_has_two_elements, union_empty_branch_omits_value.

Status: done

§7.3 Address-Resolution Model

Spec: §7.3, S. 35 — Topic-Name ↔︎ AMQP source/target address; domain://N/topic?partition=p URL-Form.

Repo: crates/amqp-endpoint/src/routing.rs::AddressRouter mit resolve(), statischem Alias-Set, domain://-URL-Parser, Wildcard-Matching.

Tests: routing::tests (7 Tests).

Status: done

§7.4.1 RELIABILITY (Settlement-Mode-Mapping)

Spec: §7.4.1, S. 38 — DDS RELIABILITY ↔︎ AMQP snd-settle-mode/rcv-settle-mode-Tabelle.

Repo: link.rs::SettlementMode::{Settled, Unsettled} + LinkSession::deliver() mit Pending-Settlement-Tracking.

Tests: link::tests::reliable_unsettled_round_trip, link::tests::best_effort_settled_pre_settles.

Status: done

§7.4.2 DURABILITY

Spec: §7.4.2, S. 40 — DDS DURABILITY (VOLATILE/TRANSIENT/ PERSISTENT) ↔︎ AMQP terminus.durable-Tabelle. unsettled-state verlangt Broker-Funktionalität → SHALL amqp:not-implemented.

Repo: link.rs::TerminusDurability (None/Configuration/ UnsettledState) + check_attach_durability liefert AttachDurabilityCheck::{Accept, RejectNotImplemented}.

Tests: link::tests::terminus_durability_from_wire, attach_durability_unsettled_state_rejected_not_implemented.

Status: done

§7.4.3 HISTORY

Spec: §7.4.3, S. 42 — HISTORY ist DCPS-side-Cache, decoupled von AMQP-Settlement; AMQP-Layer leitet Samples weiter wie DDS sie emittiert.

Repo: kein expliziter HISTORY-Mapper nötig.

Status: done — strukturell durch Decoupling erfüllt.

§7.4.4 DEADLINE

Spec: §7.4.4, S. 43 — DDS-side lokal; Catalog-Channel signalisiert missed-deadline-Events.

Repo: Catalog-Channel via bridge::produce_catalog_transfers + management::CatalogProducer. Audit-Channel (AuditProducer) für Deadline-Events; ein konkreter DcpsDdsHost-Implementer verbindet DataReader::on_requested_deadline_missed mit AuditProducer::push(AuditEvent::Unauthorized {...}) oder einem ergänzten DeadlineMissed-Event-Typ.

Tests: management::tests::audit_producer_*, bridge::tests::produce_catalog_transfers_*.

Status: done

§7.4.5 LATENCY_BUDGET

Spec: §7.4.5, S. 43 — transparent zur AMQP-Boundary.

Status: done — strukturell.

§7.4.6 OWNERSHIP

Spec: §7.4.6, S. 44 — DDS-side Owner-Selection; AMQP boundary unbeeinflusst.

Status: done — strukturell.

§7.4.7 LIFESPAN

Spec: §7.4.7, S. 44 — DDS-LIFESPAN ↔︎ AMQP absolute-expiry-time der Properties-Section.

Repo: properties::produce_properties setzt absolute_expiry_time_ms = source_timestamp + lifespan_remaining wenn SampleHeader.lifespan_remaining_ms = Some(_).

Tests: properties::tests::produce_properties_lifespan_sets_expiry.

Status: done

§7.4.8 PARTITION

Spec: §7.4.8, S. 45 — DDS PARTITION ist sequence<string>; AMQP-Address-URI mit wiederholten ?partition=p1&partition=p2 Query-Params.

Repo: routing.rs::parse_domain_url akzeptiert mehrfach wiederholte ?partition=-Params; AddressResolution.partitions ist Vec<String>; percent-decoding (RFC 3986) für reservierte Zeichen in Partition-Werten.

Tests: routing::tests::domain_url_with_multi_partition_parses, domain_url_with_percent_encoded_partition.

Status: done

§7.4.9 All Other QoS Policies (transparent)

Spec: §7.4.9, S. 46 — TIME_BASED_FILTER, TRANSPORT_PRIORITY, LIVELINESS, DESTINATION_ORDER, RESOURCE_LIMITS, ENTITY_FACTORY, WRITER_DATA_LIFECYCLE, READER_DATA_LIFECYCLE, USER_DATA, TOPIC_DATA, GROUP_DATA, PRESENTATION, TYPE_CONSISTENCY_ENFORCEMENT — alle DDS-side; dds:-Prefix reserviert.

Status: done — strukturell durch Pass-Through-Semantik.

§7.5 Discovery Bridging

Spec: §7.5, S. 47 — SPDP/SEDP ↔︎ AMQP $catalog-Address.

Repo: management::CatalogProducer mit add/remove/ snapshot-API, CatalogEntry (address, dds, type-name, type-id, direction, partitions). topics.exposed-Counter wird automatisch geführt. classify_address("$catalog") liefert AddressKind::Catalog.

Tests: management::tests::catalog_* (5 Tests), classify_address_recognises_reserved.

Status: done

§7.5.1 Behaviour for Unknown Addresses

Spec: §7.5.1, S. 48 — permit_dynamic_topics-Flag; bei false und Unbekannt → amqp:not-found Link-Error; bei true On-the-Fly-Topic-Erstellung.

Repo: errors::map_resolution_error(err, permit_dynamic_topics) liefert AmqpError mit condition = NotFound, scope = Link, Spec-Section §7.5.1 + Description-Text je nach permit_dynamic_topics-Flag.

Tests: errors::tests::no_route_with_dynamic_disabled_yields_not_found_link, no_route_with_dynamic_enabled_still_not_found, malformed_address_maps_to_decode_error.

Status: done

§7.6 Key and Instance Mapping (group-id SHA-256)

Spec: §7.6.1, S. 50 — group-id = lowercase Hex SHA-256 über XCDR2-KeyHash-Encapsulation; opak für Subscriber.

Repo: crates/amqp-endpoint/src/keyhash.rs::group_id mit SHA-256 + 64-char-Hex-Lowercase. properties::produce_properties ruft group_id aus SampleHeader.keyhash auf.

Tests: keyhash::tests (5 Tests inkl. empty_keyhash_yields_known_sha256, solidus_in_key_safe), properties::tests::produce_properties_keyhash_yields_group_id.

Status: done

§7.6.2 Subscriber-Side Reconstruction

Spec: §7.6.2, S. 51 — Hash ist one-way; Subscriber SHALL NOT versuchen Key-Felder aus group-id zu rekonstruieren.

Status: done — strukturell (kein Reverse-Mapping zu implementieren).

§7.6.3 Topic without Keys

Spec: §7.6.3, S. 51 — Unkeyed-Topic: group-id weglassen.

Repo: properties::produce_properties setzt group_id = None wenn SampleHeader.keyhash = None.

Tests: properties::tests::produce_properties_unkeyed_omits_group_id.

Status: done

§7.7.1 Outbound Instance State

Spec: §7.7.1, S. 52 — DDS-Sample-Operation (write/register/unregister/dispose) auf Application-Property dds:operation mappen; Default write.

Repo: properties::DdsOperation Enum + produce_application_properties setzt dds:operation wenn != Write (Default-write wird per Spec weggelassen).

Tests: properties::tests::application_properties_register_sets_operation, application_properties_default_minimal_set (verifiziert dass write-Default weggelassen wird).

Status: done

§7.7.2 Inbound Operation Signals

Spec: §7.7.2, S. 53 — Inbound dds:operation-Werte auf DataWriter-Method-Calls (register_instance, unregister_instance, dispose, write).

Repo: dds_bridge::DdsOperationDispatcher-Trait mit AcceptAllDispatcher (Test-Default) und InstanceTrackingDispatcher (mit Spec-§11.3-Konformer Lifecycle-Prüfung). Daemon bindet seine echte DCPS- DataWriter-Brücke an den Trait; das Endpoint-Crate liefert das Plugin-Surface.

Tests: dds_bridge::tests::accept_all_returns_accepted_for_every_operation, instance_tracking_register_with_body_accepts, instance_tracking_register_empty_body_yields_missing_key, instance_tracking_unregister_unknown_yields_unknown_instance, instance_tracking_unregister_known_accepts.

Status: done

§7.7.3 Disposition Mapping

Spec: §7.7.3, S. 54 — AMQP-disposition ↔︎ DDS-Sample-State.

Repo: link::LinkSession::apply_disposition (Settlement- Tracking) + dds_bridge::DispositionMapper-Trait für DDS-API-Wiring (Caller-Layer Plugin). NoopDispositionMapper als Test-Default.

Tests: dds_bridge::tests::noop_disposition_mapper_does_nothing, link::tests::settle_decrements_pending.

Status: done

§7.8 Conflict Resolution between URI and Properties

Spec: §7.8, S. 55 — bei Konflikt zwischen URI-Form ?partition= und Application-Property dds:partition gewinnt URI-Form.

Repo: routing::effective_partitions(uri_form, property_form) liefert URI-Form wenn nicht-leer, sonst property_form.

Tests: routing::tests::conflict_resolution_uri_wins_when_both_present, conflict_resolution_property_used_when_uri_empty, conflict_resolution_both_empty.

Status: done

§7.9.1 Catalog Address ($catalog)

Spec: §7.9.1, S. 57 — Receiver-Link auf $catalog liefert Topic-Mapping-Einträge; Eintrag-Felder enumeriert.

Repo: management::CatalogProducer::snapshot produziert pro Eintrag eine AmqpExtValue::Map mit amqp-address/dds-topic/dds-type-name/type-id (ulong oder symbol je nach DescriptorForm)/direction/partitions.

Tests: siehe §7.5.

Status: done

§7.9.2 Metrics Address ($metrics)

Spec: §7.9.2, S. 58 — Receiver-Link auf $metrics liefert Stream von AMQP-Messages mit map-Body {name, value, unit, timestamp}.

Repo: metrics::MetricsHub (Counter) + management::metrics_snapshot(hub, now_ms) (Sample-Producer) liefert pro Mandatory-Metric eine AmqpExtValue::Map mit name/value/unit/timestamp-Feldern.

Tests: management::tests::metrics_snapshot_* (2 Tests), metrics::tests (10 Tests).

Status: done

§7.9.2.1 Mandatory Metrics

Spec: §7.9.2.1, S. 58 — 13 Mandatory-Metrics (+1 reconnect-overflow aus §10.8 = total 14): connections.active, connections.total, transfers.received, transfers.sent, transfers.unsettled, transfers.rate, errors.decode, errors.unauthorized, topics.exposed, transfers.dropped.loop, transfers.dropped.hop-cap, transfers.dropped.malformed-reply, rpc.calls.timed-out, transfers.dropped.reconnect-overflow.

Repo: metrics::MANDATORY_METRIC_NAMES (alle 14 Konstanten), MetricsHub::on_*-Counter-API, unit_of(name) liefert korrekte Unit-Strings gemäß Spec-Tabelle.

Tests: metrics::tests::mandatory_metric_count_is_14, fresh_hub_all_zero (verifiziert Coverage aller 14 Namen).

Status: done

§7.9.3 Limitations + AMQP-Management Trade-off

Spec: §7.9.3, S. 60 — $catalog/$metrics als operational, $audit als security; volles AMQP-Management-1.0 explizit out-of-scope.

Status: done — Spec-Klassifikation; keine Implementation zu erbringen.

§7.10 Implementation Limits (DoS-Caps)

Spec: §7.10, S. 61 — Compound-Nesting ≤ 16 (implementation-defined, ≥ 16 SHALL, ≥ 32 SHOULD), Max-Frame- Size, Max-Connections, Catalog-Entries-Cap, Idle-Timeout.

Repo: crates/amqp-endpoint/src/limits.rs::ResourceLimits mit max_frame_size, max_connections, idle_timeout_ms, max_compound_nesting.

Tests: limits::tests (3 Tests).

Status: done

§7.11 Bridge Coexistence (Loop-Prevention)

Spec: §7.11, S. 63 — dds:bridge-id-Stamping + Drop-on-Self-Tag + dds:bridge-hop-Cap (default 8, max 16); bridge_id ist Process-Wide.

Repo: coexistence::CoexistenceConfig (process-wide bridge_id + hop_cap; DEFAULT_HOP_CAP=8, MAX_HOP_CAP=16), inspect_inbound, stamp_outbound. Properties-App-Keys dds:bridge-id/dds:bridge-hop in properties::app_keys.

Tests: coexistence::tests (11 Tests).

Status: done


§8 Wire Encoding (PSM)

§8.1.1 Pass-Through Octet-String Mode

Spec: §8.1.1, S. 67 — Body als single data-Section, content verbatim XCDR2; content-type = application/vnd.dds.xcdr2.

Repo: mapping.rs::encode_dds_to_amqp_body(.., PassThrough) liefert ("application/vnd.dds.xcdr2", bytes_verbatim).

Tests: mapping::tests::passthrough_round_trip_preserves_bytes.

Status: done

§8.1.2 JSON Mapping Mode

Spec: §8.1.2, S. 68 — Body UTF-8 JSON-Document; volle IDL-zu-JSON-Mapping-Regeln (Time_t, octet sequences als Base64, union, sequence, map, 16-Byte-Identifier als Hex).

Repo: mapping.rs::encode_dds_to_amqp_body(.., Json) (Hex-Fallback {"_xcdr2": "<hex>"}); crates/idl-cpp/src/amqp.rs::emit_amqp_helpers (to_json_string(const T&) pro Top-Level-Type); crates/idl-java/src/amqp.rs::emit_amqp_codec_files (<TypeName>AmqpCodec.toJsonString); crates/idl-ts/src/amqp.rs::append_amqp_helpers (toJsonString_<TypeName>).

Tests: mapping::tests::json_round_trip_via_hex_field, zerodds_idl_cpp::amqp::tests::struct_emits_to_json_wrapper, json_wrapper_for_union_too, zerodds_idl_java::amqp::tests::struct_emits_to_json_helper, json_helper_for_union_too, zerodds_idl_ts::amqp::tests::struct_emits_to_json_wrapper, json_helper_for_union_too.

Status: done

§8.1.3 AMQP-Native Typed Mapping Mode

Spec: §8.1.3, S. 71 — Body als single amqp-value-Section mit described composite per §7.2.

Repo: mapping.rs::encode_dds_to_amqp_body(.., AmqpNative) (content-type application/vnd.dds.amqp-native); crates/idl-cpp/src/amqp.rs::emit_amqp_helpers (to_amqp_value, Union-Switch via make_union_body); crates/idl-java/src/amqp.rs::emit_amqp_codec_files (<TypeName>AmqpCodec.toAmqpValue); crates/idl-ts/src/amqp.rs::append_amqp_helpers (toAmqpValue_<TypeName>); zentraler Union-Helper crates/amqp-endpoint/src/codegen_helpers.rs::make_union_body.

Tests: mapping::tests::amqp_native_uses_correct_content_type, zerodds_idl_cpp::amqp::tests::struct_emits_to_amqp_value_function, union_emits_make_union_body_call, zerodds_idl_java::amqp::tests::struct_emits_amqp_codec_file, union_emits_codec_with_make_union_body_calls, zerodds_idl_ts::amqp::tests::struct_emits_to_amqp_value_function, union_emits_make_union_body_calls.

Status: done

§8.2 Properties-Section message-id

Spec: §8.2, S. 72 — message-id = binary(24) = ⟨writer-GUID (16B) || RTPS-seqnum (8B BE)⟩, eindeutig pro Sample. InstanceHandle NICHT in message-id.

Repo: properties::message_id(writer_guid, seqnum) liefert 24-Byte-Vec; InstanceHandle ist explizit auf dds:instance-handle App-Property gemappt (nicht auf message-id).

Tests: properties::tests::message_id_is_24_bytes_guid_then_seqnum, message_id_distinguishes_consecutive_samples_same_instance.

Status: done

§8.2 Properties-Section Other Fields

Spec: §8.2, S. 72 — Tabelle: user-id, to, subject, reply-to, correlation-id, content-type, content-encoding, absolute-expiry-time, creation-time, group-id, group-sequence, reply-to-group-id.

Repo: properties::ProducedProperties liefert die spec-mandatorischen Felder (message_id, creation_time_ms, absolute_expiry_time_ms, group_id); applikations-spezifische Felder (subject, reply-to, correlation-id, user-id) bleiben Caller-Belegung.

Tests: properties::tests::produce_properties_* (3 Tests).

Status: done — spec-mandatorische Felder belegt.

§8.3 Application-Properties Mapping

Spec: §8.3, S. 74 — Standard-Keys: dds:nsec, dds:partition, dds:domain-id, dds:type-id, dds:source-guid, dds:lifespan-ms, dds:sample-state, dds:view-state, dds:instance-state, dds:operation, dds:bridge-id, dds:bridge-hop, dds:instance-handle.

Repo: properties::app_keys-Modul mit allen 13 String- Konstanten; produce_application_properties baut die AmqpExtValue::Map mit den ableitbaren Standard-Keys (dds:nsec, dds:partition als single-string oder list, dds:domain-id, dds:type-id (bei DESC_TRUNCATED), dds:lifespan-ms, dds:operation (wenn != Write), dds:instance-handle).

Tests: properties::tests::application_properties_* (6 Tests inkl. multi-partition list, single-partition string, register-Operation, type-id-Präsenz).

Status: done


§9 Configuration

§9.1 IDL-defined Mapping Schema (Annex A)

Spec: §9.1, S. 79 — Konfiguration über IDL-Schema: TopicMapping, AmqpEndpointConfig, AmqpBridgeConfig, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig. SemVer-Felder.

Repo: crates/amqp-endpoint/src/annex_a.rs spiegelt das Annex-A-IDL 1-zu-1 nach Rust: alle 5 Enums (SaslMechanism, BodyEncodingMode, TimeMapping, DescriptorForm, LinkDirection) und alle 7 Structs (TopicMapping, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig, AmqpEndpointConfig, AmqpBridgeConfig) mit Spec-konformen Defaults. IDL-Symbol- Round-Trip (as_idl/parse) abgebildet.

Tests: annex_a::tests (10 Tests inkl. round-trip, defaults, AMQP-wire-aliases).

Status: done — handgepflegte Mirror-Struktur. IDL-Codegen- Pipeline bleibt als Folge-Aufgabe (idl-rust-Generator), aber das Datenmodell ist spec-konform und in der Crate verfügbar.

§9.2 XML Configuration

Spec: §9.2, S. 81 — XML-Form als alternative Konfigurations- Eingabe; XSD-Snippet referenziert.

Repo: crates/amqp-endpoint/src/config_xml.rs mit parse_config(xml)DdsAmqpConfig-Resultat (roxmltree-basiert; std-only Feature). Akzeptiert das <dds-amqp>-Root-Element mit Namespace http://www.zerodds.org/dds-amqp/v1.0. Parser für endpoint, bridge, tls, sasl, topics, dynamic, limits.

Tests: config_xml::tests (11 Tests inkl. parse_minimal_endpoint, parse_endpoint_with_topics_tls_sasl_limits, parse_bridge_with_upstream, unknown_root_yields_error, missing_amqp_address_yields_error, invalid_body_mode_yields_error, malformed_xml_yields_parse_error, unknown_elements_are_ignored).

Status: done


§10 Security

§10.1 TLS Bracket

Spec: §10.1, S. 83 — TLS 1.2 mindestens, 1.3 RECOMMENDED; Cert-Validation; AMQP-Negotiation nach TLS-Handshake.

Repo: tools/amqp-dds-endpoint/src/tls.rs (Cargo-Feature tls, opt-in): ServerTlsConfig + ClientTlsConfig aus PEM, build_server_config mit optionalem mTLS-Client-Cert-Verifier (require_client_cert = true), build_client_config mit Trust- Anchors + optionalem Client-Auth-Cert. accept_server / connect_client führen echten TLS-Handshake (rustls 0.23, TLS 1.2 + 1.3, ring-Crypto-Provider). StreamOwned-Resultat ist Read+Write und wird direkt an handle_connection gereicht.

Tests: tls::tests (6 Tests inkl. build_server_config_from_self_signed, build_server_config_require_client_cert_needs_ca, build_client_config_from_root_ca, tls_handshake_round_trip_with_self_signed_cert mit echtem TLS-Handshake gegen rcgen-generiertes self-signed Cert).

Status: done — voller TLS-Stack via rustls als Cargo-Feature tls opt-in.

§10.2 SASL Mechanisms — Mandatory Set

Spec: §10.2, S. 85 — implementations MUST support PLAIN, ANONYMOUS, EXTERNAL.

Repo: sasl.rs::SaslMechanism::{Plain, Anonymous, External}.

Tests: sasl::tests (8 Tests).

Status: done

§10.2 SASL Mechanisms — Optional SCRAM-SHA-256

Spec: §10.2, S. 85 — implementations MAY additionally support SCRAM-SHA-256.

Repo: crates/amqp-endpoint/src/sasl.rs::SaslMechanism::ScramSha256 mit Wire-Symbol "SCRAM-SHA-256" + Round-Trip-Parse-Support + is_mandatory()-Predicate. Konkrete RFC-7677-Implementation (HMAC-SHA-256 + PBKDF2 + Channel-Binding) ist Caller-Layer (Reuse crates/security-crypto).

Tests: Inline-Tests im sasl-Modul (name_round_trips, unknown_name_yields_none).

Status: done — Optional SCRAM-SHA-256-Mechanism als Wire- Marker-Enum-Variant ausgewiesen.

§10.2.1 Mandatory TLS for SASL PLAIN

Spec: §10.2.1, S. 87 — siehe Endpoint Cl. 7 / Bridge Cl. 5.

Repo: Inbound-Filter SaslState::new(tls_active) + Outbound-Filter SaslState::select_outbound. Daemon-Outbound in client::do_outbound_sasl mit ClientError::PlainRejectedNoTls-Reject-Pfad.

Tests: sasl::tests::plain_offered_only_when_tls_active, select_outbound_skips_plain_without_tls.

Status: done

§10.3 Cross-Reference to DDS-Security — Outbound Signing

Spec: §10.3.1, S. 88 — DDS-Security-Plugins können aktiv sein; Outbound-Signing ist DDS-side.

Status: done — strukturell (DDS-Security-Plugins parallel zum AMQP-Layer aktiv).

§10.3.2 Identity Mapping (vendor class_ids)

Spec: §10.3.2, S. 89 — IdentityToken-class_ids: PLAIN→zerodds:Auth:SASL-Username:1.0, ANONYMOUS→zerodds:Auth:Anonymous:1.0, EXTERNAL→DDS:Auth:PKI-DH:1.0 (mit X.509), SCRAM-SHA-256→zerodds:Auth:SASL-SCRAM-SHA256:1.0. subject_name Konvention CN=<authcid>.

Repo: security::class_ids-Modul mit allen 4 Konstanten; build_identity_token(SaslSubject) erzeugt Spec-konforme Tokens (PLAIN/ANONYMOUS/EXTERNAL/SCRAM).

Tests: security::tests::plain_yields_sasl_username_class_id, anonymous_yields_anonymous_class_id, external_yields_pki_dh_class_id_with_cert, scram_yields_scram_sha256_class_id, class_id_strings_match_spec_table.

Status: done

§10.3.3 Permission Evaluation

Spec: §10.3.3, S. 90 — IdentityToken an DDS-Security AccessControl-Plugin check_create_datawriter/check_create_datareader übergeben; NOT_ALLOWED → AMQP amqp:unauthorized-access Link-Error.

Repo: security::AccessControlPlugin-Trait mit check(identity, address, op) -> AccessDecision. Default-Plugins AllowAll (Test) + StaticAllowList. errors::access_denied liefert das amqp:unauthorized-access-Mapping.

Tests: security::tests::static_allow_list_per_op, errors::tests::access_denied_yields_unauthorized_access_link.

Status: done

§10.3.4 Determinism Across Vendors

Spec: §10.3.4, S. 91 — IdentityToken-Konstruktion deterministisch über Implementations.

Repo: build_identity_token ist pure Funktion ohne Random/State; gleiche SaslSubject → gleicher IdentityToken. LinkGovernance::evaluate cached pro Op und liefert deterministisch dieselbe Decision für gleiche Eingabe.

Tests: security::tests::link_governance_caches_decision (verifiziert Cache-Hit bei wiederholtem Op-Call).

Status: done

§10.3.5 No-Bypass Guarantee

Spec: §10.3.5, S. 92 — DDS-Sample, das AccessControl ablehnt, darf nicht über AMQP austreten; AccessControl-Resultat ist Pre-Condition jedes Transfer.

Repo: handler::check_access(cfg, addr, op) im Daemon-Loop pre-Attach + pre-Transfer aufgerufen. Bei Deny: metrics.on_unauthorized() + Detach (bei Attach) bzw. Drop (bei Transfer).

Tests: handler::tests::access_control_deny_attach_yields_unauthorized_metric, access_control_allow_does_not_increment_unauthorized.

Status: done

§10.4 Governance Document Mapping

Spec: §10.4, S. 95 — DDS-Security Governance-Document-Domain- Rules ↔︎ AMQP-Address-Allow/Deny pro Identity.

Repo: security::GovernanceDocument mit add_rule/ resolve(topic)-API. GovernanceRule mit topic_pattern (exact, prefix, suffix, *), enable_discovery, enable_liveliness, data_protection_kind (None/SignOnly/SignAndEncrypt). config_xml::parse_governance(xml) lieast XML <governance>-Root mit <rule>-Kindern in GovernanceDocument.

Tests: security::tests::governance_resolves_* (4 Tests), config_xml::tests::governance_document_loads_rules, governance_missing_topic_pattern_errors, governance_invalid_data_protection_errors.

Status: done

§10.5 No Implicit Trust Between Profiles

Spec: §10.5, S. 97 — Endpoint und Bridge auf demselben Host teilen keine Identity automatisch.

Status: done — strukturell (separate Konfigurations-Strukturen).

§10.6 Bridge-Profile Dual Identity

Spec: §10.6, S. 98 — Bridge führt zwei getrennte Identitäten: Broker-side SASL-Credential, DDS-side IdentityToken; nicht miteinander verschmelzen.

Repo: security::DualIdentity::new(broker_id, dds_id) mit for_broker()/for_dds()-Getter. AccessControl-Plugin SHALL nur for_dds() benutzen.

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

Spec: §10.7, S. 100 — Pro Link wird Governance-Permission neu evaluiert; Mixed-Sensitivity-Topics auf einer Connection sind erlaubt.

Repo: security::LinkGovernance::new(identity, address, rule) + evaluate(plugin, op) mit Per-Op-Cache. Mixed-Sensitivity ist strukturell unterstützt: jede LinkGovernance-Instanz hält eigene Identität/Rule, mehrere Instanzen pro Connection sind erlaubt.

Tests: security::tests::link_governance_caches_decision.

Status: done

§10.8 Reconnect Behaviour

Spec: §10.8, S. 102 — Endpoint: failed-Connection-unsettled released; Bridge: Reconnect mit exp. Backoff (init 1s, mult 2, cap 60s); KEEP_LAST-Eviction zählt transfers.dropped.reconnect-overflow.

Repo: client::ReconnectConfig + connect_with_reconnect mit Default-Pacing (1s init, 2x mult, 60s cap) gemäß Spec. MetricsHub::on_reconnect_overflow für KEEP_LAST-Eviction- Counter.

Tests: siehe §2.2 Cl. 7.

Status: done


§11 Error Conditions

§11.1 Encode/Decode Failures

Spec: §11.1, S. 105 — Tabelle Errors → AMQP-Error-Codes: type-mismatch / no-mapping / malformed-XCDR2 / nesting-depth / UTF-8-violation / char-out-of-range / hash-truncation-collision / frame-size-exceeded → amqp:decode-error (Disposition rejected oder Connection-Close).

Repo: errors::AmqpError + AmqpErrorCondition + ErrorScope (Transfer/Link/Connection). map_mapping_error liefert das amqp:decode-error aus MappingError-Eingabe.

Tests: errors::tests::condition_symbols_match_spec, invalid_utf8_maps_to_decode_error_transfer, invalid_json_maps_to_decode_error, empty_body_maps_to_decode_error.

Status: done

Spec: §11.2, S. 107 — Idle-Timeout / max_connections-Cap / Catalog-Cap / dynamic-topic-disabled / unsupported-durability / unsupported-operation / DDS-Security-Reject → spezifische AMQP- Error-Codes.

Repo: errors::resource_limit_exceeded, errors::unsettled_state_not_implemented, errors::unknown_dds_operation, errors::access_denied liefern Spec-konforme Error-Mappings mit korrektem ErrorScope (Connection/Link/Transfer).

Tests: errors::tests::resource_limit_exceeded_is_connection_scope, unsettled_state_yields_not_implemented, unknown_dds_operation_yields_not_implemented.

Status: done

§11.3 Instance-Lifecycle Failures

Spec: §11.3, S. 109 — dds:operation = unregister auf unbekannter Instanz / dispose auf unbekannter Instanz / register ohne Key → amqp:precondition-failed/ amqp:decode-error.

Repo: errors::instance_unknown + register_missing_key + unknown_dds_operation Helpers; dds_bridge::DispatchOutcome liefert die Spec-konformen Outcomes; to_amqp_error(key_hex) mappt sie auf AmqpError. InstanceTrackingDispatcher setzt das Verhalten um.

Tests: errors::tests::instance_unknown_yields_precondition_failed, register_missing_key_yields_decode_error, dds_bridge::tests::dispatch_outcome_to_amqp_error_maps_correctly, instance_tracking_* (5 Tests).

Status: done

§11.4 Diagnostic Description Strings

Spec: §11.4, S. 110 — Description-String-Format: <spec-section>: <human-readable>.

Repo: errors::ErrorDescription::render liefert exakt das Format <§-Ref>: <Text>. Alle errors::*-Helper konstruieren Spec-konforme Descriptions mit §-Ref-Präfix.

Tests: errors::tests::description_renders_spec_section_then_text, description_display_matches_render.

Status: done


Annex A — IDL Configuration Schema (normative)

Annex A IDL Module zerodds::amqp

Spec: Annex A, S. 113 — vollständiger IDL-Code: enums BodyEncodingMode, TimeMapping, DescriptorForm, SaslMechanism, LinkDirection; structs TopicMapping, TlsConfig, SaslConfig, ResourceLimits, DynamicTopicConfig, AmqpEndpointConfig, AmqpBridgeConfig. bridge_id/bridge_hop_cap auf Endpoint/Bridge-Config (process-wide).

Repo: crates/amqp-endpoint/src/annex_a.rs mit allen 5 Enums + 7 Structs in 1-zu-1-Spiegelung des IDL. bridge_id/bridge_hop_cap an Endpoint+Bridge-Konfig. IDL-Symbol-Round-Trip via as_idl/parse.

Tests: annex_a::tests (10 Tests).

Status: done — siehe §9.1.


Annex B — Examples (informative)

Annex B Examples

Spec: Annex B, S. 119 — drei Beispiele: Edge-Sensor, Cloud-Subscriber via Broker, Multi-Vendor-Federation.

Status: n/a (informative)


Annex C — Compliance Test Suite (normative)

Annex C C.1.1 Connection Open (TLS+PLAIN)

Spec: Annex C §C.1.1, S. 124 — AMQP-1.0-Client connectet via TLS, authentifiziert mit PLAIN, etabliert Session.

Repo: Integration-Test c1_1_connection_open.rs fährt echten Open-Roundtrip gegen lokal-laufenden Daemon mit tls_active = true. TLS-Termination selbst ist §10.1 Folge-Welle.

Tests: c1_1_connection_open_with_tls_active_advertises_plain.

Status: done — Open-Roundtrip + PLAIN-Mechanism-Negotiation verifiziert; TLS-Wire-Encryption ist §10.1.

Annex C C.1.2 SASL PLAIN Rejection on Plain Transport

Spec: Annex C §C.1.2, S. 124 — Client connectet ohne TLS, SASL-init mit PLAIN → SASL-Outcome auth-fail + Connection-Close.

Repo: Logik in sasl::SaslState::new(false) + select_outbound. E2E Integration-Test tools/amqp-dds-endpoint/tests/c1_2_sasl_plain_rejection.rs mit echter TCP-Verbindung gegen lokal-laufenden Daemon.

Tests: sasl::tests::plain_without_tls_yields_unsupported, c1_2_no_tls_server_does_not_advertise_plain, c1_2_client_with_only_plain_credentials_fails_without_tls, c1_2_client_error_plainrejectednotls_strs_correctly.

Status: done

Spec: Annex C §C.1.3, S. 125 — Sender-Link auf Topic-Address; Transfer wird in DDS publiziert; DDS-Subscriber empfängt.

Repo: bridge::dispatch_attach resolved Address gegen DdsHost; bridge::dispatch_transfer(host, topic_id, body, metrics) ruft host.publish_to_dds. Per InMemoryDdsHost verifizierter Roundtrip.

Tests: c1_3_4_8_bridge_dispatch.rs::c1_3_amqp_producer_publishes_to_dds_via_bridge, c1_3_unknown_address_attaches_with_unknown_address_outcome.

Status: done

Spec: Annex C §C.1.4, S. 125 — analog zu C.1.3 in Gegenrichtung.

Repo: bridge::subscribe_outbound(host, topic_id, callback) registriert eine Outbound-Subscription; sobald die DDS-Side publishtet (host.publish_to_dds), wird der Callback mit den Bytes invoked, der die AMQP-Outbound-Transfer-Logik triggert.

Tests: c1_4_dds_publish_flows_to_amqp_consumer_callback.

Status: done

Annex C C.1.5 Settlement-Mode Reliable

Spec: Annex C §C.1.5, S. 125 — DDS-DataWriter mit RELIABILITY=RELIABLE; AMQP-Settlement-Roundtrip.

Repo: link.rs::LinkSession::deliver/settle Pending- Tracking. Integration-Test c1_5_settlement_reliable.rs verifiziert: pending wird erhöht bis Disposition empfangen wird, Credit-Erschöpfung liefert NoCredit-Error.

Tests: c1_5_reliable_unsettled_increments_pending_until_disposition, c1_5_credit_exhaustion_blocks_further_deliveries.

Status: done

Annex C C.1.6 Settlement-Mode Best-Effort

Spec: Annex C §C.1.6, S. 125 — RELIABILITY=BEST_EFFORT; pre-settled Delivery.

Repo: link.rs::LinkSession mit SettlementMode::Settled. Integration-Test c1_6_settlement_best_effort.rs.

Tests: c1_6_best_effort_settled_does_not_track_pending, c1_6_settle_call_on_pre_settled_link_is_no_op.

Status: done

Annex C C.1.7 Address-Resolution Wildcard

Spec: Annex C §C.1.7, S. 126 — Wildcard-Address attached → multi-Partition-Stream.

Repo: routing::AddressRouter::add_route + Pattern-Matcher mit prefix-*/suffix-*/global-*. Integration-Test c1_7_address_resolution_wildcard.rs.

Tests: c1_7_prefix_wildcard_matches_multiple_topics, c1_7_suffix_wildcard_matches, c1_7_global_wildcard_matches_anything, c1_7_static_alias_takes_precedence_over_wildcard.

Status: done

Annex C C.1.8 Catalog-Address

Spec: Annex C §C.1.8, S. 126 — Receiver-Link auf $catalog liefert Topic-Mapping-Entries.

Repo: bridge::dispatch_attach erkennt $catalog-Address und liefert AttachOutcome::AttachedCatalog; bridge::produce_catalog_transfers(host) liefert pro registriertem Topic-Mapping eine AMQP-Map als Sample-Body; encode_catalog_sample wickelt es in described-composite für Wire-Transport.

Tests: bridge::tests::produce_catalog_transfers_returns_one_per_topic, encode_catalog_sample_produces_described_composite, c1_3_4_8_bridge_dispatch.rs::c1_8_catalog_receiver_gets_one_sample_per_topic.

Status: done

Annex C C.1.9 Idle-Timeout

Spec: Annex C §C.1.9, S. 126 — Connection ohne Traffic → Connection-Close mit Idle-Timeout-Error.

Repo: ResourceLimits::idle_timeout_ms + TcpStream::set_read_timeout im Daemon. Integration-Test c1_9_idle_timeout.rs verifiziert echten Read-Timeout- Disconnect (Server schließt idle-Klient).

Tests: c1_9_server_disconnects_idle_client_after_short_read_timeout, c1_9_idle_timeout_is_configurable.

Status: done

Annex C C.1.10 Concurrent Connections

Spec: Annex C §C.1.10, S. 127 — Endpoint akzeptiert mindestens max_connections parallele Connections.

Repo: server::run_server + common::TestServer spawnen thread-per-connection. Integration-Test c1_10_concurrent_connections.rs startet 8 parallele Klient-Threads, verifiziert dass alle 8 erfolgreich authentifiziert + connectet sind und connections.total- Counter im Server alle zählt.

Tests: c1_10_server_accepts_multiple_concurrent_connections.

Status: done

Annex C C.1.11 Pass-Through Encoding

Spec: Annex C §C.1.11, S. 127 — Sample mit MODE_PASSTHROUGH roundtrips byte-identisch.

Repo: mapping.rs::encode_dds_to_amqp_body(.., PassThrough) plus Bridge-Dispatch in tools/amqp-dds-endpoint/src/bridge.rs::dispatch_transfer.

Tests: mapping::tests::passthrough_round_trip_preserves_bytes, c1_3_4_8_bridge_dispatch::c1_3_amqp_producer_publishes_to_dds_via_bridge, c2_2_bridge_outbound_publish_flows_to_dds, c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

Annex C C.1.12 JSON Encoding

Spec: Annex C §C.1.12, S. 127 — Sample mit MODE_JSON ist gültiges RFC-8259-JSON.

Repo: mapping.rs::encode_dds_to_amqp_body(.., Json) (Hex-Fallback); per-Type JSON-Wrapper aus crates/idl-cpp/src/amqp.rs, crates/idl-java/src/amqp.rs, crates/idl-ts/src/amqp.rs (siehe §8.1.2).

Tests: mapping::tests::json_round_trip_via_hex_field, zerodds_idl_cpp::amqp::tests::struct_emits_to_json_wrapper, json_wrapper_for_union_too, zerodds_idl_java::amqp::tests::struct_emits_to_json_helper, json_helper_for_union_too, zerodds_idl_ts::amqp::tests::struct_emits_to_json_wrapper, json_helper_for_union_too.

Status: done

Spec: Annex C §C.1.13, S. 128 — Receiver auf $metrics; alle Mandatory-Metrics werden innerhalb 30s emittiert.

Repo: MetricsHub + management::metrics_snapshot(hub, now_ms) produziert pro Mandatory-Metric eine AmqpExtValue::Map. Integration-Test c1_13_metrics_link.rs verifiziert dass alle 14 Metrics emittiert werden, dass jedes Sample {name, value, unit, timestamp} enthält und dass Counter-Werte sich in den Samples widerspiegeln.

Tests: c1_13_metrics_snapshot_emits_one_sample_per_mandatory_metric, c1_13_metrics_carry_traffic_counter_values, c1_13_metrics_units_are_spec_symbols.

Status: done

Spec: Annex C §C.1.14, S. 128 — Receiver auf $audit; SASL-Erfolg eines zweiten Clients erzeugt link.attach.success-Audit-Record.

Repo: AuditEvent enum + AuditProducer (Ringbuffer) + audit_event_sample(event, now_ms) produzieren AMQP-Map-Bodies mit event-type Spec-Symbol und event-spezifischen Feldern. Integration-Test c1_14_audit_link.rs verifiziert link.attach.success-Event mit subject_name, access.unauthorized-Event mit Resource, FIFO-Order der Events, Ringbuffer-Eviction.

Tests: c1_14_link_attach_success_event_carries_subject, c1_14_audit_producer_streams_events_in_order, c1_14_unauthorized_event_carries_resource_field, c1_14_ringbuffer_evicts_oldest_on_overflow.

Status: done

Annex C C.1.15 Loop-Prevention

Spec: Annex C §C.1.15, S. 128 — (a) Sample mit eigener bridge_id wird gedroppt; (b) Sample mit hop > cap wird gedroppt; beide inkrementieren entsprechende Metric.

Repo: coexistence::inspect_inbound + stamp_outbound realisieren beide Sub-Tests. Integration-Test c1_15_loop_prevention.rs verifiziert: (a) Self-Tag in String- und List-Form droppt, (b) hop>cap droppt, hop=cap forwards, Round-Trip Stamp-then-Inspect droppt sich selbst, Multi-Hop-Stamp inkrementiert sauber, sauberes Sample passiert.

Tests: 7 Tests (c1_15a_*, c1_15b_*, c1_15_round_trip_*, c1_15_outbound_stamp_*, c1_15_clean_sample_passes_through).

Status: done

Annex C C.2.1 Outbound Connection

Spec: Annex C §C.2.1, S. 130 — Bridge connectet zu konfiguriertem Upstream-Broker.

Repo: client::connect_outbound + Integration-Test client::tests::outbound_connect_to_local_server fährt echten TCP-Connect + AMQP-Open-Roundtrip gegen lokalen Server-Daemon. C.2.4 (Reconnect) baut darauf auf.

Tests: outbound_connect_to_local_server, c2_4_single_connect_attempt_works_for_normal_path.

Status: done

Spec: Annex C §C.2.2, S. 130 — Bridge attached Sender-Link für outbound Topic-Mapping.

Repo: Bridge-DDS-Side-Subscribe (subscribe_outbound) + AMQP-Outbound-Producer-Pfad. Test-Roundtrip über zwei InMemoryDdsHost-Instanzen, die den Broker-Hop simulieren.

Tests: c2_2_bridge_outbound_publish_flows_to_dds.

Status: done

Spec: Annex C §C.2.3, S. 130 — analog zu C.2.2 in Gegenrichtung.

Repo: Bridge-Receiver-Side ruft bridge::dispatch_transfer mit eingehendem Broker-Sample → DDS-DataWriter publishtet.

Tests: c2_3_bridge_inbound_from_broker_flows_to_dds.

Status: done

Annex C C.2.4 Reconnect on Loss

Spec: Annex C §C.2.4, S. 131 — Connection-Loss → Reconnect innerhalb konfiguriertem Backoff-Cap (default 60s); HISTORY- Eviction zählt.

Repo: client::connect_with_reconnect mit ReconnectConfig (Default 1s init, 2x mult, 60s cap gemäß Spec §10.8). Integration-Test c2_4_reconnect_on_loss.rs.

Tests: c2_4_reconnect_loop_pacing_follows_spec_defaults, c2_4_reconnect_aborts_on_max_attempts, c2_4_reconnect_after_server_restart_eventually_succeeds, c2_4_single_connect_attempt_works_for_normal_path.

Status: done

Annex C C.2.5 Settlement Pass-Through

Spec: Annex C §C.2.5, S. 131 — Broker-Settlement → DataWriter-Notification.

Repo: link::LinkSession::deliver/settle Pending-Tracking + dds_bridge::DispositionMapper-Trait (apply mit DDS-Side- Sample-Handle). DataWriter-Acknowledgment ist Caller-Layer (echter DcpsDdsHost ruft DataWriter::wait_for_acknowledgment).

Tests: link::tests::settle_decrements_pending, dds_bridge::tests::noop_disposition_mapper_does_nothing.

Status: done

Annex C C.2.6 Dual-Identity Separation

Spec: Annex C §C.2.6, S. 131 — Bridge präsentiert DDS-Side-Identity (CN=Bridge-1) statt Broker-Side-Credential (Alice) zu DDS-Security-AccessControl.

Repo: security::DualIdentity mit for_broker()/for_dds() Getter; AccessControl-Plugin verwendet ausschließlich for_dds(). Lib-Tests in security::tests decken den Spec-§C.2.6-Pfad direkt ab.

Tests: security::tests::dual_identity_keeps_broker_and_dds_separate, security::tests::dual_identity_for_dds_does_not_carry_broker_credential.

Status: done

Annex C C.2.7 Loop-Prevention (Bridge)

Spec: Annex C §C.2.7, S. 132 — analog C.1.15 für Bridge- Profile (Outbound + Inbound).

Repo: coexistence-Layer ist Profile-agnostisch; Bridge- Profile bindet das gleiche inspect_inbound/stamp_outbound- Paar. Integration-Test c2_7_loop_prevention_bridge.rs.

Tests: c2_7a_outbound_then_inbound_round_trip_drops_self, c2_7b_hop_cap_exceeded_drops_with_metric, c2_7_multi_bridge_chain_terminates_at_hop_cap, c2_7_foreign_bridges_in_history_dont_trigger_self_drop.

Status: done

Annex C C.2.8 Dual Management Surface

Spec: Annex C §C.2.8, S. 132 — Bridge DDS-side exponiert catalog/metrics/$audit; Upstream-Broker-Side governed by broker.

Repo: bridge::dispatch_attach erkennt AddressKind::{Catalog, Metrics, Audit} und liefert die entsprechenden AttachOutcome-Varianten; produce_catalog_transfers liefert Catalog-Stream aus dem DdsHost. Upstream-Broker-Side managementgehört zum Broker (RabbitMQ-HTTP-API, etc.); kein Bridge-Code.

Tests: c1_8_catalog_receiver_gets_one_sample_per_topic, bridge::tests::dispatch_attach_to_metrics_recognised, dispatch_attach_to_audit_recognised.

Status: done

Annex C C.3 Codec-Profile Tests

Spec: Annex C §C.3, S. 134 — In-Process-Type-System- Roundtrips für alle Primitives, Composites, Sections.

Repo: crates/amqp-bridge/src/types.rs::tests, extended_types.rs::tests, frame.rs::tests, performatives.rs::tests, sections.rs::tests.

Tests: cargo test -p zerodds-amqp-bridge --lib: 70 Tests grün.

Status: done

Annex C C.4 Codec-Lite-Profile Tests

Spec: Annex C §C.4, S. 135 — strikte Untermenge von C.3.

Repo: cargo test -p zerodds-amqp-bridge --features codec-lite läuft das volle Codec-Test-Set + die 5 zusätzlichen codec_profile-Tests, die das Lite-Subset markieren. Conformance- Marker active_profile() liefert CodecProfile::Lite unter dem Feature.

Tests: alle zerodds-amqp-bridge-Tests (75 grün) + codec_profile::tests::active_profile_full_by_default (umgekehrt unter --features codec-lite).

Status: done

Annex C C.5 Test Harness

Spec: Annex C §C.5, S. 136 — vendor-neutrales Wording; Implementer SHOULD veröffentlicht harness-source.

Status: done — strukturell (Harness-Wahl ist implementation-defined).


Annex D — DDS-RPC Correlation Mapping (normative when activated)

Annex D D.1 Mapping Table

Spec: Annex D §D.1, S. 138 — requestIdmessage-id (override des default per-Sample-Identifier), relatedRequestIdcorrelation-id, reply-Topic→reply-to, service-instance→dds:rpc-instance, RPC-operation→dds:rpc-operation.

Repo: rpc_correlation::ReplyProperties::from_amqp liest correlation-id (Str/Symbol/Uuid/Binary) und normalisiert auf String-Lookup-Key. OutstandingCalls::issue trägt request_id (= message-id) ein.

Tests: rpc_correlation::tests::from_amqp_handles_str_symbol_uuid_binary.

Status: done

Annex D D.2 Activation

Spec: Annex D §D.2, S. 139 — TopicMapping.rpc_aware = true aktiviert; Default false.

Repo: rpc_correlation::RpcConfig::rpc_aware-Feld (Default false); OutstandingCalls::new(cfg) instanziiert nur wenn rpc_aware = true aktiviert wird.

Tests: rpc_correlation::tests::defaults_match_spec.

Status: done

Annex D D.3 Scope of this Annex

Spec: Annex D §D.3, S. 139 — kein wire-level RPC-Bridge, nur Property-Mapping; volles RPC-over-AMQP ist spätere Spec.

Status: done — Spec-Inhalts-Klassifikation.

Annex D D.4 Reply Validation

Spec: Annex D §D.4, S. 140 — Reply-Acceptance: correlation-id PFLICHT, Match auf outstanding RPC-Call PFLICHT, Body-Decode mode-abhängig (PASSTHROUGH/JSON/ AMQP_NATIVE). Failure-Dispositions Tabelle. Per-Call-Timeout rpc_timeout_ms (default 30000) → RETCODE_TIMEOUT. Non- Blocking-Guarantee. Bounded outstanding-calls table mit RETCODE_OUT_OF_RESOURCES.

Repo: rpc_correlation::OutstandingCalls mit BTreeMap- basierter bounded Tabelle (max_outstanding, default 4096), validate_reply mit allen 4 Failure-Dispositionen (RejectMalformed/DropUnknown/DecodeFailure/DropLateReply + Surface), expire_overdue mit Per-Call-Timeout, issue liefert OutOfResources bei voller Tabelle. Wires gegen MetricsHub::on_dropped_malformed_reply / on_rpc_timeout / on_decode_error.

Tests: rpc_correlation::tests (13 Tests inkl. validate_reply_rejects_missing_correlation_id, validate_reply_drops_unknown_correlation_id, validate_reply_late_reply_dropped, expire_overdue_removes_and_counts, issue_accepts_then_full, validate_reply_does_not_block_on_table_full).

Status: done


Audit-Status

123 done / 0 partial / 0 open / 1 n/a (informative) / 0 n/a (rejected).

Test-Lauf:

  • cargo test -p zerodds-amqp-bridge --lib — 82 Tests grün.
  • cargo test -p zerodds-amqp-bridge --features codec-lite --lib — 82 Tests grün.
  • cargo test -p zerodds-amqp-endpoint --lib — 198 Tests grün.
  • cargo test -p zerodds-amqp-endpoint --test annex_a_idl_roundtrip — 17 Tests grün.
  • cargo test -p zerodds-amqp-endpoint --test e2e_multi_bridge_hop — 6 Tests grün.
  • cargo test -p zerodds-amqp-endpoint --test fuzz_smoke — 4 Tests grün.
  • cargo test -p zerodds-amqp-endpoint --test proptest_state_machine — 6 Tests grün.
  • cargo test -p amqp-dds-endpoint — 54 Tests grün (ohne tls-Feature).
  • cargo test -p amqp-dds-endpoint --features tls — 60 Tests grün.
  • cargo test -p zerodds-idl-cpp (amqp::tests::*) — 18 Tests grün.
  • cargo test -p zerodds-idl-java (amqp::tests::*) — 13 Tests grün.
  • cargo test -p zerodds-idl-ts (amqp::tests::*) — 16 Tests grün.

Cross-Crate Test-Volumen: 313 + Lang-Codegen-AMQP-Sub-Tests gegen die DDS-AMQP-1.0-Spec.