1extern crate alloc;
29use alloc::boxed::Box;
30use alloc::collections::BTreeMap;
31use alloc::rc::Rc;
32use alloc::string::String;
33use core::any::Any;
34
35use zerodds_cdr::{BufferReader, BufferWriter, DecodeError, EncodeError};
36
37use crate::runtime::ValueBase;
38
39const VALUE_TAG_BASE: u32 = 0x7fff_ff00;
41const VT_FLAG_CODEBASE: u32 = 0x0000_0001;
43const VT_FLAG_CHUNKED: u32 = 0x0000_0008;
45const VT_REPO_MASK: u32 = 0x0000_0006;
47const VT_REPO_NONE: u32 = 0x0000_0000;
48const VT_REPO_SINGLE: u32 = 0x0000_0002;
49const VT_REPO_LIST: u32 = 0x0000_0006;
50const VALUE_NULL: u32 = 0x0000_0000;
52const VALUE_INDIRECTION: u32 = 0xffff_ffff;
54
55fn enc_err(message: &'static str) -> EncodeError {
56 EncodeError::ValueOutOfRange { message }
57}
58fn dec_err(kind: &'static str) -> DecodeError {
59 DecodeError::InvalidEnum { kind, value: 0 }
60}
61
62pub trait ValueMarshal: ValueBase {
65 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError>;
70}
71
72#[derive(Default)]
75pub struct ValueWriter {
76 seen: BTreeMap<usize, usize>,
78}
79
80impl ValueWriter {
81 #[must_use]
83 pub fn new() -> Self {
84 Self::default()
85 }
86
87 pub fn write(
93 &mut self,
94 w: &mut BufferWriter,
95 value: Option<&Rc<dyn ValueMarshal>>,
96 ) -> Result<(), EncodeError> {
97 let Some(v) = value else {
98 w.write_u32(VALUE_NULL)?;
99 return Ok(());
100 };
101 w.align(4);
102 let tag_pos = w.position();
103 let id = Rc::as_ptr(v).cast::<()>() as usize;
104 if let Some(&prior) = self.seen.get(&id) {
105 w.write_u32(VALUE_INDIRECTION)?;
107 let offset = i32::try_from(prior as i64 - w.position() as i64)
109 .map_err(|_| enc_err("value indirection offset overflow"))?;
110 w.write_u32(offset as u32)?;
111 return Ok(());
112 }
113 self.seen.insert(id, tag_pos);
114 w.write_u32(VALUE_TAG_BASE | VT_REPO_SINGLE)?; w.write_string(v.repository_id())?;
116 v.marshal_state(w)
117 }
118
119 pub fn write_with_codebase(
127 &mut self,
128 w: &mut BufferWriter,
129 value: Option<&Rc<dyn ValueMarshal>>,
130 codebase: &str,
131 ) -> Result<(), EncodeError> {
132 let Some(v) = value else {
133 w.write_u32(VALUE_NULL)?;
134 return Ok(());
135 };
136 w.align(4);
137 let tag_pos = w.position();
138 let id = Rc::as_ptr(v).cast::<()>() as usize;
139 if let Some(&prior) = self.seen.get(&id) {
140 w.write_u32(VALUE_INDIRECTION)?;
141 let offset = i32::try_from(prior as i64 - w.position() as i64)
142 .map_err(|_| enc_err("value indirection offset overflow"))?;
143 w.write_u32(offset as u32)?;
144 return Ok(());
145 }
146 self.seen.insert(id, tag_pos);
147 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CODEBASE | VT_REPO_SINGLE)?; w.write_string(codebase)?;
149 w.write_string(v.repository_id())?;
150 v.marshal_state(w)
151 }
152
153 pub fn write_chunked(
162 &mut self,
163 w: &mut BufferWriter,
164 value: Option<&Rc<dyn ValueMarshal>>,
165 base_ids: &[&str],
166 ) -> Result<(), EncodeError> {
167 let Some(v) = value else {
168 w.write_u32(VALUE_NULL)?;
169 return Ok(());
170 };
171 w.align(4);
172 let tag_pos = w.position();
173 let id = Rc::as_ptr(v).cast::<()>() as usize;
174 if let Some(&prior) = self.seen.get(&id) {
175 w.write_u32(VALUE_INDIRECTION)?;
176 let offset = i32::try_from(prior as i64 - w.position() as i64)
177 .map_err(|_| enc_err("value indirection offset overflow"))?;
178 w.write_u32(offset as u32)?;
179 return Ok(());
180 }
181 self.seen.insert(id, tag_pos);
182
183 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)?;
185 let count = u32::try_from(1 + base_ids.len())
186 .map_err(|_| enc_err("value RepositoryId list too long"))?;
187 w.write_u32(count)?;
188 w.write_string(v.repository_id())?;
189 for b in base_ids {
190 w.write_string(b)?;
191 }
192
193 w.align(4);
196 let state_offset = w.position() + 4; let mut tmp = BufferWriter::new(w.endianness()).with_align_origin(state_offset);
198 v.marshal_state(&mut tmp)?;
199 let state = tmp.into_bytes();
200 let size = i32::try_from(state.len()).map_err(|_| enc_err("chunk size overflow"))?;
201 w.write_u32(size as u32)?;
202 w.write_bytes(&state)?;
203
204 w.align(4);
206 w.write_u32((-1i32) as u32)
207 }
208
209 pub fn write_chunked_tree(
222 &mut self,
223 w: &mut BufferWriter,
224 node: &ChunkedNode<'_>,
225 ) -> Result<(), EncodeError> {
226 self.write_chunked_node(w, node, 1)
227 }
228
229 fn write_chunked_node(
234 &mut self,
235 w: &mut BufferWriter,
236 node: &ChunkedNode<'_>,
237 level: u32,
238 ) -> Result<(), EncodeError> {
239 w.align(4);
240 let tag_pos = w.position();
241 let id = Rc::as_ptr(node.value).cast::<()>() as usize;
242 if let Some(&prior) = self.seen.get(&id) {
243 w.write_u32(VALUE_INDIRECTION)?;
244 let offset = i32::try_from(prior as i64 - w.position() as i64)
245 .map_err(|_| enc_err("value indirection offset overflow"))?;
246 w.write_u32(offset as u32)?;
247 return Ok(());
248 }
249 self.seen.insert(id, tag_pos);
250
251 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)?;
253 let count = u32::try_from(1 + node.base_ids.len())
254 .map_err(|_| enc_err("value RepositoryId list too long"))?;
255 w.write_u32(count)?;
256 w.write_string(node.value.repository_id())?;
257 for b in node.base_ids {
258 w.write_string(b)?;
259 }
260
261 w.align(4);
263 let state_offset = w.position() + 4;
264 let mut tmp = BufferWriter::new(w.endianness()).with_align_origin(state_offset);
265 node.value.marshal_state(&mut tmp)?;
266 let state = tmp.into_bytes();
267 let size = i32::try_from(state.len()).map_err(|_| enc_err("chunk size overflow"))?;
268 w.write_u32(size as u32)?;
269 w.write_bytes(&state)?;
270
271 for child in node.nested {
274 self.write_chunked_node(w, child, level + 1)?;
275 }
276
277 w.align(4);
279 w.write_u32((-(level as i32)) as u32)
280 }
281}
282
283pub struct ChunkedNode<'a> {
290 pub value: &'a Rc<dyn ValueMarshal>,
292 pub base_ids: &'a [&'a str],
294 pub nested: &'a [ChunkedNode<'a>],
296}
297
298pub type ValueCtor = Box<dyn Fn(&mut BufferReader<'_>) -> Result<Rc<dyn Any>, DecodeError>>;
301
302pub type CodebaseResolver = Box<dyn Fn(&str, &str) -> Option<ValueCtor>>;
308
309enum CtorRef<'a> {
312 Borrowed(&'a ValueCtor),
313 Owned(ValueCtor),
314}
315
316impl CtorRef<'_> {
317 fn call(&self, r: &mut BufferReader<'_>) -> Result<Rc<dyn Any>, DecodeError> {
318 match self {
319 CtorRef::Borrowed(c) => c(r),
320 CtorRef::Owned(c) => c(r),
321 }
322 }
323}
324
325#[derive(Default)]
329pub struct ValueRegistry {
330 ctors: BTreeMap<String, ValueCtor>,
331 codebase_resolver: Option<CodebaseResolver>,
332}
333
334impl ValueRegistry {
335 #[must_use]
337 pub fn new() -> Self {
338 Self::default()
339 }
340
341 pub fn register(&mut self, repo_id: impl Into<String>, ctor: ValueCtor) {
343 self.ctors.insert(repo_id.into(), ctor);
344 }
345
346 pub fn set_codebase_resolver(&mut self, resolver: CodebaseResolver) {
349 self.codebase_resolver = Some(resolver);
350 }
351
352 fn ctor_for(&self, repo_id: &str, codebase: Option<&str>) -> Option<CtorRef<'_>> {
355 if let Some(c) = self.ctors.get(repo_id) {
356 return Some(CtorRef::Borrowed(c));
357 }
358 if let (Some(cb), Some(res)) = (codebase, self.codebase_resolver.as_ref()) {
359 if let Some(c) = res(cb, repo_id) {
360 return Some(CtorRef::Owned(c));
361 }
362 }
363 None
364 }
365}
366
367#[derive(Default)]
370pub struct ValueReader {
371 cache: BTreeMap<usize, Rc<dyn Any>>,
373}
374
375impl ValueReader {
376 #[must_use]
378 pub fn new() -> Self {
379 Self::default()
380 }
381
382 pub fn read(
389 &mut self,
390 r: &mut BufferReader<'_>,
391 base: usize,
392 reg: &ValueRegistry,
393 ) -> Result<Option<Rc<dyn Any>>, DecodeError> {
394 r.align(4)?;
395 let tag_pos = base + r.position();
396 let tag = r.read_u32()?;
397
398 if tag == VALUE_NULL {
399 return Ok(None);
400 }
401 if tag == VALUE_INDIRECTION {
402 let off_field = base + r.position();
403 let offset = r.read_u32()? as i32;
404 if offset >= 0 {
405 return Err(dec_err("value indirection: offset must be negative"));
406 }
407 let target = usize::try_from(off_field as i64 + i64::from(offset))
408 .map_err(|_| dec_err("value indirection: target before stream start"))?;
409 return self
410 .cache
411 .get(&target)
412 .cloned()
413 .map(Some)
414 .ok_or_else(|| dec_err("value indirection: unresolved target"));
415 }
416 if tag < VALUE_TAG_BASE {
417 return Err(dec_err("invalid value_tag"));
418 }
419 let chunked = tag & VT_FLAG_CHUNKED != 0;
420 let codebase: Option<String> = if tag & VT_FLAG_CODEBASE != 0 {
422 Some(r.read_string()?)
423 } else {
424 None
425 };
426 let ids: alloc::vec::Vec<String> = match tag & VT_REPO_MASK {
429 VT_REPO_SINGLE => alloc::vec![r.read_string()?],
430 VT_REPO_LIST => {
431 let n = r.read_u32()? as usize;
432 let mut ids = alloc::vec::Vec::with_capacity(n.min(16));
433 for _ in 0..n {
434 ids.push(r.read_string()?);
435 }
436 if ids.is_empty() {
437 return Err(dec_err("empty value RepositoryId list"));
438 }
439 ids
440 }
441 VT_REPO_NONE => return Err(dec_err("value without type info unsupported")),
442 _ => return Err(dec_err("invalid value_tag repo-id flags")),
443 };
444
445 let v = if chunked {
446 read_chunked_state(r, &ids, reg, codebase.as_deref())?
447 } else {
448 let ctor = reg
452 .ctor_for(&ids[0], codebase.as_deref())
453 .ok_or_else(|| dec_err("no ValueFactory for RepositoryId"))?;
454 ctor.call(r)?
455 };
456 self.cache.insert(tag_pos, Rc::clone(&v));
457 Ok(Some(v))
458 }
459}
460
461fn read_chunked_state(
470 r: &mut BufferReader<'_>,
471 ids: &[String],
472 reg: &ValueRegistry,
473 codebase: Option<&str>,
474) -> Result<Rc<dyn Any>, DecodeError> {
475 let ctor = ids
476 .iter()
477 .find_map(|id| reg.ctor_for(id, codebase))
478 .ok_or_else(|| dec_err("no ValueFactory for any RepositoryId in chunked value"))?;
479
480 let mut value: Option<Rc<dyn Any>> = None;
481 loop {
482 r.align(4)?;
483 let marker = r.read_u32()? as i32;
484 if marker < 0 {
485 break; }
487 let marker_u = marker as u32;
488 if marker_u == VALUE_INDIRECTION {
489 let _offset = r.read_u32()?;
491 continue;
492 }
493 if marker_u >= VALUE_TAG_BASE {
494 let closed_level = skip_value_from_tag(r, 2, marker_u)?;
497 if closed_level <= 1 {
498 break; }
500 continue;
501 }
502 let chunk_size = marker as usize;
503 let pos_before = r.position();
504 if value.is_none() {
505 let v = ctor.call(r)?;
507 let consumed = r.position() - pos_before;
508 if consumed > chunk_size {
509 return Err(dec_err("chunked value: ctor over-read the chunk"));
510 }
511 if consumed < chunk_size {
512 let _ = r.read_bytes(chunk_size - consumed)?;
514 }
515 value = Some(v);
516 } else {
517 let _ = r.read_bytes(chunk_size)?;
519 }
520 }
521 value.ok_or_else(|| dec_err("chunked value produced no state"))
522}
523
524fn skip_value_from_tag(r: &mut BufferReader<'_>, depth: u32, tag: u32) -> Result<u32, DecodeError> {
534 if tag & VT_FLAG_CODEBASE != 0 {
535 let _ = r.read_string()?;
536 }
537 match tag & VT_REPO_MASK {
538 VT_REPO_SINGLE => {
539 let _ = r.read_string()?;
540 }
541 VT_REPO_LIST => {
542 let n = r.read_u32()? as usize;
543 for _ in 0..n {
544 let _ = r.read_string()?;
545 }
546 }
547 _ => return Err(dec_err("nested value without type info")),
548 }
549 if tag & VT_FLAG_CHUNKED == 0 {
550 return Err(dec_err("non-chunked nested value cannot be skipped"));
553 }
554 skip_chunked_body(r, depth)
555}
556
557fn skip_chunked_body(r: &mut BufferReader<'_>, depth: u32) -> Result<u32, DecodeError> {
563 loop {
564 r.align(4)?;
565 let marker = r.read_u32()? as i32;
566 if marker < 0 {
567 return Ok((-marker) as u32);
568 }
569 let marker_u = marker as u32;
570 if marker_u == VALUE_INDIRECTION {
571 let _offset = r.read_u32()?;
572 continue;
573 }
574 if marker_u >= VALUE_TAG_BASE {
575 let closed_level = skip_value_from_tag(r, depth + 1, marker_u)?;
576 if closed_level <= depth {
577 return Ok(closed_level);
579 }
580 continue;
581 }
582 let _ = r.read_bytes(marker as usize)?;
583 }
584}
585
586#[cfg(test)]
587#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
588mod tests {
589 use super::*;
590 use zerodds_cdr::{CdrDecode, CdrEncode, Endianness};
591
592 #[derive(Debug, PartialEq, Eq)]
594 struct Point {
595 x: i32,
596 y: i32,
597 }
598 impl ValueBase for Point {
599 fn repository_id(&self) -> &str {
600 "IDL:Geo/Point:1.0"
601 }
602 }
603 impl ValueMarshal for Point {
604 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
605 self.x.encode(w)?;
606 self.y.encode(w)
607 }
608 }
609 fn point_registry() -> ValueRegistry {
610 let mut reg = ValueRegistry::new();
611 reg.register(
612 "IDL:Geo/Point:1.0",
613 Box::new(|r: &mut BufferReader<'_>| {
614 let x = i32::decode(r)?;
615 let y = i32::decode(r)?;
616 Ok(Rc::new(Point { x, y }) as Rc<dyn Any>)
617 }),
618 );
619 reg
620 }
621
622 #[derive(Debug, PartialEq, Eq)]
624 struct JPoint {
625 x: i32,
626 y: i32,
627 }
628 impl ValueBase for JPoint {
629 fn repository_id(&self) -> &str {
630 "IDL:Point:1.0"
631 }
632 }
633 impl ValueMarshal for JPoint {
634 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
635 self.x.encode(w)?;
636 self.y.encode(w)
637 }
638 }
639
640 #[test]
646 fn jacorb_capture_byte_identical() {
647 let mut w = BufferWriter::new(Endianness::Big);
648 let mut vw = ValueWriter::new();
649 let p: Rc<dyn ValueMarshal> = Rc::new(JPoint { x: 42, y: -7 });
650 vw.write(&mut w, Some(&p)).unwrap();
651 let bytes = w.into_bytes();
652 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
653 assert_eq!(
654 hex, "7fffff020000000e49444c3a506f696e743a312e300000000000002afffffff9",
655 "ZeroDDS-Valuetype-Wire weicht von JacORB-Capture ab"
656 );
657 }
658
659 #[derive(Debug, PartialEq, Eq)]
662 struct Base {
663 id: i32,
664 }
665 impl ValueBase for Base {
666 fn repository_id(&self) -> &str {
667 "IDL:Base:1.0"
668 }
669 }
670 impl ValueMarshal for Base {
671 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
672 self.id.encode(w)
673 }
674 }
675 #[derive(Debug, PartialEq, Eq)]
676 struct Derived {
677 id: i32,
678 extra: alloc::string::String,
679 }
680 impl ValueBase for Derived {
681 fn repository_id(&self) -> &str {
682 "IDL:Derived:1.0"
683 }
684 }
685 impl ValueMarshal for Derived {
686 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
687 self.id.encode(w)?; self.extra.encode(w)
689 }
690 }
691 fn base_ctor(reg: &mut ValueRegistry) {
692 reg.register(
693 "IDL:Base:1.0",
694 Box::new(|r: &mut BufferReader<'_>| {
695 Ok(Rc::new(Base {
696 id: i32::decode(r)?,
697 }) as Rc<dyn Any>)
698 }),
699 );
700 }
701 fn derived_ctor(reg: &mut ValueRegistry) {
702 reg.register(
703 "IDL:Derived:1.0",
704 Box::new(|r: &mut BufferReader<'_>| {
705 let id = i32::decode(r)?;
706 let extra = alloc::string::String::decode(r)?;
707 Ok(Rc::new(Derived { id, extra }) as Rc<dyn Any>)
708 }),
709 );
710 }
711
712 const JACORB_CHUNKED: &str = "7fffff0e000000020000001049444c3a446572697665643a312e30000000000d49444c3a426173653a312e30000000000000000b0000002a0000000368690000ffffffff";
715
716 #[test]
717 fn chunked_encode_byte_identical_to_jacorb() {
718 let mut w = BufferWriter::new(Endianness::Big);
719 let mut vw = ValueWriter::new();
720 let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
721 id: 42,
722 extra: "hi".into(),
723 });
724 vw.write_chunked(&mut w, Some(&d), &["IDL:Base:1.0"])
725 .unwrap();
726 let hex: alloc::string::String = w
727 .into_bytes()
728 .iter()
729 .map(|b| alloc::format!("{b:02x}"))
730 .collect();
731 assert_eq!(
732 hex, JACORB_CHUNKED,
733 "chunked-Wire weicht von JacORB-Capture ab"
734 );
735 }
736
737 fn jacorb_chunked_bytes() -> alloc::vec::Vec<u8> {
738 (0..JACORB_CHUNKED.len() / 2)
739 .map(|i| u8::from_str_radix(&JACORB_CHUNKED[2 * i..2 * i + 2], 16).unwrap())
740 .collect()
741 }
742
743 #[test]
744 fn chunked_decode_full_when_derived_known() {
745 let bytes = jacorb_chunked_bytes();
746 let mut reg = ValueRegistry::new();
747 derived_ctor(&mut reg);
748 base_ctor(&mut reg);
749 let mut r = BufferReader::new(&bytes, Endianness::Big);
750 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
751 assert_eq!(
752 *v.downcast_ref::<Derived>().unwrap(),
753 Derived {
754 id: 42,
755 extra: "hi".into()
756 }
757 );
758 }
759
760 #[test]
761 fn chunked_decode_truncates_to_base() {
762 let bytes = jacorb_chunked_bytes();
764 let mut reg = ValueRegistry::new();
765 base_ctor(&mut reg);
766 let mut r = BufferReader::new(&bytes, Endianness::Big);
767 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
768 assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
769 }
770
771 #[test]
772 fn chunked_roundtrip_zerodds_to_zerodds() {
773 for e in [Endianness::Big, Endianness::Little] {
774 let mut w = BufferWriter::new(e);
775 let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
776 id: 7,
777 extra: "xyz".into(),
778 });
779 ValueWriter::new()
780 .write_chunked(&mut w, Some(&d), &["IDL:Base:1.0"])
781 .unwrap();
782 let bytes = w.into_bytes();
783 let mut reg = ValueRegistry::new();
784 derived_ctor(&mut reg);
785 base_ctor(&mut reg);
786 let mut r = BufferReader::new(&bytes, e);
787 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
788 assert_eq!(
789 *v.downcast_ref::<Derived>().unwrap(),
790 Derived {
791 id: 7,
792 extra: "xyz".into()
793 }
794 );
795 }
796 }
797
798 #[test]
799 fn single_value_and_null_roundtrip() {
800 for e in [Endianness::Big, Endianness::Little] {
801 let mut w = BufferWriter::new(e);
802 let mut vw = ValueWriter::new();
803 let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 3, y: 7 });
804 vw.write(&mut w, Some(&p)).unwrap();
805 vw.write(&mut w, None).unwrap(); let bytes = w.into_bytes();
807 let mut r = BufferReader::new(&bytes, e);
809 let mut vr = ValueReader::new();
810 let reg = point_registry();
811 let v = vr.read(&mut r, 0, ®).unwrap().expect("non-null");
812 let pt = v.downcast_ref::<Point>().expect("Point");
813 assert_eq!(*pt, Point { x: 3, y: 7 });
814 assert!(vr.read(&mut r, 0, ®).unwrap().is_none()); }
816 }
817
818 #[test]
819 fn value_sharing_indirection() {
820 for e in [Endianness::Big, Endianness::Little] {
823 let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 2 });
824 let mut w = BufferWriter::new(e);
825 let mut vw = ValueWriter::new();
826 vw.write(&mut w, Some(&p)).unwrap();
827 vw.write(&mut w, Some(&p)).unwrap(); let bytes = w.into_bytes();
829
830 let mut r = BufferReader::new(&bytes, e);
831 let mut vr = ValueReader::new();
832 let reg = point_registry();
833 let a = vr.read(&mut r, 0, ®).unwrap().unwrap();
834 let b = vr.read(&mut r, 0, ®).unwrap().unwrap();
835 assert!(
836 Rc::ptr_eq(&a, &b),
837 "value sharing: both refs = one instance"
838 );
839 assert_eq!(*a.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 2 });
840 }
841 }
842
843 #[test]
844 fn distinct_values_are_not_shared() {
845 let p1: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 1 });
846 let p2: Rc<dyn ValueMarshal> = Rc::new(Point { x: 2, y: 2 });
847 let mut w = BufferWriter::new(Endianness::Big);
848 let mut vw = ValueWriter::new();
849 vw.write(&mut w, Some(&p1)).unwrap();
850 vw.write(&mut w, Some(&p2)).unwrap();
851 let bytes = w.into_bytes();
852 let mut r = BufferReader::new(&bytes, Endianness::Big);
853 let mut vr = ValueReader::new();
854 let reg = point_registry();
855 let a = vr.read(&mut r, 0, ®).unwrap().unwrap();
856 let b = vr.read(&mut r, 0, ®).unwrap().unwrap();
857 assert!(!Rc::ptr_eq(&a, &b));
858 assert_eq!(*a.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 1 });
859 assert_eq!(*b.downcast_ref::<Point>().unwrap(), Point { x: 2, y: 2 });
860 }
861
862 #[test]
863 fn value_tag_is_single_repo_id_on_wire() {
864 let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 0, y: 0 });
865 let mut w = BufferWriter::new(Endianness::Big);
866 ValueWriter::new().write(&mut w, Some(&p)).unwrap();
867 let bytes = w.into_bytes();
868 assert_eq!(&bytes[0..4], &[0x7f, 0xff, 0xff, 0x02]);
870 }
871
872 #[test]
873 fn forward_indirection_rejected() {
874 let mut w = BufferWriter::new(Endianness::Big);
875 w.write_u32(VALUE_INDIRECTION).unwrap();
876 w.write_u32(4).unwrap(); let bytes = w.into_bytes();
878 let mut r = BufferReader::new(&bytes, Endianness::Big);
879 assert!(
880 ValueReader::new()
881 .read(&mut r, 0, &ValueRegistry::new())
882 .is_err()
883 );
884 }
885
886 #[test]
889 fn codebase_value_tag_and_roundtrip() {
890 let mut w = BufferWriter::new(Endianness::Big);
891 let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 5, y: 9 });
892 ValueWriter::new()
893 .write_with_codebase(&mut w, Some(&p), "file:///stubs.jar")
894 .unwrap();
895 let bytes = w.into_bytes();
896 assert_eq!(&bytes[0..4], &[0x7f, 0xff, 0xff, 0x03]);
898 let mut r = BufferReader::new(&bytes, Endianness::Big);
900 let reg = point_registry();
901 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
902 assert_eq!(*v.downcast_ref::<Point>().unwrap(), Point { x: 5, y: 9 });
903 }
904
905 #[test]
906 fn codebase_resolver_supplies_missing_factory() {
907 let mut w = BufferWriter::new(Endianness::Big);
908 let p: Rc<dyn ValueMarshal> = Rc::new(Point { x: 1, y: 2 });
909 ValueWriter::new()
910 .write_with_codebase(&mut w, Some(&p), "ior://factory-host/Point")
911 .unwrap();
912 let bytes = w.into_bytes();
913
914 let mut reg = ValueRegistry::new();
917 reg.set_codebase_resolver(Box::new(|codebase: &str, repo_id: &str| {
918 if codebase.contains("factory-host") && repo_id == "IDL:Geo/Point:1.0" {
919 Some(Box::new(|r: &mut BufferReader<'_>| {
920 let x = i32::decode(r)?;
921 let y = i32::decode(r)?;
922 Ok(Rc::new(Point { x, y }) as Rc<dyn Any>)
923 }) as ValueCtor)
924 } else {
925 None
926 }
927 }));
928
929 let mut r = BufferReader::new(&bytes, Endianness::Big);
930 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
931 assert_eq!(*v.downcast_ref::<Point>().unwrap(), Point { x: 1, y: 2 });
932
933 let mut r2 = BufferReader::new(&bytes, Endianness::Big);
935 assert!(
936 ValueReader::new()
937 .read(&mut r2, 0, &ValueRegistry::new())
938 .is_err()
939 );
940 }
941
942 fn chunked_with_nested_tail(e: Endianness) -> alloc::vec::Vec<u8> {
948 let mut w = BufferWriter::new(e);
949 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
950 .unwrap(); w.write_u32(2).unwrap();
952 w.write_string("IDL:Derived:1.0").unwrap();
953 w.write_string("IDL:Base:1.0").unwrap();
954 w.align(4);
956 w.write_u32(4).unwrap();
957 w.write_u32(42).unwrap();
958 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
960 .unwrap();
961 w.write_u32(1).unwrap();
962 w.write_string("IDL:Inner:1.0").unwrap();
963 w.align(4);
964 w.write_u32(4).unwrap(); w.write_u32(0x0bad_cafe).unwrap(); w.align(4);
967 w.write_u32((-2i32) as u32).unwrap(); w.align(4);
970 w.write_u32((-1i32) as u32).unwrap();
971 w.into_bytes()
972 }
973
974 #[test]
975 fn nested_chunked_value_in_tail_is_consumed_on_truncation() {
976 for e in [Endianness::Big, Endianness::Little] {
977 let bytes = chunked_with_nested_tail(e);
978 let mut reg = ValueRegistry::new();
981 base_ctor(&mut reg);
982 let mut r = BufferReader::new(&bytes, e);
983 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
984 assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
985 assert_eq!(r.position(), bytes.len());
987 }
988 }
989
990 #[test]
991 fn shared_end_tag_closes_nested_and_outer() {
992 let e = Endianness::Big;
995 let mut w = BufferWriter::new(e);
996 w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
997 .unwrap();
998 w.write_u32(2).unwrap();
999 w.write_string("IDL:Derived:1.0").unwrap();
1000 w.write_string("IDL:Base:1.0").unwrap();
1001 w.align(4);
1002 w.write_u32(4).unwrap();
1003 w.write_u32(42).unwrap(); w.write_u32(VALUE_TAG_BASE | VT_FLAG_CHUNKED | VT_REPO_LIST)
1006 .unwrap();
1007 w.write_u32(1).unwrap();
1008 w.write_string("IDL:Inner:1.0").unwrap();
1009 w.align(4);
1010 w.write_u32(4).unwrap();
1011 w.write_u32(0x0bad_cafe).unwrap();
1012 w.align(4);
1014 w.write_u32((-1i32) as u32).unwrap();
1015 let bytes = w.into_bytes();
1016
1017 let mut reg = ValueRegistry::new();
1018 base_ctor(&mut reg);
1019 let mut r = BufferReader::new(&bytes, e);
1020 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
1021 assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 42 });
1022 assert_eq!(r.position(), bytes.len());
1023 }
1024
1025 #[derive(Debug)]
1031 struct OuterBaseState {
1032 id: i32,
1033 }
1034 impl ValueBase for OuterBaseState {
1035 fn repository_id(&self) -> &str {
1036 "IDL:Derived:1.0"
1037 }
1038 }
1039 impl ValueMarshal for OuterBaseState {
1040 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
1041 self.id.encode(w)
1042 }
1043 }
1044
1045 #[derive(Debug)]
1047 struct InnerVal {
1048 word: u32,
1049 }
1050 impl ValueBase for InnerVal {
1051 fn repository_id(&self) -> &str {
1052 "IDL:Inner:1.0"
1053 }
1054 }
1055 impl ValueMarshal for InnerVal {
1056 fn marshal_state(&self, w: &mut BufferWriter) -> Result<(), EncodeError> {
1057 w.write_u32(self.word)
1058 }
1059 }
1060
1061 #[test]
1062 fn chunked_tree_leaf_is_byte_identical_to_write_chunked() {
1063 for e in [Endianness::Big, Endianness::Little] {
1066 let d: Rc<dyn ValueMarshal> = Rc::new(Derived {
1067 id: 42,
1068 extra: "hi".into(),
1069 });
1070
1071 let mut w1 = BufferWriter::new(e);
1072 ValueWriter::new()
1073 .write_chunked(&mut w1, Some(&d), &["IDL:Base:1.0"])
1074 .unwrap();
1075
1076 let mut w2 = BufferWriter::new(e);
1077 let node = ChunkedNode {
1078 value: &d,
1079 base_ids: &["IDL:Base:1.0"],
1080 nested: &[],
1081 };
1082 ValueWriter::new()
1083 .write_chunked_tree(&mut w2, &node)
1084 .unwrap();
1085
1086 assert_eq!(
1087 w1.into_bytes(),
1088 w2.into_bytes(),
1089 "leaf tree != write_chunked"
1090 );
1091 }
1092 }
1093
1094 #[test]
1095 fn chunked_tree_with_nested_matches_handbuilt_wire() {
1096 for e in [Endianness::Big, Endianness::Little] {
1101 let inner: Rc<dyn ValueMarshal> = Rc::new(InnerVal { word: 0x0bad_cafe });
1102 let outer: Rc<dyn ValueMarshal> = Rc::new(OuterBaseState { id: 42 });
1103 let inner_node = ChunkedNode {
1104 value: &inner,
1105 base_ids: &[],
1106 nested: &[],
1107 };
1108 let outer_node = ChunkedNode {
1109 value: &outer,
1110 base_ids: &["IDL:Base:1.0"],
1111 nested: core::slice::from_ref(&inner_node),
1112 };
1113 let mut w = BufferWriter::new(e);
1114 ValueWriter::new()
1115 .write_chunked_tree(&mut w, &outer_node)
1116 .unwrap();
1117 assert_eq!(
1118 w.into_bytes(),
1119 chunked_with_nested_tail(e),
1120 "multi-chunk encode != hand-built nested-tail wire"
1121 );
1122 }
1123 }
1124
1125 #[test]
1126 fn chunked_tree_roundtrips_with_base_truncation() {
1127 for e in [Endianness::Big, Endianness::Little] {
1130 let inner: Rc<dyn ValueMarshal> = Rc::new(InnerVal { word: 0x0bad_cafe });
1131 let outer: Rc<dyn ValueMarshal> = Rc::new(OuterBaseState { id: 99 });
1132 let inner_node = ChunkedNode {
1133 value: &inner,
1134 base_ids: &[],
1135 nested: &[],
1136 };
1137 let outer_node = ChunkedNode {
1138 value: &outer,
1139 base_ids: &["IDL:Base:1.0"],
1140 nested: core::slice::from_ref(&inner_node),
1141 };
1142 let mut w = BufferWriter::new(e);
1143 ValueWriter::new()
1144 .write_chunked_tree(&mut w, &outer_node)
1145 .unwrap();
1146 let bytes = w.into_bytes();
1147
1148 let mut reg = ValueRegistry::new();
1149 base_ctor(&mut reg);
1150 let mut r = BufferReader::new(&bytes, e);
1151 let v = ValueReader::new().read(&mut r, 0, ®).unwrap().unwrap();
1152 assert_eq!(*v.downcast_ref::<Base>().unwrap(), Base { id: 99 });
1153 assert_eq!(r.position(), bytes.len(), "nested tail not fully consumed");
1154 }
1155 }
1156}