use alloc::vec::Vec;
use zerodds_cdr::{BufferReader, BufferWriter};
use crate::error::{GiopError, GiopResult};
pub type ObjectKey = Vec<u8>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i16)]
pub enum AddressingDisposition {
KeyAddr = 0,
ProfileAddr = 1,
ReferenceAddr = 2,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TargetAddress {
Key(ObjectKey),
Profile(Vec<u8>),
Reference {
selected_profile_index: u32,
ior: Vec<u8>,
},
}
impl TargetAddress {
#[must_use]
pub const fn disposition(&self) -> AddressingDisposition {
match self {
Self::Key(_) => AddressingDisposition::KeyAddr,
Self::Profile(_) => AddressingDisposition::ProfileAddr,
Self::Reference { .. } => AddressingDisposition::ReferenceAddr,
}
}
pub fn encode(&self, w: &mut BufferWriter) -> GiopResult<()> {
let disc = self.disposition() as i16;
w.align(2);
w.write_u16(disc as u16)?;
match self {
Self::Key(k) => {
let n = u32::try_from(k.len())
.map_err(|_| GiopError::Malformed("object_key too long".into()))?;
w.write_u32(n)?;
w.write_bytes(k)?;
}
Self::Profile(bytes) => {
let n = u32::try_from(bytes.len())
.map_err(|_| GiopError::Malformed("profile too long".into()))?;
w.write_u32(n)?;
w.write_bytes(bytes)?;
}
Self::Reference {
selected_profile_index,
ior,
} => {
w.write_u32(*selected_profile_index)?;
let n = u32::try_from(ior.len())
.map_err(|_| GiopError::Malformed("ior too long".into()))?;
w.write_u32(n)?;
w.write_bytes(ior)?;
}
}
Ok(())
}
pub fn decode(r: &mut BufferReader<'_>) -> GiopResult<Self> {
r.align(2)?;
let disc = r.read_u16()?;
match disc {
0 => {
let n = r.read_u32()? as usize;
let bytes = r.read_bytes(n)?;
Ok(Self::Key(bytes.to_vec()))
}
1 => {
let n = r.read_u32()? as usize;
let bytes = r.read_bytes(n)?;
Ok(Self::Profile(bytes.to_vec()))
}
2 => {
let selected_profile_index = r.read_u32()?;
let n = r.read_u32()? as usize;
let bytes = r.read_bytes(n)?;
Ok(Self::Reference {
selected_profile_index,
ior: bytes.to_vec(),
})
}
other => Err(GiopError::UnknownAddressingDisposition(other)),
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
use zerodds_cdr::Endianness;
#[test]
fn disposition_values_match_spec() {
assert_eq!(AddressingDisposition::KeyAddr as i16, 0);
assert_eq!(AddressingDisposition::ProfileAddr as i16, 1);
assert_eq!(AddressingDisposition::ReferenceAddr as i16, 2);
}
#[test]
fn key_addr_round_trip() {
let t = TargetAddress::Key(alloc::vec![0xde, 0xad, 0xbe, 0xef]);
let mut w = BufferWriter::new(Endianness::Big);
t.encode(&mut w).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Big);
let decoded = TargetAddress::decode(&mut r).unwrap();
assert_eq!(decoded, t);
}
#[test]
fn profile_addr_round_trip() {
let t = TargetAddress::Profile(alloc::vec![1, 2, 3]);
let mut w = BufferWriter::new(Endianness::Little);
t.encode(&mut w).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Little);
let decoded = TargetAddress::decode(&mut r).unwrap();
assert_eq!(decoded, t);
}
#[test]
fn reference_addr_round_trip() {
let t = TargetAddress::Reference {
selected_profile_index: 7,
ior: alloc::vec![0xff, 0x00, 0xaa],
};
let mut w = BufferWriter::new(Endianness::Big);
t.encode(&mut w).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Big);
let decoded = TargetAddress::decode(&mut r).unwrap();
assert_eq!(decoded, t);
}
#[test]
fn unknown_disposition_is_diagnostic() {
let mut w = BufferWriter::new(Endianness::Big);
w.write_u16(99).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Big);
let err = TargetAddress::decode(&mut r).unwrap_err();
assert!(matches!(err, GiopError::UnknownAddressingDisposition(99)));
}
}