1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[repr(u32)]
43pub enum ReplyStatusType {
44 NoException = 0,
46 UserException = 1,
48 SystemException = 2,
50 LocationForward = 3,
53 LocationForwardPerm = 4,
55 NeedsAddressingMode = 5,
58}
59
60impl ReplyStatusType {
61 #[must_use]
63 pub const fn as_u32(self) -> u32 {
64 self as u32
65 }
66
67 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#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct Reply {
94 pub request_id: u32,
96 pub reply_status: ReplyStatusType,
98 pub service_context: ServiceContextList,
100 pub body: Vec<u8>,
103}
104
105impl Reply {
106 pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
111 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 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 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 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}