use alloc::vec::Vec;
use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
use crate::error::{GiopError, GiopResult};
use crate::service_context::{ServiceContext, ServiceContextList, ServiceContextTag};
pub mod well_known {
pub const ISO_8859_1: u32 = 0x0001_0001;
pub const UTF_8: u32 = 0x0501_0001;
pub const UTF_16: u32 = 0x0001_0109;
pub const UCS_2: u32 = 0x0001_0100;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodeSetContext {
pub char_data: u32,
pub wchar_data: u32,
}
impl Default for CodeSetContext {
fn default() -> Self {
Self::default_pair()
}
}
impl CodeSetContext {
#[must_use]
pub const fn new(char_data: u32, wchar_data: u32) -> Self {
Self {
char_data,
wchar_data,
}
}
#[must_use]
pub const fn default_pair() -> Self {
Self {
char_data: well_known::UTF_8,
wchar_data: well_known::UTF_16,
}
}
pub fn encode_encapsulation(&self, endianness: Endianness) -> GiopResult<Vec<u8>> {
let mut w = BufferWriter::new(endianness);
w.write_u8(match endianness {
Endianness::Big => 0,
Endianness::Little => 1,
})?;
w.write_u32(self.char_data)?;
w.write_u32(self.wchar_data)?;
Ok(w.into_bytes())
}
pub fn decode_encapsulation(encap: &[u8]) -> GiopResult<Self> {
if encap.is_empty() {
return Err(GiopError::Malformed(
"empty CodeSetContext encapsulation".into(),
));
}
let endianness = match encap[0] {
0 => Endianness::Big,
1 => Endianness::Little,
other => {
return Err(GiopError::Malformed(alloc::format!(
"invalid CodeSetContext byte-order octet: {other}"
)));
}
};
let mut r = BufferReader::new(encap, endianness);
let _bo = r.read_u8()?;
let char_data = r.read_u32()?;
let wchar_data = r.read_u32()?;
Ok(Self {
char_data,
wchar_data,
})
}
pub fn to_service_context(&self, endianness: Endianness) -> GiopResult<ServiceContext> {
let data = self.encode_encapsulation(endianness)?;
Ok(ServiceContext::new(
ServiceContextTag::CodeSets.as_u32(),
data,
))
}
pub fn from_service_context_list(list: &ServiceContextList) -> GiopResult<Option<Self>> {
let tag = ServiceContextTag::CodeSets.as_u32();
match list.0.iter().find(|c| c.context_id == tag) {
Some(ctx) => Ok(Some(Self::decode_encapsulation(&ctx.context_data)?)),
None => Ok(None),
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn well_known_ids_match_osf_registry() {
assert_eq!(well_known::ISO_8859_1, 0x0001_0001);
assert_eq!(well_known::UTF_8, 0x0501_0001);
assert_eq!(well_known::UTF_16, 0x0001_0109);
assert_eq!(well_known::UCS_2, 0x0001_0100);
}
#[test]
fn encapsulation_wire_layout_be() {
let ctx = CodeSetContext::new(well_known::UTF_8, well_known::UTF_16);
let encap = ctx.encode_encapsulation(Endianness::Big).unwrap();
assert_eq!(encap.len(), 12);
assert_eq!(encap[0], 0); assert_eq!(&encap[4..8], &0x0501_0001u32.to_be_bytes());
assert_eq!(&encap[8..12], &0x0001_0109u32.to_be_bytes());
}
#[test]
fn encapsulation_roundtrip_both_orders() {
for e in [Endianness::Big, Endianness::Little] {
let ctx = CodeSetContext::new(well_known::ISO_8859_1, well_known::UCS_2);
let encap = ctx.encode_encapsulation(e).unwrap();
assert_eq!(CodeSetContext::decode_encapsulation(&encap).unwrap(), ctx);
}
}
#[test]
fn service_context_roundtrip_via_list() {
let ctx = CodeSetContext::default_pair();
let sc = ctx.to_service_context(Endianness::Little).unwrap();
assert_eq!(sc.context_id, 1);
let list = ServiceContextList(alloc::vec![sc]);
let found = CodeSetContext::from_service_context_list(&list)
.unwrap()
.expect("CodeSets context present");
assert_eq!(found, ctx);
}
#[test]
fn absent_context_is_none() {
let list = ServiceContextList(alloc::vec![ServiceContext::new(42, alloc::vec![1, 2, 3])]);
assert_eq!(
CodeSetContext::from_service_context_list(&list).unwrap(),
None
);
}
#[test]
fn invalid_byte_order_octet_rejected() {
let bad = alloc::vec![0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert!(CodeSetContext::decode_encapsulation(&bad).is_err());
}
}