zerodds-corba-giop 1.0.0-rc.1

OMG CORBA 3.3 Part 2 §15 GIOP (General Inter-ORB Protocol) wire codec — alle 8 Message-Types fuer GIOP 1.0/1.1/1.2 inkl. Bidirectional-GIOP. no_std + alloc.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 ZeroDDS Contributors

//! LocateReply-Message — Spec §15.4.6.
//!
//! ```text
//! enum LocateStatusType_1_0 {
//!     UNKNOWN_OBJECT, OBJECT_HERE, OBJECT_FORWARD
//! };
//! enum LocateStatusType_1_2 {
//!     UNKNOWN_OBJECT, OBJECT_HERE, OBJECT_FORWARD,
//!     OBJECT_FORWARD_PERM, LOC_SYSTEM_EXCEPTION,
//!     LOC_NEEDS_ADDRESSING_MODE
//! };
//!
//! struct LocateReplyHeader {
//!     unsigned long       request_id;
//!     LocateStatusType    locate_status;
//!     // body depending on status (GIOP 1.2: 8-aligned)
//! };
//! ```

use alloc::vec::Vec;

use zerodds_cdr::{BufferReader, BufferWriter};

use crate::error::{GiopError, GiopResult};
use crate::version::Version;

/// LocateStatusType — alle 6 Werte (Spec §15.4.6.1).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum LocateStatusType {
    /// `UNKNOWN_OBJECT` — Server kennt das Object nicht.
    UnknownObject = 0,
    /// `OBJECT_HERE` — Object ist hier verfuegbar.
    ObjectHere = 1,
    /// `OBJECT_FORWARD` — Server bittet um Forward (transient).
    ObjectForward = 2,
    /// `OBJECT_FORWARD_PERM` — Forward-Hint persistent (GIOP 1.2+).
    ObjectForwardPerm = 3,
    /// `LOC_SYSTEM_EXCEPTION` — System-Exception statt Locate-Reply
    /// (GIOP 1.2+).
    LocSystemException = 4,
    /// `LOC_NEEDS_ADDRESSING_MODE` — Server braucht anderen
    /// `TargetAddress`-Discriminator (GIOP 1.2+).
    LocNeedsAddressingMode = 5,
}

impl LocateStatusType {
    /// Diskriminanten-Wert.
    #[must_use]
    pub const fn as_u32(self) -> u32 {
        self as u32
    }

    /// Parsing aus `unsigned long`.
    ///
    /// # Errors
    /// `UnknownLocateStatus` ausserhalb 0..=5; `Malformed` wenn Wert
    /// nicht zur Version passt.
    pub fn from_u32(value: u32, version: Version) -> GiopResult<Self> {
        match value {
            0 => Ok(Self::UnknownObject),
            1 => Ok(Self::ObjectHere),
            2 => Ok(Self::ObjectForward),
            3 if version.uses_v1_2_request_layout() => Ok(Self::ObjectForwardPerm),
            4 if version.uses_v1_2_request_layout() => Ok(Self::LocSystemException),
            5 if version.uses_v1_2_request_layout() => Ok(Self::LocNeedsAddressingMode),
            3..=5 => Err(GiopError::Malformed(alloc::format!(
                "LocateStatus {value} only valid in GIOP 1.2+"
            ))),
            other => Err(GiopError::UnknownLocateStatus(other)),
        }
    }
}

/// LocateReply-Body.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocateReply {
    /// `request_id`.
    pub request_id: u32,
    /// Status.
    pub locate_status: LocateStatusType,
    /// Body (Status-abhaengig). Spec §15.4.6.2:
    /// * `OBJECT_FORWARD[_PERM]` → IOR-Bytes (encapsuliert).
    /// * `LOC_SYSTEM_EXCEPTION` → SystemExceptionReplyBody.
    /// * `LOC_NEEDS_ADDRESSING_MODE` → `AddressingDisposition`-short.
    /// * sonst → empty.
    pub body: Vec<u8>,
}

impl LocateReply {
    /// CDR-Encode.
    ///
    /// # Errors
    /// Buffer-Schreibfehler.
    pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
        // Status-Versions-Validierung.
        match self.locate_status {
            LocateStatusType::ObjectForwardPerm
            | LocateStatusType::LocSystemException
            | LocateStatusType::LocNeedsAddressingMode
                if !version.uses_v1_2_request_layout() =>
            {
                return Err(GiopError::Malformed(alloc::format!(
                    "LocateStatus {:?} only valid in GIOP 1.2+",
                    self.locate_status
                )));
            }
            _ => {}
        }
        w.write_u32(self.request_id)?;
        w.write_u32(self.locate_status.as_u32())?;
        if version.uses_v1_2_request_layout() {
            w.align(8);
        }
        w.write_bytes(&self.body)?;
        Ok(())
    }

    /// CDR-Decode.
    ///
    /// # Errors
    /// Buffer-Lesefehler.
    pub fn decode(version: Version, r: &mut BufferReader<'_>) -> GiopResult<Self> {
        let request_id = r.read_u32()?;
        let status_raw = r.read_u32()?;
        let locate_status = LocateStatusType::from_u32(status_raw, version)?;
        if version.uses_v1_2_request_layout() {
            r.align(8)?;
        }
        let body = r.read_bytes(r.remaining())?.to_vec();
        Ok(Self {
            request_id,
            locate_status,
            body,
        })
    }
}

#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
    use super::*;
    use zerodds_cdr::Endianness;

    #[test]
    fn locate_status_values_match_spec() {
        assert_eq!(LocateStatusType::UnknownObject.as_u32(), 0);
        assert_eq!(LocateStatusType::ObjectHere.as_u32(), 1);
        assert_eq!(LocateStatusType::ObjectForward.as_u32(), 2);
        assert_eq!(LocateStatusType::ObjectForwardPerm.as_u32(), 3);
        assert_eq!(LocateStatusType::LocSystemException.as_u32(), 4);
        assert_eq!(LocateStatusType::LocNeedsAddressingMode.as_u32(), 5);
    }

    #[test]
    fn round_trip_giop_1_0_object_here() {
        let l = LocateReply {
            request_id: 1,
            locate_status: LocateStatusType::ObjectHere,
            body: alloc::vec::Vec::new(),
        };
        let mut w = BufferWriter::new(Endianness::Big);
        l.encode(Version::V1_0, &mut w).unwrap();
        let bytes = w.into_bytes();
        let mut r = BufferReader::new(&bytes, Endianness::Big);
        let decoded = LocateReply::decode(Version::V1_0, &mut r).unwrap();
        assert_eq!(decoded, l);
    }

    #[test]
    fn round_trip_giop_1_2_object_forward_perm() {
        let l = LocateReply {
            request_id: 2,
            locate_status: LocateStatusType::ObjectForwardPerm,
            body: alloc::vec![0xff; 4],
        };
        let mut w = BufferWriter::new(Endianness::Little);
        l.encode(Version::V1_2, &mut w).unwrap();
        let bytes = w.into_bytes();
        let mut r = BufferReader::new(&bytes, Endianness::Little);
        let decoded = LocateReply::decode(Version::V1_2, &mut r).unwrap();
        assert_eq!(decoded, l);
    }

    #[test]
    fn forward_perm_in_1_0_is_rejected() {
        let l = LocateReply {
            request_id: 1,
            locate_status: LocateStatusType::ObjectForwardPerm,
            body: alloc::vec::Vec::new(),
        };
        let mut w = BufferWriter::new(Endianness::Big);
        let err = l.encode(Version::V1_0, &mut w).unwrap_err();
        assert!(matches!(err, GiopError::Malformed(_)));
    }
}