1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
54pub struct ResponseFlags(pub u8);
55
56impl ResponseFlags {
57 pub const SYNC_NONE: Self = Self(0x00);
59 pub const SYNC_WITH_TRANSPORT: Self = Self(0x01);
61 pub const SYNC_WITH_SERVER: Self = Self(0x02);
63 pub const SYNC_WITH_TARGET: Self = Self(0x03);
66
67 #[must_use]
70 pub const fn response_expected(self) -> bool {
71 self.0 >= Self::SYNC_WITH_SERVER.0
72 }
73
74 #[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#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Request {
88 pub request_id: u32,
90 pub response_flags: ResponseFlags,
92 pub target: TargetAddress,
95 pub operation: String,
97 pub requesting_principal: Option<Vec<u8>>,
101 pub service_context: ServiceContextList,
103 pub body: Vec<u8>,
109}
110
111impl Request {
112 #[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 pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
138 if version.uses_v1_2_request_layout() {
139 w.write_u32(self.request_id)?;
141 w.write_u8(self.response_flags.0)?;
142 w.write_u8(0)?;
144 w.write_u8(0)?;
145 w.write_u8(0)?;
146 self.target.encode(w)?;
148 write_string(w, &self.operation)?;
149 self.service_context.encode(w)?;
150 w.align(8);
152 } else {
153 self.service_context.encode(w)?;
155 w.write_u32(self.request_id)?;
156 w.write_u8(u8::from(self.response_flags.response_expected()))?;
158 if version >= Version::V1_1 {
159 w.write_u8(0)?;
161 w.write_u8(0)?;
162 w.write_u8(0)?;
163 }
164 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 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 w.write_bytes(&self.body)?;
188 Ok(())
189 }
190
191 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 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 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
248fn write_string(w: &mut BufferWriter, s: &str) -> GiopResult<()> {
250 w.write_string(s)?;
251 Ok(())
252}
253
254fn 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 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 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 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 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}