Skip to main content

zerodds_corba_giop/
reply.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Reply-Message — Spec §15.4.3.
5//!
6//! GIOP 1.0/1.1 (`§15.4.3.1`):
7//! ```text
8//! enum ReplyStatusType_1_0 {
9//!     NO_EXCEPTION, USER_EXCEPTION, SYSTEM_EXCEPTION, LOCATION_FORWARD
10//! };
11//! struct ReplyHeader_1_0 {
12//!     IOP::ServiceContextList service_context;
13//!     unsigned long           request_id;
14//!     ReplyStatusType_1_0     reply_status;
15//! };
16//! ```
17//!
18//! GIOP 1.2 (`§15.4.3.2`):
19//! ```text
20//! enum ReplyStatusType_1_2 {
21//!     NO_EXCEPTION, USER_EXCEPTION, SYSTEM_EXCEPTION,
22//!     LOCATION_FORWARD, LOCATION_FORWARD_PERM, NEEDS_ADDRESSING_MODE
23//! };
24//! struct ReplyHeader_1_2 {
25//!     unsigned long           request_id;
26//!     ReplyStatusType_1_2     reply_status;
27//!     IOP::ServiceContextList service_context;
28//!     // body 8-aligned
29//! };
30//! ```
31
32use alloc::vec::Vec;
33
34use zerodds_cdr::{BufferReader, BufferWriter};
35
36use crate::error::{GiopError, GiopResult};
37use crate::service_context::ServiceContextList;
38use crate::version::Version;
39
40/// Reply-Status — alle 6 Spec-Werte (Spec §15.4.3.1 + §15.4.3.2).
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[repr(u32)]
43pub enum ReplyStatusType {
44    /// `NO_EXCEPTION` — Operation success.
45    NoException = 0,
46    /// `USER_EXCEPTION` — IDL-deklarierte User-Exception.
47    UserException = 1,
48    /// `SYSTEM_EXCEPTION` — System-Level-Fault.
49    SystemException = 2,
50    /// `LOCATION_FORWARD` — Server bittet Client, an einen anderen
51    /// Endpoint zu gehen (transient).
52    LocationForward = 3,
53    /// `LOCATION_FORWARD_PERM` — wie oben, aber persistent (GIOP 1.2+).
54    LocationForwardPerm = 4,
55    /// `NEEDS_ADDRESSING_MODE` — Server moechte einen anderen
56    /// `TargetAddress`-Discriminator (GIOP 1.2+).
57    NeedsAddressingMode = 5,
58}
59
60impl ReplyStatusType {
61    /// Diskriminanten-Wert.
62    #[must_use]
63    pub const fn as_u32(self) -> u32 {
64        self as u32
65    }
66
67    /// Parsing aus `unsigned long`.
68    ///
69    /// # Errors
70    /// `UnknownReplyStatus` ausserhalb 0..=5; `Malformed` wenn der
71    /// Wert nicht zur GIOP-Version passt (LOCATION_FORWARD_PERM /
72    /// NEEDS_ADDRESSING_MODE in 1.0/1.1 nicht erlaubt).
73    pub fn from_u32(value: u32, version: Version) -> GiopResult<Self> {
74        match value {
75            0 => Ok(Self::NoException),
76            1 => Ok(Self::UserException),
77            2 => Ok(Self::SystemException),
78            3 => Ok(Self::LocationForward),
79            4 if version.uses_v1_2_request_layout() => Ok(Self::LocationForwardPerm),
80            5 if version.uses_v1_2_request_layout() => Ok(Self::NeedsAddressingMode),
81            4..=5 => Err(GiopError::Malformed(alloc::format!(
82                "ReplyStatus {value} only valid in GIOP 1.2+, got {}.{}",
83                version.major,
84                version.minor
85            ))),
86            other => Err(GiopError::UnknownReplyStatus(other)),
87        }
88    }
89}
90
91/// Reply-Message-Body.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Reply {
94    /// `request_id`.
95    pub request_id: u32,
96    /// Reply-Status-Discriminator.
97    pub reply_status: ReplyStatusType,
98    /// `service_context_list`.
99    pub service_context: ServiceContextList,
100    /// Body-Bytes (CDR-encoded `out`/`return`/`Exception`-Inhalt).
101    /// In GIOP 1.2 8-Byte-aligned ab Header-Start.
102    pub body: Vec<u8>,
103}
104
105impl Reply {
106    /// CDR-Encode.
107    ///
108    /// # Errors
109    /// Buffer-Schreibfehler.
110    pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
111        // Status-Validierung gegen Version.
112        match self.reply_status {
113            ReplyStatusType::LocationForwardPerm | ReplyStatusType::NeedsAddressingMode
114                if !version.uses_v1_2_request_layout() =>
115            {
116                return Err(GiopError::Malformed(alloc::format!(
117                    "ReplyStatus {:?} only valid in GIOP 1.2+",
118                    self.reply_status
119                )));
120            }
121            _ => {}
122        }
123        if version.uses_v1_2_request_layout() {
124            w.write_u32(self.request_id)?;
125            w.write_u32(self.reply_status.as_u32())?;
126            self.service_context.encode(w)?;
127            w.align(8);
128        } else {
129            self.service_context.encode(w)?;
130            w.write_u32(self.request_id)?;
131            w.write_u32(self.reply_status.as_u32())?;
132        }
133        w.write_bytes(&self.body)?;
134        Ok(())
135    }
136
137    /// CDR-Decode.
138    ///
139    /// # Errors
140    /// Buffer-Lesefehler oder unbekannter Status.
141    pub fn decode(version: Version, r: &mut BufferReader<'_>) -> GiopResult<Self> {
142        if version.uses_v1_2_request_layout() {
143            let request_id = r.read_u32()?;
144            let status_raw = r.read_u32()?;
145            let reply_status = ReplyStatusType::from_u32(status_raw, version)?;
146            let service_context = ServiceContextList::decode(r)?;
147            r.align(8)?;
148            let body = r.read_bytes(r.remaining())?.to_vec();
149            Ok(Self {
150                request_id,
151                reply_status,
152                service_context,
153                body,
154            })
155        } else {
156            let service_context = ServiceContextList::decode(r)?;
157            let request_id = r.read_u32()?;
158            let status_raw = r.read_u32()?;
159            let reply_status = ReplyStatusType::from_u32(status_raw, version)?;
160            let body = r.read_bytes(r.remaining())?.to_vec();
161            Ok(Self {
162                request_id,
163                reply_status,
164                service_context,
165                body,
166            })
167        }
168    }
169}
170
171#[cfg(test)]
172#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
173mod tests {
174    use super::*;
175    use zerodds_cdr::Endianness;
176
177    #[test]
178    fn reply_status_values_match_spec() {
179        // Spec §15.4.3.1 + §15.4.3.2.
180        assert_eq!(ReplyStatusType::NoException.as_u32(), 0);
181        assert_eq!(ReplyStatusType::UserException.as_u32(), 1);
182        assert_eq!(ReplyStatusType::SystemException.as_u32(), 2);
183        assert_eq!(ReplyStatusType::LocationForward.as_u32(), 3);
184        assert_eq!(ReplyStatusType::LocationForwardPerm.as_u32(), 4);
185        assert_eq!(ReplyStatusType::NeedsAddressingMode.as_u32(), 5);
186    }
187
188    #[test]
189    fn round_trip_giop_1_0_no_exception() {
190        let r = Reply {
191            request_id: 42,
192            reply_status: ReplyStatusType::NoException,
193            service_context: ServiceContextList::default(),
194            body: alloc::vec![0xde, 0xad],
195        };
196        let mut w = BufferWriter::new(Endianness::Big);
197        r.encode(Version::V1_0, &mut w).unwrap();
198        let bytes = w.into_bytes();
199        let mut rd = BufferReader::new(&bytes, Endianness::Big);
200        let decoded = Reply::decode(Version::V1_0, &mut rd).unwrap();
201        assert_eq!(decoded, r);
202    }
203
204    #[test]
205    fn round_trip_giop_1_2_user_exception() {
206        let r = Reply {
207            request_id: 1,
208            reply_status: ReplyStatusType::UserException,
209            service_context: ServiceContextList::default(),
210            body: alloc::vec![1, 2, 3, 4, 5, 6, 7, 8],
211        };
212        let mut w = BufferWriter::new(Endianness::Little);
213        r.encode(Version::V1_2, &mut w).unwrap();
214        let bytes = w.into_bytes();
215        let mut rd = BufferReader::new(&bytes, Endianness::Little);
216        let decoded = Reply::decode(Version::V1_2, &mut rd).unwrap();
217        assert_eq!(decoded, r);
218    }
219
220    #[test]
221    fn round_trip_location_forward_perm_in_1_2() {
222        let r = Reply {
223            request_id: 5,
224            reply_status: ReplyStatusType::LocationForwardPerm,
225            service_context: ServiceContextList::default(),
226            body: alloc::vec::Vec::new(),
227        };
228        let mut w = BufferWriter::new(Endianness::Big);
229        r.encode(Version::V1_2, &mut w).unwrap();
230        let bytes = w.into_bytes();
231        let mut rd = BufferReader::new(&bytes, Endianness::Big);
232        let decoded = Reply::decode(Version::V1_2, &mut rd).unwrap();
233        assert_eq!(decoded, r);
234    }
235
236    #[test]
237    fn location_forward_perm_in_giop_1_0_is_rejected() {
238        let r = Reply {
239            request_id: 1,
240            reply_status: ReplyStatusType::LocationForwardPerm,
241            service_context: ServiceContextList::default(),
242            body: alloc::vec::Vec::new(),
243        };
244        let mut w = BufferWriter::new(Endianness::Big);
245        let err = r.encode(Version::V1_0, &mut w).unwrap_err();
246        assert!(matches!(err, GiopError::Malformed(_)));
247    }
248
249    #[test]
250    fn unknown_reply_status_is_diagnostic() {
251        let mut w = BufferWriter::new(Endianness::Big);
252        // Service-context list (empty) + request_id + invalid status.
253        ServiceContextList::default().encode(&mut w).unwrap();
254        w.write_u32(1).unwrap();
255        w.write_u32(99).unwrap();
256        let bytes = w.into_bytes();
257        let mut rd = BufferReader::new(&bytes, Endianness::Big);
258        let err = Reply::decode(Version::V1_0, &mut rd).unwrap_err();
259        assert!(matches!(err, GiopError::UnknownReplyStatus(99)));
260    }
261}