Skip to main content

zerodds_corba_giop/
request.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! Request-Message — Spec §15.4.2.
5//!
6//! GIOP 1.0 (`§15.4.2.1`):
7//! ```text
8//! struct RequestHeader_1_0 {
9//!     IOP::ServiceContextList service_context;
10//!     unsigned long           request_id;
11//!     boolean                 response_expected;
12//!     sequence<octet>         object_key;
13//!     string                  operation;
14//!     CSI::AuthorizationToken requesting_principal;  // sequence<octet>
15//! };
16//! ```
17//!
18//! GIOP 1.1 — wie 1.0 plus 3-Byte-`reserved` zwischen
19//! `response_expected` und `object_key` (Spec §15.4.2.1).
20//!
21//! GIOP 1.2 (`§15.4.2.2`):
22//! ```text
23//! struct RequestHeader_1_2 {
24//!     unsigned long           request_id;
25//!     octet                   response_flags;
26//!     octet                   reserved[3];
27//!     TargetAddress           target;
28//!     string                  operation;
29//!     IOP::ServiceContextList service_context;
30//!     // body follows after CDR-alignment to 8
31//! };
32//! ```
33
34use alloc::string::String;
35use alloc::vec::Vec;
36
37use zerodds_cdr::{BufferReader, BufferWriter};
38
39use crate::error::{GiopError, GiopResult};
40use crate::service_context::ServiceContextList;
41use crate::target_address::TargetAddress;
42use crate::version::Version;
43
44/// `response_flags`-Octet (Spec §15.4.2.2).
45///
46/// Bits encodieren das `Messaging::SyncScope`-Modell aus CORBA-
47/// Messaging (Spec §22.2.5):
48/// * `SYNC_NONE = 0x00` — fire-and-forget.
49/// * `SYNC_WITH_TRANSPORT = 0x01` — Reply nach Transport-Buffer-Send.
50/// * `SYNC_WITH_SERVER = 0x02` — Reply nach Server-Receive.
51/// * `SYNC_WITH_TARGET = 0x03` — Reply nach Servant-Invocation
52///   (klassisches synchrones Verhalten).
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
54pub struct ResponseFlags(pub u8);
55
56impl ResponseFlags {
57    /// `SYNC_NONE` (Spec §22.2.5).
58    pub const SYNC_NONE: Self = Self(0x00);
59    /// `SYNC_WITH_TRANSPORT`.
60    pub const SYNC_WITH_TRANSPORT: Self = Self(0x01);
61    /// `SYNC_WITH_SERVER`.
62    pub const SYNC_WITH_SERVER: Self = Self(0x02);
63    /// `SYNC_WITH_TARGET` — Default fuer synchrone GIOP-1.0/1.1-
64    /// Requests (`response_expected = true`).
65    pub const SYNC_WITH_TARGET: Self = Self(0x03);
66
67    /// `true` wenn der Caller einen Reply erwartet (`SyncScope >=
68    /// SYNC_WITH_SERVER`, Spec §22.2.5).
69    #[must_use]
70    pub const fn response_expected(self) -> bool {
71        self.0 >= Self::SYNC_WITH_SERVER.0
72    }
73
74    /// Konvertiert von GIOP-1.0/1.1-Boolean zu Flags-Octet.
75    #[must_use]
76    pub const fn from_response_expected(response_expected: bool) -> Self {
77        if response_expected {
78            Self::SYNC_WITH_TARGET
79        } else {
80            Self::SYNC_NONE
81        }
82    }
83}
84
85/// Request-Message-Body (versions-uniform Repraesentation).
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Request {
88    /// `request_id`.
89    pub request_id: u32,
90    /// `response_flags` (GIOP 1.2) bzw. `response_expected`-Aequivalent.
91    pub response_flags: ResponseFlags,
92    /// Object-Adressing — in GIOP 1.0/1.1 immer `Key`; in GIOP 1.2
93    /// kann jede `TargetAddress`-Variante vorkommen.
94    pub target: TargetAddress,
95    /// Operation-Name (`string`, NUL-terminiert ueber CDR).
96    pub operation: String,
97    /// `requesting_principal` (`CSI::AuthorizationToken` =
98    /// `sequence<octet>`). In GIOP 1.2 entfernt; wir halten das Feld
99    /// als Option fuer Versions-Inter-Op.
100    pub requesting_principal: Option<Vec<u8>>,
101    /// `service_context_list`.
102    pub service_context: ServiceContextList,
103    /// Body-Bytes (CDR-encoded `in`/`inout`-Args). Caller liefert
104    /// die fertig-kodierten Bytes; dieser Codec haengt sie unaligned
105    /// an den Header — das `body`-Alignment-Anchor (8 Bytes ab
106    /// Header-Start) ist in GIOP 1.2 spec-relevant und wird vom
107    /// Encoder erzwungen (Spec §15.4.2.2 normativ).
108    pub body: Vec<u8>,
109}
110
111impl Request {
112    /// Konstruktor.
113    #[must_use]
114    pub fn new(
115        request_id: u32,
116        response_flags: ResponseFlags,
117        target: TargetAddress,
118        operation: String,
119    ) -> Self {
120        Self {
121            request_id,
122            response_flags,
123            target,
124            operation,
125            requesting_principal: None,
126            service_context: ServiceContextList::default(),
127            body: Vec::new(),
128        }
129    }
130
131    /// CDR-Encode in einen `BufferWriter`. Body-Alignment auf 8 Bytes
132    /// ab Header-Start (= 8 Bytes ab Buffer-Start in GIOP 1.2,
133    /// Spec §15.4 normativ).
134    ///
135    /// # Errors
136    /// Buffer-Schreibfehler oder Length-Overflow.
137    pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
138        if version.uses_v1_2_request_layout() {
139            // GIOP 1.2 — request_id zuerst.
140            w.write_u32(self.request_id)?;
141            w.write_u8(self.response_flags.0)?;
142            // 3 Bytes reserved.
143            w.write_u8(0)?;
144            w.write_u8(0)?;
145            w.write_u8(0)?;
146            // TargetAddress union.
147            self.target.encode(w)?;
148            write_string(w, &self.operation)?;
149            self.service_context.encode(w)?;
150            // Body 8-Byte-aligned (Spec §15.4 normativ ab GIOP 1.2).
151            w.align(8);
152        } else {
153            // GIOP 1.0 / 1.1 — service_context zuerst.
154            self.service_context.encode(w)?;
155            w.write_u32(self.request_id)?;
156            // boolean response_expected.
157            w.write_u8(u8::from(self.response_flags.response_expected()))?;
158            if version >= Version::V1_1 {
159                // GIOP 1.1: 3-Byte-reserved.
160                w.write_u8(0)?;
161                w.write_u8(0)?;
162                w.write_u8(0)?;
163            }
164            // object_key sequence<octet> — TargetAddress muss
165            // KeyAddr-Form sein.
166            let key = match &self.target {
167                TargetAddress::Key(k) => k.as_slice(),
168                _ => {
169                    return Err(GiopError::Malformed(
170                        "GIOP 1.0/1.1 only supports TargetAddress::Key".into(),
171                    ));
172                }
173            };
174            let n = u32::try_from(key.len())
175                .map_err(|_| GiopError::Malformed("object_key too long".into()))?;
176            w.write_u32(n)?;
177            w.write_bytes(key)?;
178            write_string(w, &self.operation)?;
179            // requesting_principal sequence<octet>.
180            let p = self.requesting_principal.as_deref().unwrap_or(&[]);
181            let pn = u32::try_from(p.len())
182                .map_err(|_| GiopError::Malformed("principal too long".into()))?;
183            w.write_u32(pn)?;
184            w.write_bytes(p)?;
185        }
186        // Body-Bytes.
187        w.write_bytes(&self.body)?;
188        Ok(())
189    }
190
191    /// CDR-Decode aus `BufferReader`.
192    ///
193    /// # Errors
194    /// Buffer-Lesefehler.
195    pub fn decode(version: Version, r: &mut BufferReader<'_>) -> GiopResult<Self> {
196        if version.uses_v1_2_request_layout() {
197            let request_id = r.read_u32()?;
198            let response_flags = ResponseFlags(r.read_u8()?);
199            // Skip 3 reserved bytes.
200            let _ = r.read_u8()?;
201            let _ = r.read_u8()?;
202            let _ = r.read_u8()?;
203            let target = TargetAddress::decode(r)?;
204            let operation = read_string(r)?;
205            let service_context = ServiceContextList::decode(r)?;
206            // Body-Alignment 8 ab Buffer-Start.
207            r.align(8)?;
208            let body = r.read_bytes(r.remaining())?.to_vec();
209            Ok(Self {
210                request_id,
211                response_flags,
212                target,
213                operation,
214                requesting_principal: None,
215                service_context,
216                body,
217            })
218        } else {
219            let service_context = ServiceContextList::decode(r)?;
220            let request_id = r.read_u32()?;
221            let response_expected = r.read_u8()? != 0;
222            let response_flags = ResponseFlags::from_response_expected(response_expected);
223            if version >= Version::V1_1 {
224                let _ = r.read_u8()?;
225                let _ = r.read_u8()?;
226                let _ = r.read_u8()?;
227            }
228            let key_len = r.read_u32()? as usize;
229            let key_bytes = r.read_bytes(key_len)?;
230            let target = TargetAddress::Key(key_bytes.to_vec());
231            let operation = read_string(r)?;
232            let pn = r.read_u32()? as usize;
233            let principal = r.read_bytes(pn)?.to_vec();
234            let body = r.read_bytes(r.remaining())?.to_vec();
235            Ok(Self {
236                request_id,
237                response_flags,
238                target,
239                operation,
240                requesting_principal: Some(principal),
241                service_context,
242                body,
243            })
244        }
245    }
246}
247
248/// CDR-`string`-Encoder (length-prefix + bytes + NUL-terminator).
249fn write_string(w: &mut BufferWriter, s: &str) -> GiopResult<()> {
250    w.write_string(s)?;
251    Ok(())
252}
253
254/// CDR-`string`-Decoder.
255fn read_string(r: &mut BufferReader<'_>) -> GiopResult<String> {
256    Ok(r.read_string()?)
257}
258
259#[cfg(test)]
260#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
261mod tests {
262    use super::*;
263    use zerodds_cdr::Endianness;
264
265    #[test]
266    fn response_flags_sync_levels_match_spec() {
267        // Spec §22.2.5.
268        assert_eq!(ResponseFlags::SYNC_NONE.0, 0);
269        assert_eq!(ResponseFlags::SYNC_WITH_TRANSPORT.0, 1);
270        assert_eq!(ResponseFlags::SYNC_WITH_SERVER.0, 2);
271        assert_eq!(ResponseFlags::SYNC_WITH_TARGET.0, 3);
272    }
273
274    #[test]
275    fn response_expected_returns_true_at_with_server_or_higher() {
276        assert!(!ResponseFlags::SYNC_NONE.response_expected());
277        assert!(!ResponseFlags::SYNC_WITH_TRANSPORT.response_expected());
278        assert!(ResponseFlags::SYNC_WITH_SERVER.response_expected());
279        assert!(ResponseFlags::SYNC_WITH_TARGET.response_expected());
280    }
281
282    fn sample_request(target: TargetAddress) -> Request {
283        Request {
284            request_id: 7,
285            response_flags: ResponseFlags::SYNC_WITH_TARGET,
286            target,
287            operation: "ping".into(),
288            requesting_principal: Some(alloc::vec::Vec::new()),
289            service_context: ServiceContextList::default(),
290            body: alloc::vec![1, 2, 3, 4],
291        }
292    }
293
294    #[test]
295    fn round_trip_giop_1_0_request() {
296        let req = sample_request(TargetAddress::Key(alloc::vec![0xab, 0xcd]));
297        let mut w = BufferWriter::new(Endianness::Big);
298        req.encode(Version::V1_0, &mut w).unwrap();
299        let bytes = w.into_bytes();
300        let mut r = BufferReader::new(&bytes, Endianness::Big);
301        let decoded = Request::decode(Version::V1_0, &mut r).unwrap();
302        assert_eq!(decoded, req);
303    }
304
305    #[test]
306    fn round_trip_giop_1_1_request_with_reserved_bytes() {
307        let req = sample_request(TargetAddress::Key(alloc::vec![0x10, 0x20, 0x30]));
308        let mut w = BufferWriter::new(Endianness::Little);
309        req.encode(Version::V1_1, &mut w).unwrap();
310        let bytes = w.into_bytes();
311        let mut r = BufferReader::new(&bytes, Endianness::Little);
312        let decoded = Request::decode(Version::V1_1, &mut r).unwrap();
313        assert_eq!(decoded, req);
314    }
315
316    #[test]
317    fn round_trip_giop_1_2_request_with_target_address() {
318        let mut req = sample_request(TargetAddress::Key(alloc::vec![0x11, 0x22]));
319        // GIOP 1.2 — kein requesting_principal.
320        req.requesting_principal = None;
321        let mut w = BufferWriter::new(Endianness::Big);
322        req.encode(Version::V1_2, &mut w).unwrap();
323        let bytes = w.into_bytes();
324        let mut r = BufferReader::new(&bytes, Endianness::Big);
325        let decoded = Request::decode(Version::V1_2, &mut r).unwrap();
326        assert_eq!(decoded, req);
327    }
328
329    #[test]
330    fn giop_1_0_rejects_profile_target_address() {
331        let req = sample_request(TargetAddress::Profile(alloc::vec![1, 2]));
332        let mut w = BufferWriter::new(Endianness::Big);
333        let err = req.encode(Version::V1_0, &mut w).unwrap_err();
334        assert!(matches!(err, GiopError::Malformed(_)));
335    }
336
337    #[test]
338    fn giop_1_2_request_body_is_8_aligned() {
339        // Spec §15.4 normativ: in GIOP 1.2 ist Body 8-aligned ab
340        // Header-Start. Wir testen indirekt ueber Buffer-Position.
341        let req = Request {
342            request_id: 1,
343            response_flags: ResponseFlags::SYNC_WITH_TARGET,
344            target: TargetAddress::Key(alloc::vec![0xa]),
345            operation: "x".into(),
346            requesting_principal: None,
347            service_context: ServiceContextList::default(),
348            body: alloc::vec![0xff],
349        };
350        let mut w = BufferWriter::new(Endianness::Big);
351        req.encode(Version::V1_2, &mut w).unwrap();
352        let bytes = w.into_bytes();
353        // Letzten Body-Byte rueckwaerts suchen.
354        let body_pos = bytes.iter().rposition(|b| *b == 0xff).unwrap();
355        assert_eq!(
356            body_pos % 8,
357            0,
358            "body must be 8-byte aligned, got pos {body_pos}"
359        );
360    }
361}