extern crate alloc;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::rc::Rc;
use alloc::string::String;
use core::any::Any;
use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
use crate::runtime::ValueBase;
const VALUE_TAG_BASE: u32 = 0x7fff_ff00;
const VT_FLAG_CODEBASE: u32 = 0x0000_0001;
const VT_FLAG_CHUNKED: u32 = 0x0000_0008;
const VT_REPO_MASK: u32 = 0x0000_0006;
const VT_REPO_NONE: u32 = 0x0000_0000;
const VT_REPO_SINGLE: u32 = 0x0000_0002;
const VT_REPO_LIST: u32 = 0x0000_0006;
const VALUE_NULL: u32 = 0x0000_0000;
const VALUE_INDIRECTION: u32 = 0xffff_ffff;
fn enc_err(message: &'static str) -> EncodeError {
EncodeError::ValueOutOfRange { message }
}
fn dec_err(kind: &'static str) -> DecodeError {
DecodeError::InvalidEnum { kind, value: 0 }
}
pub trait ValueMarshal: ValueBase {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError>;
}
#[derive(Default)]
pub struct ValueWriter {
seen: BTreeMap<usize, usize>,
}
impl ValueWriter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn write(
&mut self,
w: &mut BufferWriter,
value: Option<&Rc<dyn ValueMarshal>>,
) -> Result<(), EncodeError> {
let Some(v) = value else {
w.write_u32(VALUE_NULL)?;
return Ok(());
};
w.align(4);
let tag_pos = w.position();
let id = Rc::as_ptr(v).cast::<()>() as usize;
if let Some(&prior) = self.seen.get(&id) {
w.write_u32(VALUE_INDIRECTION)?;
let offset = i32::try_from(prior as i64 - w.position() as i64)
.map_err(|_| enc_err("value indirection offset overflow"))?;
w.write_u32(offset as u32)?;
return Ok(());
}
self.seen.insert(id, tag_pos);
w.write_u32(VALUE_TAG_BASE | VT_REPO_SINGLE)?; w.write_string(v.repository_id())?;
v.marshal_state(w)
}
pub fn write_with_codebase(
&mut self,
w: &mut BufferWriter,
value: Option<&Rc<dyn ValueMarshal>>,
codebase: &str,
) -> Result<(), EncodeError> {
let Some(v) = value else {
w.write_u32(VALUE_NULL)?;
return Ok(());
};
w.align(4);
let tag_pos = w.position();
let id = Rc::as_ptr(v).cast::<()>() as usize;
if let Some(&prior) = self.seen.get(&id) {
w.write_u32(VALUE_INDIRECTION)?;
let offset = i32::try_from(prior as i64 - w.position() as i64)
.map_err(|_| enc_err("value indirection offset overflow"))?;
w.write_u32(offset as u32)?;
return Ok(());
}
self.seen.insert(id, tag_pos);
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CODEBASE | VT_REPO_SINGLE)?; w.write_string(codebase)?;
w.write_string(v.repository_id())?;
v.marshal_state(w)
}
pub fn write_chunked(
&mut self,
w: &mut BufferWriter,
value: Option<&Rc<dyn ValueMarshal>>,
base_ids: &[&str],
) -> Result<(), EncodeError> {
let Some(v) = value else {
w.write_u32(VALUE_NULL)?;
return Ok(());
};
w.align(4);
let tag_pos = w.position();
let id = Rc::as_ptr(v).cast::<()>() as usize;
if let Some(&prior) = self.seen.get(&id) {
w.write_u32(VALUE_INDIRECTION)?;
let offset = i32::try_from(prior as i64 - w.position() as i64)
.map_err(|_| enc_err("value indirection offset overflow"))?;
w.write_u32(offset as u32)?;
return Ok(());
}
self.seen.insert(id, tag_pos);
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)?;
let count = u32::try_from(1 + base_ids.len())
.map_err(|_| enc_err("value RepositoryId list too long"))?;
w.write_u32(count)?;
w.write_string(v.repository_id())?;
for b in base_ids {
w.write_string(b)?;
}
w.align(4);
let state_offset = w.position() + 4; let mut tmp = BufferWriter::new(w.endianness()).with_align_origin(state_offset);
v.marshal_state(&mut tmp)?;
let state = tmp.into_bytes();
let size = i32::try_from(state.len()).map_err(|_| enc_err("chunk size overflow"))?;
w.write_u32(size as u32)?;
w.write_bytes(&state)?;
w.align(4);
w.write_u32((-1i32) as u32)
}
pub fn write_chunked_tree(
&mut self,
w: &mut BufferWriter,
node: &ChunkedNode<'_>,
) -> Result<(), EncodeError> {
self.write_chunked_node(w, node, 1)
}
fn write_chunked_node(
&mut self,
w: &mut BufferWriter,
node: &ChunkedNode<'_>,
level: u32,
) -> Result<(), EncodeError> {
w.align(4);
let tag_pos = w.position();
let id = Rc::as_ptr(node.value).cast::<()>() as usize;
if let Some(&prior) = self.seen.get(&id) {
w.write_u32(VALUE_INDIRECTION)?;
let offset = i32::try_from(prior as i64 - w.position() as i64)
.map_err(|_| enc_err("value indirection offset overflow"))?;
w.write_u32(offset as u32)?;
return Ok(());
}
self.seen.insert(id, tag_pos);
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)?;
let count = u32::try_from(1 + node.base_ids.len())
.map_err(|_| enc_err("value RepositoryId list too long"))?;
w.write_u32(count)?;
w.write_string(node.value.repository_id())?;
for b in node.base_ids {
w.write_string(b)?;
}
w.align(4);
let state_offset = w.position() + 4;
let mut tmp = BufferWriter::new(w.endianness()).with_align_origin(state_offset);
node.value.marshal_state(&mut tmp)?;
let state = tmp.into_bytes();
let size = i32::try_from(state.len()).map_err(|_| enc_err("chunk size overflow"))?;
w.write_u32(size as u32)?;
w.write_bytes(&state)?;
for child in node.nested {
self.write_chunked_node(w, child, level + 1)?;
}
w.align(4);
w.write_u32((-(level as i32)) as u32)
}
}
pub struct ChunkedNode<'a> {
pub value: &'a Rc<dyn ValueMarshal>,
pub base_ids: &'a [&'a str],
pub nested: &'a [ChunkedNode<'a>],
}
pub type ValueCtor = Box<dyn Fn(&mut BufferReader<'_>) -> Result<Rc<dyn Any>, DecodeError>>;
pub type CodebaseResolver = Box<dyn Fn(&str, &str) -> Option<ValueCtor>>;
enum CtorRef<'a> {
Borrowed(&'a ValueCtor),
Owned(ValueCtor),
}
impl CtorRef<'_> {
fn call(&self, r: &mut BufferReader<'_>) -> Result<Rc<dyn Any>, DecodeError> {
match self {
CtorRef::Borrowed(c) => c(r),
CtorRef::Owned(c) => c(r),
}
}
}
#[derive(Default)]
pub struct ValueRegistry {
ctors: BTreeMap<String, ValueCtor>,
codebase_resolver: Option<CodebaseResolver>,
}
impl ValueRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, repo_id: impl Into<String>, ctor: ValueCtor) {
self.ctors.insert(repo_id.into(), ctor);
}
pub fn set_codebase_resolver(&mut self, resolver: CodebaseResolver) {
self.codebase_resolver = Some(resolver);
}
fn ctor_for(&self, repo_id: &str, codebase: Option<&str>) -> Option<CtorRef<'_>> {
if let Some(c) = self.ctors.get(repo_id) {
return Some(CtorRef::Borrowed(c));
}
if let (Some(cb), Some(res)) = (codebase, self.codebase_resolver.as_ref()) {
if let Some(c) = res(cb, repo_id) {
return Some(CtorRef::Owned(c));
}
}
None
}
}
#[derive(Default)]
pub struct ValueReader {
cache: BTreeMap<usize, Rc<dyn Any>>,
}
impl ValueReader {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn read(
&mut self,
r: &mut BufferReader<'_>,
base: usize,
reg: &ValueRegistry,
) -> Result<Option<Rc<dyn Any>>, DecodeError> {
r.align(4)?;
let tag_pos = base + r.position();
let tag = r.read_u32()?;
if tag == VALUE_NULL {
return Ok(None);
}
if tag == VALUE_INDIRECTION {
let off_field = base + r.position();
let offset = r.read_u32()? as i32;
if offset >= 0 {
return Err(dec_err("value indirection: offset must be negative"));
}
let target = usize::try_from(off_field as i64 + i64::from(offset))
.map_err(|_| dec_err("value indirection: target before stream start"))?;
return self
.cache
.get(&target)
.cloned()
.map(Some)
.ok_or_else(|| dec_err("value indirection: unresolved target"));
}
if tag < VALUE_TAG_BASE {
return Err(dec_err("invalid value_tag"));
}
let chunked = tag & VT_FLAG_CHUNKED != 0;
let codebase: Option<String> = if tag & VT_FLAG_CODEBASE != 0 {
Some(r.read_string()?)
} else {
None
};
let ids: alloc::vec::Vec<String> = match tag & VT_REPO_MASK {
VT_REPO_SINGLE => alloc::vec![r.read_string()?],
VT_REPO_LIST => {
let n = r.read_u32()? as usize;
let mut ids = alloc::vec::Vec::with_capacity(n.min(16));
for _ in 0..n {
ids.push(r.read_string()?);
}
if ids.is_empty() {
return Err(dec_err("empty value RepositoryId list"));
}
ids
}
VT_REPO_NONE => return Err(dec_err("value without type info unsupported")),
_ => return Err(dec_err("invalid value_tag repo-id flags")),
};
let v = if chunked {
read_chunked_state(r, &ids, reg, codebase.as_deref())?
} else {
let ctor = reg
.ctor_for(&ids[0], codebase.as_deref())
.ok_or_else(|| dec_err("no ValueFactory for RepositoryId"))?;
ctor.call(r)?
};
self.cache.insert(tag_pos, Rc::clone(&v));
Ok(Some(v))
}
}
fn read_chunked_state(
r: &mut BufferReader<'_>,
ids: &[String],
reg: &ValueRegistry,
codebase: Option<&str>,
) -> Result<Rc<dyn Any>, DecodeError> {
let ctor = ids
.iter()
.find_map(|id| reg.ctor_for(id, codebase))
.ok_or_else(|| dec_err("no ValueFactory for any RepositoryId in chunked value"))?;
let mut value: Option<Rc<dyn Any>> = None;
loop {
r.align(4)?;
let marker = r.read_u32()? as i32;
if marker < 0 {
break; }
let marker_u = marker as u32;
if marker_u == VALUE_INDIRECTION {
let _offset = r.read_u32()?;
continue;
}
if marker_u >= VALUE_TAG_BASE {
let closed_level = skip_value_from_tag(r, 2, marker_u)?;
if closed_level <= 1 {
break; }
continue;
}
let chunk_size = marker as usize;
let pos_before = r.position();
if value.is_none() {
let v = ctor.call(r)?;
let consumed = r.position() - pos_before;
if consumed > chunk_size {
return Err(dec_err("chunked value: ctor over-read the chunk"));
}
if consumed < chunk_size {
let _ = r.read_bytes(chunk_size - consumed)?;
}
value = Some(v);
} else {
let _ = r.read_bytes(chunk_size)?;
}
}
value.ok_or_else(|| dec_err("chunked value produced no state"))
}
fn skip_value_from_tag(r: &mut BufferReader<'_>, depth: u32, tag: u32) -> Result<u32, DecodeError> {
if tag & VT_FLAG_CODEBASE != 0 {
let _ = r.read_string()?;
}
match tag & VT_REPO_MASK {
VT_REPO_SINGLE => {
let _ = r.read_string()?;
}
VT_REPO_LIST => {
let n = r.read_u32()? as usize;
for _ in 0..n {
let _ = r.read_string()?;
}
}
_ => return Err(dec_err("nested value without type info")),
}
if tag & VT_FLAG_CHUNKED == 0 {
return Err(dec_err("non-chunked nested value cannot be skipped"));
}
skip_chunked_body(r, depth)
}
fn skip_chunked_body(r: &mut BufferReader<'_>, depth: u32) -> Result<u32, DecodeError> {
loop {
r.align(4)?;
let marker = r.read_u32()? as i32;
if marker < 0 {
return Ok((-marker) as u32);
}
let marker_u = marker as u32;
if marker_u == VALUE_INDIRECTION {
let _offset = r.read_u32()?;
continue;
}
if marker_u >= VALUE_TAG_BASE {
let closed_level = skip_value_from_tag(r, depth + 1, marker_u)?;
if closed_level <= depth {
return Ok(closed_level);
}
continue;
}
let _ = r.read_bytes(marker as usize)?;
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use zerodds_cdr::{CdrDecode, CdrEncode, Endianness};
#[derive(Debug, PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}
impl ValueBase for Point {
fn repository_id(&self) -> &str {
"IDL:Geo/Point:1.0"
}
}
impl ValueMarshal for Point {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
self.x.encode(w)?;
self.y.encode(w)
}
}
fn point_registry() -> ValueRegistry {
let mut reg = ValueRegistry::new();
reg.register(
"IDL:Geo/Point:1.0",
Box::new(|r: &mut BufferReader<'_>| {
let x = i32::decode(r)?;
let y = i32::decode(r)?;
Ok(Rc::new(Point { x, y }) as Rc<dyn Any>)
}),
);
reg
}
#[derive(Debug, PartialEq, Eq)]
struct JPoint {
x: i32,
y: i32,
}
impl ValueBase for JPoint {
fn repository_id(&self) -> &str {
"IDL:Point:1.0"
}
}
impl ValueMarshal for JPoint {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
self.x.encode(w)?;
self.y.encode(w)
}
}
#[test]
fn jacorb_capture_byte_identical() {
let mut w = BufferWriter::new(Endianness::Big);
let mut vw = ValueWriter::new();
let p: Rc<dyn ValueMarshal> = Rc::new(JPoint { x: 42, y: -7 });
vw.write(&mut w, Some(&p)).unwrap();
let bytes = w.into_bytes();
let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
hex, "7fffff020000000e49444c3a506f696e743a312e300000000000002afffffff9",
"ZeroDDS-Valuetype-Wire weicht von JacORB-Capture ab"
);
}
#[derive(Debug, PartialEq, Eq)]
struct Base {
id: i32,
}
impl ValueBase for Base {
fn repository_id(&self) -> &str {
"IDL:Base:1.0"
}
}
impl ValueMarshal for Base {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
self.id.encode(w)
}
}
#[derive(Debug, PartialEq, Eq)]
struct Derived {
id: i32,
extra: alloc::string::String,
}
impl ValueBase for Derived {
fn repository_id(&self) -> &str {
"IDL:Derived:1.0"
}
}
impl ValueMarshal for Derived {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
self.id.encode(w)?; self.extra.encode(w)
}
}
fn base_ctor(reg: &mut ValueRegistry) {
reg.register(
"IDL:Base:1.0",
Box::new(|r: &mut BufferReader<'_>| {
Ok(Rc::new(Base {
id: i32::decode(r)?,
}) as Rc<dyn Any>)
}),
);
}
fn derived_ctor(reg: &mut ValueRegistry) {
reg.register(
"IDL:Derived:1.0",
Box::new(|r: &mut BufferReader<'_>| {
let id = i32::decode(r)?;
let extra = alloc::string::String::decode(r)?;
Ok(Rc::new(Derived { id, extra }) as Rc<dyn Any>)
}),
);
}
const JACORB_CHUNKED: &str = "7fffff0e000000020000001049444c3a446572697665643a312e30000000000d49444c3a426173653a312e30000000000000000b0000002a0000000368690000ffffffff";
#[test]
fn chunked_encode_byte_identical_to_jacorb() {
let mut w = BufferWriter::new(Endianness::Big);
let mut vw = ValueWriter::new();
let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
id: 42,
extra: "hi".into(),
});
vw.write_chunked(&mut w, Some(&d), &["IDL:Base:1.0"])
.unwrap();
let hex: alloc::string::String = w
.into_bytes()
.iter()
.map(|b| alloc::format!("{b:02x}"))
.collect();
assert_eq!(
hex, JACORB_CHUNKED,
"chunked-Wire weicht von JacORB-Capture ab"
);
}
fn jacorb_chunked_bytes() -> alloc::vec::Vec<u8> {
(0..JACORB_CHUNKED.len() / 2)
.map(|i| u8::from_str_radix(&JACORB_CHUNKED[2 * i..2 * i + 2], 16).unwrap())
.collect()
}
#[test]
fn chunked_decode_full_when_derived_known() {
let bytes = jacorb_chunked_bytes();
let mut reg = ValueRegistry::new();
derived_ctor(&mut reg);
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, Endianness::Big);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(
*v.downcast_ref::<Derived>().unwrap(),
Derived {
id: 42,
extra: "hi".into()
}
);
}
#[test]
fn chunked_decode_truncates_to_base() {
let bytes = jacorb_chunked_bytes();
let mut reg = ValueRegistry::new();
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, Endianness::Big);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
}
#[test]
fn chunked_roundtrip_zerodds_to_zerodds() {
for e in [Endianness::Big, Endianness::Little] {
let mut w = BufferWriter::new(e);
let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
id: 7,
extra: "xyz".into(),
});
ValueWriter::new()
.write_chunked(&mut w, Some(&d), &["IDL:Base:1.0"])
.unwrap();
let bytes = w.into_bytes();
let mut reg = ValueRegistry::new();
derived_ctor(&mut reg);
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, e);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(
*v.downcast_ref::<Derived>().unwrap(),
Derived {
id: 7,
extra: "xyz".into()
}
);
}
}
#[test]
fn single_value_and_null_roundtrip() {
for e in [Endianness::Big, Endianness::Little] {
let mut w = BufferWriter::new(e);
let mut vw = ValueWriter::new();
let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 3, y: 7 });
vw.write(&mut w, Some(&p)).unwrap();
vw.write(&mut w, None).unwrap(); let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, e);
let mut vr = ValueReader::new();
let reg = point_registry();
let v = vr.read(&mut r, 0, ®).unwrap().expect("non-null");
let pt = v.downcast_ref::<Point>().expect("Point");
assert_eq!(*pt, Point { x: 3, y: 7 });
assert!(vr.read(&mut r, 0, ®).unwrap().is_none()); }
}
#[test]
fn value_sharing_indirection() {
for e in [Endianness::Big, Endianness::Little] {
let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 2 });
let mut w = BufferWriter::new(e);
let mut vw = ValueWriter::new();
vw.write(&mut w, Some(&p)).unwrap();
vw.write(&mut w, Some(&p)).unwrap(); let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, e);
let mut vr = ValueReader::new();
let reg = point_registry();
let a = vr.read(&mut r, 0, ®).unwrap().unwrap();
let b = vr.read(&mut r, 0, ®).unwrap().unwrap();
assert!(
Rc::ptr_eq(&a, &b),
"value sharing: both refs = one instance"
);
assert_eq!(*a.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 2 });
}
}
#[test]
fn distinct_values_are_not_shared() {
let p1: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 1 });
let p2: Rc<dyn ValueMarshal> = Rc::new(Point { x: 2, y: 2 });
let mut w = BufferWriter::new(Endianness::Big);
let mut vw = ValueWriter::new();
vw.write(&mut w, Some(&p1)).unwrap();
vw.write(&mut w, Some(&p2)).unwrap();
let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Big);
let mut vr = ValueReader::new();
let reg = point_registry();
let a = vr.read(&mut r, 0, ®).unwrap().unwrap();
let b = vr.read(&mut r, 0, ®).unwrap().unwrap();
assert!(!Rc::ptr_eq(&a, &b));
assert_eq!(*a.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 1 });
assert_eq!(*b.downcast_ref::<Point>().unwrap(), Point { x: 2, y: 2 });
}
#[test]
fn value_tag_is_single_repo_id_on_wire() {
let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 0, y: 0 });
let mut w = BufferWriter::new(Endianness::Big);
ValueWriter::new().write(&mut w, Some(&p)).unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[0x7f, 0xff, 0xff, 0x02]);
}
#[test]
fn forward_indirection_rejected() {
let mut w = BufferWriter::new(Endianness::Big);
w.write_u32(VALUE_INDIRECTION).unwrap();
w.write_u32(4).unwrap(); let bytes = w.into_bytes();
let mut r = BufferReader::new(&bytes, Endianness::Big);
assert!(
ValueReader::new()
.read(&mut r, 0, &ValueRegistry::new())
.is_err()
);
}
#[test]
fn codebase_value_tag_and_roundtrip() {
let mut w = BufferWriter::new(Endianness::Big);
let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 5, y: 9 });
ValueWriter::new()
.write_with_codebase(&mut w, Some(&p), "file:///stubs.jar")
.unwrap();
let bytes = w.into_bytes();
assert_eq!(&bytes[0..4], &[0x7f, 0xff, 0xff, 0x03]);
let mut r = BufferReader::new(&bytes, Endianness::Big);
let reg = point_registry();
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Point>().unwrap(), Point { x: 5, y: 9 });
}
#[test]
fn codebase_resolver_supplies_missing_factory() {
let mut w = BufferWriter::new(Endianness::Big);
let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 2 });
ValueWriter::new()
.write_with_codebase(&mut w, Some(&p), "ior://factory-host/Point")
.unwrap();
let bytes = w.into_bytes();
let mut reg = ValueRegistry::new();
reg.set_codebase_resolver(Box::new(|codebase: &str, repo_id: &str| {
if codebase.contains("factory-host") && repo_id == "IDL:Geo/Point:1.0" {
Some(Box::new(|r: &mut BufferReader<'_>| {
let x = i32::decode(r)?;
let y = i32::decode(r)?;
Ok(Rc::new(Point { x, y }) as Rc<dyn Any>)
}) as ValueCtor)
} else {
None
}
}));
let mut r = BufferReader::new(&bytes, Endianness::Big);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 2 });
let mut r2 = BufferReader::new(&bytes, Endianness::Big);
assert!(
ValueReader::new()
.read(&mut r2, 0, &ValueRegistry::new())
.is_err()
);
}
fn chunked_with_nested_tail(e: Endianness) -> alloc::vec::Vec<u8> {
let mut w = BufferWriter::new(e);
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
.unwrap(); w.write_u32(2).unwrap();
w.write_string("IDL:Derived:1.0").unwrap();
w.write_string("IDL:Base:1.0").unwrap();
w.align(4);
w.write_u32(4).unwrap();
w.write_u32(42).unwrap();
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
.unwrap();
w.write_u32(1).unwrap();
w.write_string("IDL:Inner:1.0").unwrap();
w.align(4);
w.write_u32(4).unwrap(); w.write_u32(0x0bad_cafe).unwrap(); w.align(4);
w.write_u32((-2i32) as u32).unwrap(); w.align(4);
w.write_u32((-1i32) as u32).unwrap();
w.into_bytes()
}
#[test]
fn nested_chunked_value_in_tail_is_consumed_on_truncation() {
for e in [Endianness::Big, Endianness::Little] {
let bytes = chunked_with_nested_tail(e);
let mut reg = ValueRegistry::new();
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, e);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
assert_eq!(r.position(), bytes.len());
}
}
#[test]
fn shared_end_tag_closes_nested_and_outer() {
let e = Endianness::Big;
let mut w = BufferWriter::new(e);
w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
.unwrap();
w.write_u32(2).unwrap();
w.write_string("IDL:Derived:1.0").unwrap();
w.write_string("IDL:Base:1.0").unwrap();
w.align(4);
w.write_u32(4).unwrap();
w.write_u32(42).unwrap(); w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
.unwrap();
w.write_u32(1).unwrap();
w.write_string("IDL:Inner:1.0").unwrap();
w.align(4);
w.write_u32(4).unwrap();
w.write_u32(0x0bad_cafe).unwrap();
w.align(4);
w.write_u32((-1i32) as u32).unwrap();
let bytes = w.into_bytes();
let mut reg = ValueRegistry::new();
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, e);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
assert_eq!(r.position(), bytes.len());
}
#[derive(Debug)]
struct OuterBaseState {
id: i32,
}
impl ValueBase for OuterBaseState {
fn repository_id(&self) -> &str {
"IDL:Derived:1.0"
}
}
impl ValueMarshal for OuterBaseState {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
self.id.encode(w)
}
}
#[derive(Debug)]
struct InnerVal {
word: u32,
}
impl ValueBase for InnerVal {
fn repository_id(&self) -> &str {
"IDL:Inner:1.0"
}
}
impl ValueMarshal for InnerVal {
fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
w.write_u32(self.word)
}
}
#[test]
fn chunked_tree_leaf_is_byte_identical_to_write_chunked() {
for e in [Endianness::Big, Endianness::Little] {
let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
id: 42,
extra: "hi".into(),
});
let mut w1 = BufferWriter::new(e);
ValueWriter::new()
.write_chunked(&mut w1, Some(&d), &["IDL:Base:1.0"])
.unwrap();
let mut w2 = BufferWriter::new(e);
let node = ChunkedNode {
value: &d,
base_ids: &["IDL:Base:1.0"],
nested: &[],
};
ValueWriter::new()
.write_chunked_tree(&mut w2, &node)
.unwrap();
assert_eq!(
w1.into_bytes(),
w2.into_bytes(),
"leaf tree != write_chunked"
);
}
}
#[test]
fn chunked_tree_with_nested_matches_handbuilt_wire() {
for e in [Endianness::Big, Endianness::Little] {
let inner: Rc<dyn ValueMarshal> = Rc::new(InnerVal { word: 0x0bad_cafe });
let outer: Rc<dyn ValueMarshal> = Rc::new(OuterBaseState { id: 42 });
let inner_node = ChunkedNode {
value: &inner,
base_ids: &[],
nested: &[],
};
let outer_node = ChunkedNode {
value: &outer,
base_ids: &["IDL:Base:1.0"],
nested: core::slice::from_ref(&inner_node),
};
let mut w = BufferWriter::new(e);
ValueWriter::new()
.write_chunked_tree(&mut w, &outer_node)
.unwrap();
assert_eq!(
w.into_bytes(),
chunked_with_nested_tail(e),
"multi-chunk encode != hand-built nested-tail wire"
);
}
}
#[test]
fn chunked_tree_roundtrips_with_base_truncation() {
for e in [Endianness::Big, Endianness::Little] {
let inner: Rc<dyn ValueMarshal> = Rc::new(InnerVal { word: 0x0bad_cafe });
let outer: Rc<dyn ValueMarshal> = Rc::new(OuterBaseState { id: 99 });
let inner_node = ChunkedNode {
value: &inner,
base_ids: &[],
nested: &[],
};
let outer_node = ChunkedNode {
value: &outer,
base_ids: &["IDL:Base:1.0"],
nested: core::slice::from_ref(&inner_node),
};
let mut w = BufferWriter::new(e);
ValueWriter::new()
.write_chunked_tree(&mut w, &outer_node)
.unwrap();
let bytes = w.into_bytes();
let mut reg = ValueRegistry::new();
base_ctor(&mut reg);
let mut r = BufferReader::new(&bytes, e);
let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 99 });
assert_eq!(r.position(), bytes.len(), "nested tail not fully consumed");
}
}
}