zerodds-dcps 1.0.0-rc.1

DCPS Public API (OMG DDS 1.4 §2.2.2): DomainParticipant, Publisher, Subscriber, Topic, DataWriter, DataReader. Live-Runtime mit SPDP/SEDP/WLP, Built-in-Topics, TypeLookup-Service, Durability-Backend.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors
//! F-TYPES-3 E2E-Test: XTypes-1.3 §7.6.3 PID_ZERODDS_TYPE_ID Wire-Roundtrip.
//!
//! Verifiziert, dass der Vendor-PID 0x8002 sauber durch
//! `PublicationBuiltinTopicData::to_pl_cdr_le` + `from_pl_cdr_le`
//! roundtripped wird, und dass Reader-Match per `TypeMatcher` die
//! Compatibility prüft.

#![allow(
    clippy::expect_used,
    clippy::unwrap_used,
    clippy::panic,
    clippy::print_stderr,
    clippy::print_stdout,
    clippy::field_reassign_with_default,
    clippy::manual_flatten,
    clippy::collapsible_if,
    clippy::empty_line_after_doc_comments,
    clippy::uninlined_format_args,
    clippy::drop_non_drop,
    missing_docs
)]

extern crate alloc;

use zerodds_qos as qos;
use zerodds_rtps::publication_data::PublicationBuiltinTopicData;
use zerodds_rtps::subscription_data::SubscriptionBuiltinTopicData;
use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
use zerodds_types::qos::TypeConsistencyEnforcement;
use zerodds_types::resolve::TypeRegistry;
use zerodds_types::type_matcher::TypeMatcher;
use zerodds_types::{PrimitiveKind, TypeIdentifier};

fn mk_pubd(type_id: TypeIdentifier) -> PublicationBuiltinTopicData {
    PublicationBuiltinTopicData {
        key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
        participant_key: Guid::new(GuidPrefix::from_bytes([1; 12]), EntityId::PARTICIPANT),
        topic_name: "T".into(),
        type_name: "TypeA".into(),
        durability: qos::DurabilityKind::Volatile,
        reliability: qos::ReliabilityQosPolicy::default(),
        ownership: qos::OwnershipKind::Shared,
        ownership_strength: 0,
        liveliness: qos::LivelinessQosPolicy::default(),
        deadline: qos::DeadlineQosPolicy::default(),
        lifespan: qos::LifespanQosPolicy::default(),
        partition: alloc::vec::Vec::new(),
        user_data: alloc::vec::Vec::new(),
        topic_data: alloc::vec::Vec::new(),
        group_data: alloc::vec::Vec::new(),
        type_information: None,
        data_representation: alloc::vec::Vec::new(),
        security_info: None,
        service_instance_name: None,
        related_entity_guid: None,
        topic_aliases: None,
        type_identifier: type_id,
    }
}

fn mk_subd(type_id: TypeIdentifier) -> SubscriptionBuiltinTopicData {
    SubscriptionBuiltinTopicData {
        key: Guid::new(GuidPrefix::from_bytes([2; 12]), EntityId::PARTICIPANT),
        participant_key: Guid::new(GuidPrefix::from_bytes([2; 12]), EntityId::PARTICIPANT),
        topic_name: "T".into(),
        type_name: "TypeA".into(),
        durability: qos::DurabilityKind::Volatile,
        reliability: qos::ReliabilityQosPolicy::default(),
        ownership: qos::OwnershipKind::Shared,
        liveliness: qos::LivelinessQosPolicy::default(),
        deadline: qos::DeadlineQosPolicy::default(),
        partition: alloc::vec::Vec::new(),
        user_data: alloc::vec::Vec::new(),
        topic_data: alloc::vec::Vec::new(),
        group_data: alloc::vec::Vec::new(),
        type_information: None,
        data_representation: alloc::vec::Vec::new(),
        content_filter: None,
        security_info: None,
        service_instance_name: None,
        related_entity_guid: None,
        topic_aliases: None,
        type_identifier: type_id,
    }
}

#[test]
fn pid_zerodds_type_id_publication_roundtrip_primitive() {
    // Wire-Roundtrip mit Primitive-TypeIdentifier.
    let original_id = TypeIdentifier::Primitive(PrimitiveKind::Int32);
    let pubd = mk_pubd(original_id.clone());
    let bytes = pubd.to_pl_cdr_le().unwrap();
    let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
    assert_eq!(decoded.type_identifier, original_id);
}

#[test]
fn pid_zerodds_type_id_subscription_roundtrip_string8() {
    let original_id = TypeIdentifier::String8Small { bound: 64 };
    let subd = mk_subd(original_id.clone());
    let bytes = subd.to_pl_cdr_le().unwrap();
    let decoded = SubscriptionBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
    assert_eq!(decoded.type_identifier, original_id);
}

#[test]
fn pid_zerodds_type_id_default_none_omitted_from_wire() {
    // type_identifier = None: PID nicht emittiert. Decoded bleibt None.
    let pubd = mk_pubd(TypeIdentifier::None);
    let bytes = pubd.to_pl_cdr_le().unwrap();
    let decoded = PublicationBuiltinTopicData::from_pl_cdr_le(&bytes).unwrap();
    assert_eq!(decoded.type_identifier, TypeIdentifier::None);
}

#[test]
fn type_matcher_accepts_identical_primitive_ids() {
    let tce = TypeConsistencyEnforcement::default();
    let m = TypeMatcher::new(&tce);
    let registry = TypeRegistry::new();
    let id = TypeIdentifier::Primitive(PrimitiveKind::Int32);
    assert!(m.match_types(&id, &id, &registry).is_match());
}

#[test]
fn type_matcher_rejects_incompatible_primitives_under_force_validation() {
    let tce = TypeConsistencyEnforcement {
        force_type_validation: true,
        ..Default::default()
    };
    let m = TypeMatcher::new(&tce);
    let registry = TypeRegistry::new();
    let writer = TypeIdentifier::Primitive(PrimitiveKind::Int32);
    let reader = TypeIdentifier::Primitive(PrimitiveKind::Float64);
    assert!(
        !m.match_types(&writer, &reader, &registry).is_match(),
        "Int32 vs Float64 unter force_type_validation darf nicht matchen"
    );
}