use alloc::vec::Vec;
use zerodds_cdr::{BufferReader, BufferWriter};
use crate::error::{GiopError, GiopResult};
use crate::service_context::ServiceContextList;
use crate::version::Version;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ReplyStatusType {
NoException = 0,
UserException = 1,
SystemException = 2,
LocationForward = 3,
LocationForwardPerm = 4,
NeedsAddressingMode = 5,
}
impl ReplyStatusType {
#[must_use]
pub const fn as_u32(self) -> u32 {
self as u32
}
pub fn from_u32(value: u32, version: Version) -> GiopResult<Self> {
match value {
0 => Ok(Self::NoException),
1 => Ok(Self::UserException),
2 => Ok(Self::SystemException),
3 => Ok(Self::LocationForward),
4 if version.uses_v1_2_request_layout() => Ok(Self::LocationForwardPerm),
5 if version.uses_v1_2_request_layout() => Ok(Self::NeedsAddressingMode),
4..=5 => Err(GiopError::Malformed(alloc::format!(
"ReplyStatus {value} only valid in GIOP 1.2+, got {}.{}",
version.major,
version.minor
))),
other => Err(GiopError::UnknownReplyStatus(other)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reply {
pub request_id: u32,
pub reply_status: ReplyStatusType,
pub service_context: ServiceContextList,
pub body: Vec<u8>,
}
impl Reply {
pub fn encode(&self, version: Version, w: &mut BufferWriter) -> GiopResult<()> {
match self.reply_status {
ReplyStatusType::LocationForwardPerm | ReplyStatusType::NeedsAddressingMode
if !version.uses_v1_2_request_layout() =>
{
return Err(GiopError::Malformed(alloc::format!(
"ReplyStatus {:?} only valid in GIOP 1.2+",
self.reply_status
)));
}
_ => {}
}
if version.uses_v1_2_request_layout() {
w.write_u32(self.request_id)?;
w.write_u32(self.reply_status.as_u32())?;
self.service_context.encode(w)?;
w.align(8);
} else {
self.service_context.encode(w)?;
w.write_u32(self.request_id)?;
w.write_u32(self.reply_status.as_u32())?;
}
w.write_bytes(&self.body)?;
Ok(())
}
pub fn decode(version: Version, r: &mut BufferReader<'_>) -> GiopResult<Self> {
if version.uses_v1_2_request_layout() {
let request_id = r.read_u32()?;
let status_raw = r.read_u32()?;
let reply_status = ReplyStatusType::from_u32(status_raw, version)?;
let service_context = ServiceContextList::decode(r)?;
r.align(8)?;
let body = r.read_bytes(r.remaining())?.to_vec();
Ok(Self {
request_id,
reply_status,
service_context,
body,
})
} else {
let service_context = ServiceContextList::decode(r)?;
let request_id = r.read_u32()?;
let status_raw = r.read_u32()?;
let reply_status = ReplyStatusType::from_u32(status_raw, version)?;
let body = r.read_bytes(r.remaining())?.to_vec();
Ok(Self {
request_id,
reply_status,
service_context,
body,
})
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use zerodds_cdr::Endianness;
#[test]
fn reply_status_values_match_spec() {
assert_eq!(ReplyStatusType::NoException.as_u32(), 0);
assert_eq!(ReplyStatusType::UserException.as_u32(), 1);
assert_eq!(ReplyStatusType::SystemException.as_u32(), 2);
assert_eq!(ReplyStatusType::LocationForward.as_u32(), 3);
assert_eq!(ReplyStatusType::LocationForwardPerm.as_u32(), 4);
assert_eq!(ReplyStatusType::NeedsAddressingMode.as_u32(), 5);
}
#[test]
fn round_trip_giop_1_0_no_exception() {
let r = Reply {
request_id: 42,
reply_status: ReplyStatusType::NoException,
service_context: ServiceContextList::default(),
body: alloc::vec![0xde, 0xad],
};
let mut w = BufferWriter::new(Endianness::Big);
r.encode(Version::V1_0, &mut w).unwrap();
let bytes = w.into_bytes();
let mut rd = BufferReader::new(&bytes, Endianness::Big);
let decoded = Reply::decode(Version::V1_0, &mut rd).unwrap();
assert_eq!(decoded, r);
}
#[test]
fn round_trip_giop_1_2_user_exception() {
let r = Reply {
request_id: 1,
reply_status: ReplyStatusType::UserException,
service_context: ServiceContextList::default(),
body: alloc::vec![1, 2, 3, 4, 5, 6, 7, 8],
};
let mut w = BufferWriter::new(Endianness::Little);
r.encode(Version::V1_2, &mut w).unwrap();
let bytes = w.into_bytes();
let mut rd = BufferReader::new(&bytes, Endianness::Little);
let decoded = Reply::decode(Version::V1_2, &mut rd).unwrap();
assert_eq!(decoded, r);
}
#[test]
fn round_trip_location_forward_perm_in_1_2() {
let r = Reply {
request_id: 5,
reply_status: ReplyStatusType::LocationForwardPerm,
service_context: ServiceContextList::default(),
body: alloc::vec::Vec::new(),
};
let mut w = BufferWriter::new(Endianness::Big);
r.encode(Version::V1_2, &mut w).unwrap();
let bytes = w.into_bytes();
let mut rd = BufferReader::new(&bytes, Endianness::Big);
let decoded = Reply::decode(Version::V1_2, &mut rd).unwrap();
assert_eq!(decoded, r);
}
#[test]
fn location_forward_perm_in_giop_1_0_is_rejected() {
let r = Reply {
request_id: 1,
reply_status: ReplyStatusType::LocationForwardPerm,
service_context: ServiceContextList::default(),
body: alloc::vec::Vec::new(),
};
let mut w = BufferWriter::new(Endianness::Big);
let err = r.encode(Version::V1_0, &mut w).unwrap_err();
assert!(matches!(err, GiopError::Malformed(_)));
}
#[test]
fn unknown_reply_status_is_diagnostic() {
let mut w = BufferWriter::new(Endianness::Big);
ServiceContextList::default().encode(&mut w).unwrap();
w.write_u32(1).unwrap();
w.write_u32(99).unwrap();
let bytes = w.into_bytes();
let mut rd = BufferReader::new(&bytes, Endianness::Big);
let err = Reply::decode(Version::V1_0, &mut rd).unwrap_err();
assert!(matches!(err, GiopError::UnknownReplyStatus(99)));
}
}