use alloc::string::String;
use alloc::vec::Vec;
use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct IiopVersion {
pub major: u8,
pub minor: u8,
}
impl IiopVersion {
pub const V1_0: Self = Self { major: 1, minor: 0 };
pub const V1_1: Self = Self { major: 1, minor: 1 };
pub const V1_2: Self = Self { major: 1, minor: 2 };
pub const V1_3: Self = Self { major: 1, minor: 3 };
#[must_use]
pub const fn new(major: u8, minor: u8) -> Self {
Self { major, minor }
}
#[must_use]
pub const fn has_components(self) -> bool {
if self.major > 1 {
true
} else {
self.minor >= 1
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TaggedComponent {
pub tag: u32,
pub component_data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IiopProfileBody {
pub iiop_version: IiopVersion,
pub host: String,
pub port: u16,
pub object_key: Vec<u8>,
pub components: Vec<TaggedComponent>,
}
impl IiopProfileBody {
#[must_use]
pub fn new(version: IiopVersion, host: String, port: u16, object_key: Vec<u8>) -> Self {
Self {
iiop_version: version,
host,
port,
object_key,
components: Vec::new(),
}
}
pub fn encode(&self, w: &mut BufferWriter) -> Result<(), CdrError> {
w.write_u8(self.iiop_version.major)?;
w.write_u8(self.iiop_version.minor)?;
w.write_string(&self.host)?;
w.write_u16(self.port)?;
let n = u32::try_from(self.object_key.len()).map_err(|_| CdrError::Overflow)?;
w.write_u32(n)?;
w.write_bytes(&self.object_key)?;
if self.iiop_version.has_components() {
let cn = u32::try_from(self.components.len()).map_err(|_| CdrError::Overflow)?;
w.write_u32(cn)?;
for c in &self.components {
w.write_u32(c.tag)?;
let cd = u32::try_from(c.component_data.len()).map_err(|_| CdrError::Overflow)?;
w.write_u32(cd)?;
w.write_bytes(&c.component_data)?;
}
}
Ok(())
}
pub fn decode(r: &mut BufferReader<'_>) -> Result<Self, CdrError> {
let major = r.read_u8()?;
let minor = r.read_u8()?;
let iiop_version = IiopVersion::new(major, minor);
let host = r.read_string()?;
let port = r.read_u16()?;
let key_len = r.read_u32()? as usize;
let object_key = r.read_bytes(key_len)?.to_vec();
let components = if iiop_version.has_components() {
let cn = r.read_u32()? as usize;
let mut out = Vec::with_capacity(cn.min(64));
for _ in 0..cn {
let tag = r.read_u32()?;
let cd_len = r.read_u32()? as usize;
let component_data = r.read_bytes(cd_len)?.to_vec();
out.push(TaggedComponent {
tag,
component_data,
});
}
out
} else {
Vec::new()
};
Ok(Self {
iiop_version,
host,
port,
object_key,
components,
})
}
pub fn encode_encapsulation(&self, endianness: Endianness) -> Result<Vec<u8>, CdrError> {
let mut out = Vec::with_capacity(64);
out.push(match endianness {
Endianness::Big => 0,
Endianness::Little => 1,
});
let mut w = BufferWriter::new(endianness);
self.encode(&mut w)?;
out.extend_from_slice(w.as_bytes());
Ok(out)
}
pub fn decode_encapsulation(bytes: &[u8]) -> Result<Self, CdrError> {
if bytes.is_empty() {
return Err(CdrError::Truncated);
}
let endianness = match bytes[0] {
0 => Endianness::Big,
1 => Endianness::Little,
_ => return Err(CdrError::InvalidEndianness),
};
let mut r = BufferReader::new(&bytes[1..], endianness);
Self::decode(&mut r)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CdrError {
Encode(zerodds_cdr::EncodeError),
Decode(zerodds_cdr::DecodeError),
Overflow,
Truncated,
InvalidEndianness,
}
impl From<zerodds_cdr::EncodeError> for CdrError {
fn from(e: zerodds_cdr::EncodeError) -> Self {
Self::Encode(e)
}
}
impl From<zerodds_cdr::DecodeError> for CdrError {
fn from(e: zerodds_cdr::DecodeError) -> Self {
Self::Decode(e)
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn iiop_version_constants_match_spec() {
assert_eq!(IiopVersion::V1_0, IiopVersion::new(1, 0));
assert_eq!(IiopVersion::V1_3, IiopVersion::new(1, 3));
}
#[test]
fn components_only_from_iiop_1_1() {
assert!(!IiopVersion::V1_0.has_components());
assert!(IiopVersion::V1_1.has_components());
assert!(IiopVersion::V1_2.has_components());
assert!(IiopVersion::V1_3.has_components());
}
fn sample_v1_0() -> IiopProfileBody {
IiopProfileBody::new(
IiopVersion::V1_0,
"example.com".into(),
7777,
alloc::vec![0xab, 0xcd],
)
}
fn sample_v1_2_with_components() -> IiopProfileBody {
IiopProfileBody {
iiop_version: IiopVersion::V1_2,
host: "10.0.0.1".into(),
port: 9999,
object_key: alloc::vec![1, 2, 3, 4],
components: alloc::vec![
TaggedComponent {
tag: 0, component_data: alloc::vec![0xde, 0xad],
},
TaggedComponent {
tag: 1, component_data: alloc::vec![1, 2, 3, 4, 5, 6, 7, 8],
},
],
}
}
#[test]
fn round_trip_v1_0_be() {
let p = sample_v1_0();
let bytes = p.encode_encapsulation(Endianness::Big).unwrap();
let decoded = IiopProfileBody::decode_encapsulation(&bytes).unwrap();
assert_eq!(decoded, p);
assert_eq!(bytes[0], 0);
}
#[test]
fn round_trip_v1_0_le() {
let p = sample_v1_0();
let bytes = p.encode_encapsulation(Endianness::Little).unwrap();
let decoded = IiopProfileBody::decode_encapsulation(&bytes).unwrap();
assert_eq!(decoded, p);
assert_eq!(bytes[0], 1);
}
#[test]
fn round_trip_v1_2_with_components() {
let p = sample_v1_2_with_components();
let bytes = p.encode_encapsulation(Endianness::Big).unwrap();
let decoded = IiopProfileBody::decode_encapsulation(&bytes).unwrap();
assert_eq!(decoded, p);
assert_eq!(decoded.components.len(), 2);
}
#[test]
fn v1_0_does_not_emit_components_field() {
let mut p = sample_v1_0();
p.components = alloc::vec![TaggedComponent {
tag: 99,
component_data: alloc::vec![1, 2],
}];
let bytes = p.encode_encapsulation(Endianness::Big).unwrap();
let decoded = IiopProfileBody::decode_encapsulation(&bytes).unwrap();
assert!(decoded.components.is_empty());
}
#[test]
fn invalid_endianness_byte_is_diagnostic() {
let bytes = alloc::vec![0xff, 0, 0];
let err = IiopProfileBody::decode_encapsulation(&bytes).unwrap_err();
assert!(matches!(err, CdrError::InvalidEndianness));
}
#[test]
fn empty_encapsulation_is_truncated() {
let err = IiopProfileBody::decode_encapsulation(&[]).unwrap_err();
assert!(matches!(err, CdrError::Truncated));
}
}