Skip to main content

wacore_binary/
marshal.rs

1use std::io::Write;
2
3use crate::{
4    BinaryError, Node, NodeRef, Result,
5    decoder::Decoder,
6    encoder::{Encoder, build_marshaled_node_plan, build_marshaled_node_ref_plan},
7    node::{NodeContent, NodeContentRef},
8};
9
10const DEFAULT_MARSHAL_CAPACITY: usize = 1024;
11const AUTO_RESERVE_ATTRS_THRESHOLD: usize = 24;
12const AUTO_RESERVE_CHILDREN_THRESHOLD: usize = 64;
13const AUTO_RESERVE_SCALAR_THRESHOLD: usize = 8 * 1024;
14const AUTO_CHILD_SAMPLE_LIMIT: usize = 32;
15const AUTO_MAX_HINT_CAPACITY: usize = 512 * 1024;
16const AUTO_ATTR_ESTIMATE: usize = 24;
17const AUTO_CHILD_ESTIMATE: usize = 96;
18const AUTO_GRANDCHILD_ESTIMATE: usize = 40;
19
20pub fn unmarshal_ref(data: &[u8]) -> Result<NodeRef<'_>> {
21    let mut decoder = Decoder::new(data);
22    let node = decoder.read_node_ref()?;
23
24    if decoder.is_finished() {
25        Ok(node)
26    } else {
27        Err(BinaryError::LeftoverData(decoder.bytes_left()))
28    }
29}
30
31pub fn marshal_to(node: &Node, writer: &mut impl Write) -> Result<()> {
32    let mut encoder = Encoder::new(writer)?;
33    encoder.write_node(node)?;
34    Ok(())
35}
36
37/// Serialize an owned node directly into a `Vec<u8>` using the fast vec writer path.
38pub fn marshal_to_vec(node: &Node, output: &mut Vec<u8>) -> Result<()> {
39    let mut encoder = Encoder::new_vec(output)?;
40    encoder.write_node(node)?;
41    Ok(())
42}
43
44pub fn marshal(node: &Node) -> Result<Vec<u8>> {
45    let mut payload = Vec::with_capacity(DEFAULT_MARSHAL_CAPACITY);
46    marshal_to_vec(node, &mut payload)?;
47    Ok(payload)
48}
49
50/// Serialize a `Node` using a conservative auto strategy.
51///
52/// This keeps the fast one-pass path for typical payloads and only uses
53/// a lightweight preallocation hint for obviously larger payload shapes.
54pub fn marshal_auto(node: &Node) -> Result<Vec<u8>> {
55    if should_auto_reserve_node(node) {
56        marshal_with_capacity(node, estimate_capacity_node(node))
57    } else {
58        marshal(node)
59    }
60}
61
62/// Serialize a `Node` using a two-pass strategy:
63/// 1) compute exact encoded size
64/// 2) write directly into a fixed-size output buffer
65///
66/// This avoids output buffer growth/copies and can be beneficial for large/variable payloads.
67pub fn marshal_exact(node: &Node) -> Result<Vec<u8>> {
68    let plan = build_marshaled_node_plan(node);
69    let mut payload = vec![0; plan.size];
70    let mut encoder = Encoder::new_slice(payload.as_mut_slice(), Some(&plan.hints))?;
71    encoder.write_node(node)?;
72    let written = encoder.bytes_written();
73    // Real checks, not debug_asserts: replayed hints are trusted in release,
74    // so a plan/encode traversal divergence must fail the marshal instead of
75    // shipping corrupt bytes. Two integer compares per stanza.
76    if written != payload.len() || !plan.hints.fully_consumed() {
77        return Err(BinaryError::PlanMismatch);
78    }
79    Ok(payload)
80}
81
82/// Zero-copy serialization of a `NodeRef` directly into a writer.
83/// This avoids the allocation overhead of converting to an owned `Node` first.
84pub fn marshal_ref_to(node: &NodeRef<'_>, writer: &mut impl Write) -> Result<()> {
85    let mut encoder = Encoder::new(writer)?;
86    encoder.write_node(node)?;
87    Ok(())
88}
89
90/// Serialize a borrowed node directly into a `Vec<u8>` using the fast vec writer path.
91pub fn marshal_ref_to_vec(node: &NodeRef<'_>, output: &mut Vec<u8>) -> Result<()> {
92    let mut encoder = Encoder::new_vec(output)?;
93    encoder.write_node(node)?;
94    Ok(())
95}
96
97/// Zero-copy serialization of a `NodeRef` to a new `Vec<u8>`.
98/// Prefer `marshal_ref_to` with a reusable buffer for best performance.
99pub fn marshal_ref(node: &NodeRef<'_>) -> Result<Vec<u8>> {
100    let mut payload = Vec::with_capacity(DEFAULT_MARSHAL_CAPACITY);
101    marshal_ref_to_vec(node, &mut payload)?;
102    Ok(payload)
103}
104
105/// Serialize a `NodeRef` using the same conservative auto strategy as `marshal_auto`.
106pub fn marshal_ref_auto(node: &NodeRef<'_>) -> Result<Vec<u8>> {
107    if should_auto_reserve_node_ref(node) {
108        marshal_ref_with_capacity(node, estimate_capacity_node_ref(node))
109    } else {
110        marshal_ref(node)
111    }
112}
113
114/// Serialize a `NodeRef` using a two-pass exact-size strategy.
115///
116/// This avoids output buffer growth/copies and preserves zero-copy input semantics.
117pub fn marshal_ref_exact(node: &NodeRef<'_>) -> Result<Vec<u8>> {
118    let plan = build_marshaled_node_ref_plan(node);
119    let mut payload = vec![0; plan.size];
120    let mut encoder = Encoder::new_slice(payload.as_mut_slice(), Some(&plan.hints))?;
121    encoder.write_node(node)?;
122    let written = encoder.bytes_written();
123    // Same invariant enforcement as marshal_exact.
124    if written != payload.len() || !plan.hints.fully_consumed() {
125        return Err(BinaryError::PlanMismatch);
126    }
127    Ok(payload)
128}
129
130#[inline]
131fn marshal_with_capacity(node: &Node, capacity: usize) -> Result<Vec<u8>> {
132    let mut payload = Vec::with_capacity(capacity);
133    marshal_to_vec(node, &mut payload)?;
134    Ok(payload)
135}
136
137#[inline]
138fn marshal_ref_with_capacity(node: &NodeRef<'_>, capacity: usize) -> Result<Vec<u8>> {
139    let mut payload = Vec::with_capacity(capacity);
140    marshal_ref_to_vec(node, &mut payload)?;
141    Ok(payload)
142}
143
144#[inline]
145fn should_auto_reserve_node(node: &Node) -> bool {
146    if node.attrs.len() >= AUTO_RESERVE_ATTRS_THRESHOLD {
147        return true;
148    }
149
150    match &node.content {
151        Some(NodeContent::Bytes(bytes)) => bytes.len() >= AUTO_RESERVE_SCALAR_THRESHOLD,
152        Some(NodeContent::String(text)) => text.len() >= AUTO_RESERVE_SCALAR_THRESHOLD,
153        Some(NodeContent::Nodes(children)) => {
154            if children.len() >= AUTO_RESERVE_CHILDREN_THRESHOLD {
155                return true;
156            }
157            // Check one level deeper for large nested lists (e.g., <iq> -> <list> -> 812 keys)
158            children.iter().any(|child| {
159                matches!(&child.content, Some(NodeContent::Nodes(gc)) if gc.len() >= AUTO_RESERVE_CHILDREN_THRESHOLD)
160            })
161        }
162        None => false,
163    }
164}
165
166#[inline]
167fn should_auto_reserve_node_ref(node: &NodeRef<'_>) -> bool {
168    if node.attrs.len() >= AUTO_RESERVE_ATTRS_THRESHOLD {
169        return true;
170    }
171
172    match node.content.as_ref() {
173        Some(NodeContentRef::Bytes(bytes)) => bytes.len() >= AUTO_RESERVE_SCALAR_THRESHOLD,
174        Some(NodeContentRef::String(text)) => text.len() >= AUTO_RESERVE_SCALAR_THRESHOLD,
175        Some(NodeContentRef::Nodes(children)) => {
176            if children.len() >= AUTO_RESERVE_CHILDREN_THRESHOLD {
177                return true;
178            }
179            // Check one level deeper for large nested lists (e.g., <iq> -> <list> -> 812 keys)
180            children.iter().any(|child| {
181                matches!(child.content.as_ref(), Some(NodeContentRef::Nodes(gc)) if gc.len() >= AUTO_RESERVE_CHILDREN_THRESHOLD)
182            })
183        }
184        None => false,
185    }
186}
187
188#[inline]
189fn estimate_capacity_node(node: &Node) -> usize {
190    let mut estimate = DEFAULT_MARSHAL_CAPACITY + 16;
191    estimate += node.tag.len();
192    estimate += node.attrs.len() * AUTO_ATTR_ESTIMATE;
193
194    match &node.content {
195        Some(NodeContent::Bytes(bytes)) => {
196            estimate += bytes.len() + 8;
197        }
198        Some(NodeContent::String(text)) => {
199            estimate += text.len() + 8;
200        }
201        Some(NodeContent::Nodes(children)) => {
202            estimate += children.len() * AUTO_CHILD_ESTIMATE;
203            for child in children.iter().take(AUTO_CHILD_SAMPLE_LIMIT) {
204                estimate += child.tag.len() + child.attrs.len() * AUTO_ATTR_ESTIMATE;
205                match &child.content {
206                    Some(NodeContent::Bytes(bytes)) => estimate += bytes.len() + 8,
207                    Some(NodeContent::String(text)) => estimate += text.len() + 8,
208                    Some(NodeContent::Nodes(grand_children)) => {
209                        estimate += grand_children.len() * AUTO_GRANDCHILD_ESTIMATE;
210                    }
211                    None => {}
212                }
213                if estimate >= AUTO_MAX_HINT_CAPACITY {
214                    return AUTO_MAX_HINT_CAPACITY;
215                }
216            }
217        }
218        None => {}
219    }
220
221    estimate.clamp(DEFAULT_MARSHAL_CAPACITY, AUTO_MAX_HINT_CAPACITY)
222}
223
224#[inline]
225fn estimate_capacity_node_ref(node: &NodeRef<'_>) -> usize {
226    let mut estimate = DEFAULT_MARSHAL_CAPACITY + 16;
227    estimate += node.tag.len();
228    estimate += node.attrs.len() * AUTO_ATTR_ESTIMATE;
229
230    match node.content.as_ref() {
231        Some(NodeContentRef::Bytes(bytes)) => {
232            estimate += bytes.len() + 8;
233        }
234        Some(NodeContentRef::String(text)) => {
235            estimate += text.len() + 8;
236        }
237        Some(NodeContentRef::Nodes(children)) => {
238            estimate += children.len() * AUTO_CHILD_ESTIMATE;
239            for child in children.iter().take(AUTO_CHILD_SAMPLE_LIMIT) {
240                estimate += child.tag.len() + child.attrs.len() * AUTO_ATTR_ESTIMATE;
241                match child.content.as_ref() {
242                    Some(NodeContentRef::Bytes(bytes)) => estimate += bytes.len() + 8,
243                    Some(NodeContentRef::String(text)) => estimate += text.len() + 8,
244                    Some(NodeContentRef::Nodes(grand_children)) => {
245                        estimate += grand_children.len() * AUTO_GRANDCHILD_ESTIMATE;
246                    }
247                    None => {}
248                }
249                if estimate >= AUTO_MAX_HINT_CAPACITY {
250                    return AUTO_MAX_HINT_CAPACITY;
251                }
252            }
253        }
254        None => {}
255    }
256
257    estimate.clamp(DEFAULT_MARSHAL_CAPACITY, AUTO_MAX_HINT_CAPACITY)
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::jid::Jid;
264    use crate::node::{Attrs, NodeContent, NodeValue};
265
266    type TestResult = Result<()>;
267
268    /// An interop JID's `integrator` has a wire field only in `INTEROP_JID`.
269    /// Encoded as a `JID_PAIR` it is simply absent, so two interop destinations
270    /// that differ only there went out as the same bytes.
271    ///
272    /// Asserted on the emitted bytes rather than by round-tripping: our decoder
273    /// reads a trailing server that the outbound form does not carry (see
274    /// `write_interop_jid`), so a local encode/decode cycle is the wrong oracle
275    /// here — the two directions of this token genuinely differ.
276    ///
277    /// Every encoder path is covered because `marshal_exact` sizes its output
278    /// slice from the size estimator before writing into it: an estimator that
279    /// disagrees with the writer surfaces as `UnexpectedEof`, not as wrong bytes,
280    /// and `marshal_exact` is what production sends through.
281    #[test]
282    fn interop_jid_carries_its_integrator_onto_the_wire() -> TestResult {
283        use crate::jid::Server;
284
285        fn node_for(jid: &Jid) -> Node {
286            let mut attrs = Attrs::with_capacity(1);
287            attrs.push("jid".to_string(), NodeValue::Jid(jid.clone()));
288            Node::new("iq", attrs, None)
289        }
290
291        fn encode_all(jid: &Jid) -> Vec<(&'static str, Vec<u8>)> {
292            let node = node_for(jid);
293            let r = node.as_node_ref();
294            vec![
295                ("marshal", marshal(&node).expect("marshal")),
296                ("marshal_exact", marshal_exact(&node).expect("exact")),
297                ("marshal_ref", marshal_ref(&r).expect("ref")),
298                (
299                    "marshal_ref_exact",
300                    marshal_ref_exact(&r).expect("ref exact"),
301                ),
302            ]
303        }
304
305        let base = Jid {
306            user: "123456789".into(),
307            server: Server::Interop,
308            agent: 0,
309            device: 7,
310            integrator: 300,
311        };
312
313        // The integrator reaches the bytes: change only it, and the encoding
314        // changes. Under JID_PAIR these were byte-identical.
315        let other = Jid {
316            integrator: 301,
317            ..base.clone()
318        };
319        for ((path, a), (_, b)) in encode_all(&base).into_iter().zip(encode_all(&other)) {
320            assert_ne!(a, b, "{path}: the integrator must reach the wire");
321            assert!(
322                a.contains(&crate::token::INTEROP_JID),
323                "{path}: must use the INTEROP_JID token"
324            );
325            // Big-endian device and integrator, adjacent, as WA Web writes them.
326            assert!(
327                a.windows(4).any(|w| w == [0x00, 0x07, 0x01, 0x2C]),
328                "{path}: device 7 and integrator 300 as u16 BE"
329            );
330        }
331
332        // Every path agrees byte for byte, so the exact-size plan matches the
333        // writer. When it does not, `marshal_exact` fails outright.
334        let encodings = encode_all(&base);
335        let (_, first) = &encodings[0];
336        for (path, bytes) in &encodings[1..] {
337            assert_eq!(bytes, first, "{path}: must agree with marshal");
338        }
339
340        // A zero-integrator interop JID keeps the JID_PAIR form we have always
341        // sent. That form carries user and server only — it drops the device —
342        // which is pre-existing and deliberately untouched here.
343        let plain = Jid {
344            integrator: 0,
345            ..base.clone()
346        };
347        let bytes = marshal(&node_for(&plain))?;
348        assert!(
349            !bytes.contains(&crate::token::INTEROP_JID),
350            "no integrator, no INTEROP_JID token"
351        );
352        let decoded = unmarshal_ref(&bytes[1..])?;
353        let back = decoded
354            .attrs
355            .iter()
356            .find(|(k, _)| &**k == "jid")
357            .and_then(|(_, v)| v.to_jid())
358            .expect("jid attr");
359        assert_eq!(back.server, Server::Interop);
360        assert_eq!(back.device, 0, "JID_PAIR carries no device");
361
362        Ok(())
363    }
364
365    /// The two round-trips real code performs — through the wire, and through the
366    /// store, which holds JIDs as text — have to agree: a JID that survives one
367    /// must equal a JID that survives the other, or `stored_jid == wire_jid`
368    /// silently flips depending on where each side came from.
369    #[test]
370    fn ad_jid_round_trips_equal_through_the_wire_and_through_text() -> TestResult {
371        use crate::jid::Server;
372        use std::str::FromStr;
373
374        for server in [Server::Pn, Server::Lid, Server::Hosted, Server::HostedLid] {
375            let original = Jid {
376                user: "123456789012345".into(),
377                server,
378                agent: 0,
379                device: 7,
380                integrator: 0,
381            };
382
383            let mut attrs = Attrs::with_capacity(1);
384            attrs.push("jid".to_string(), NodeValue::Jid(original.clone()));
385            let node = Node::new("iq", attrs, None);
386
387            // marshal writes a leading format byte that unmarshal_ref does not expect.
388            let bytes = marshal(&node)?;
389            let decoded = unmarshal_ref(&bytes[1..])?;
390            let from_wire = decoded
391                .attrs
392                .iter()
393                .find(|(k, _)| &**k == "jid")
394                .and_then(|(_, v)| v.to_jid())
395                .expect("jid attr survives the round-trip");
396
397            assert_eq!(
398                from_wire, original,
399                "{server:?}: encode -> decode must be idempotent"
400            );
401
402            let from_text = Jid::from_str(&from_wire.to_string()).expect("renders parseably");
403            assert_eq!(
404                from_wire, from_text,
405                "{server:?}: a wire-decoded JID must equal the same JID read back as text"
406            );
407        }
408
409        Ok(())
410    }
411
412    fn fixture_node() -> Node {
413        let mut attrs = Attrs::with_capacity(4);
414        attrs.push("id".to_string(), "ABC123");
415        attrs.push("to".to_string(), "123456789@s.whatsapp.net");
416        attrs.push(
417            "participant".to_string(),
418            NodeValue::Jid("15551234567@s.whatsapp.net".parse::<Jid>().unwrap()),
419        );
420        attrs.push("hex".to_string(), "DEADBEEF");
421
422        let child = Node::new(
423            "item",
424            Attrs::new(),
425            Some(NodeContent::Bytes(vec![1, 2, 3, 4, 5, 6, 7, 8])),
426        );
427
428        Node::new(
429            "message",
430            attrs,
431            Some(NodeContent::Nodes(vec![
432                child,
433                Node::new(
434                    "text",
435                    Attrs::new(),
436                    Some(NodeContent::String("hello".repeat(40).into())),
437                ),
438            ])),
439        )
440    }
441
442    fn large_binary_fixture() -> Node {
443        Node::new(
444            "message",
445            Attrs::new(),
446            Some(NodeContent::Bytes(vec![
447                0xAB;
448                AUTO_RESERVE_SCALAR_THRESHOLD + 2048
449            ])),
450        )
451    }
452
453    #[test]
454    fn test_marshaled_node_size_matches_output() -> TestResult {
455        let node = fixture_node();
456        let plan = build_marshaled_node_plan(&node);
457        let payload = marshal(&node)?;
458        assert_eq!(payload.len(), plan.size);
459        Ok(())
460    }
461
462    // The exact path replays plan-recorded hints by traversal order, so it
463    // must produce byte-identical output to the hint-free vec path for every
464    // string shape (tokens, numerics, hex, JIDs with device/agent/empty user,
465    // long strings, bytes, nesting). A divergence in traversal order shows up
466    // here (and as a debug_assert in write_string) before it can corrupt the
467    // wire.
468    #[test]
469    fn test_exact_matches_plain_for_all_string_shapes() -> TestResult {
470        let mut attrs = Attrs::with_capacity(8);
471        attrs.push("to".to_string(), "15551234567@s.whatsapp.net");
472        attrs.push("from".to_string(), "15550000001:12@s.whatsapp.net");
473        attrs.push("participant".to_string(), "15550000002_1@lid");
474        attrs.push("broadcast".to_string(), "status@broadcast");
475        attrs.push("type".to_string(), "text");
476        attrs.push("count".to_string(), "12345");
477        attrs.push("hexish".to_string(), "0123ABCDEF");
478        attrs.push("plain".to_string(), "not_a_token_value");
479        // Empty-user JID: the one branch that skips a hint entirely.
480        attrs.push("empty_user".to_string(), "@s.whatsapp.net");
481        // Typed JID value: user/server hints with no wrapping string hint.
482        attrs.push(
483            "typed_jid".to_string(),
484            NodeValue::Jid("15550000003:7@s.whatsapp.net".parse::<Jid>().unwrap()),
485        );
486        let node = Node::new(
487            "iq",
488            attrs,
489            Some(NodeContent::Nodes(vec![
490                Node::new(
491                    "text",
492                    Attrs::new(),
493                    Some(NodeContent::String("x".repeat(300).into())),
494                ),
495                Node::new("empty", Attrs::new(), Some(NodeContent::String("".into()))),
496                Node::new(
497                    "bin",
498                    Attrs::new(),
499                    Some(NodeContent::Bytes(vec![0xAB; 64])),
500                ),
501                Node::new("leaf", Attrs::new(), None),
502            ])),
503        );
504
505        assert_eq!(marshal(&node)?, marshal_exact(&node)?);
506        let node_ref = node.as_node_ref();
507        assert_eq!(marshal_ref(&node_ref)?, marshal_ref_exact(&node_ref)?);
508        Ok(())
509    }
510
511    #[test]
512    fn test_marshaled_node_ref_size_matches_output() -> TestResult {
513        let node = fixture_node();
514        let node_ref = node.as_node_ref();
515        let plan = build_marshaled_node_ref_plan(&node_ref);
516        let payload = marshal_ref(&node_ref)?;
517        assert_eq!(payload.len(), plan.size);
518        Ok(())
519    }
520
521    #[test]
522    fn test_marshal_matches_marshal_to_bytes() -> TestResult {
523        let node = fixture_node();
524
525        let payload_alloc = marshal(&node)?;
526
527        let mut payload_writer = Vec::new();
528        marshal_to(&node, &mut payload_writer)?;
529
530        assert_eq!(payload_alloc, payload_writer);
531        Ok(())
532    }
533
534    #[test]
535    fn test_marshal_ref_matches_marshal_ref_to_bytes() -> TestResult {
536        let node = fixture_node();
537        let node_ref = node.as_node_ref();
538
539        let payload_alloc = marshal_ref(&node_ref)?;
540
541        let mut payload_writer = Vec::new();
542        marshal_ref_to(&node_ref, &mut payload_writer)?;
543
544        assert_eq!(payload_alloc, payload_writer);
545        Ok(())
546    }
547
548    #[test]
549    fn test_marshal_to_vec_matches_marshal_to() -> TestResult {
550        let node = fixture_node();
551
552        let mut payload_vec_writer = Vec::new();
553        marshal_to_vec(&node, &mut payload_vec_writer)?;
554
555        let mut payload_writer = Vec::new();
556        marshal_to(&node, &mut payload_writer)?;
557
558        assert_eq!(payload_vec_writer, payload_writer);
559        Ok(())
560    }
561
562    #[test]
563    fn test_marshal_ref_to_vec_matches_marshal_ref_to() -> TestResult {
564        let node = fixture_node();
565        let node_ref = node.as_node_ref();
566
567        let mut payload_vec_writer = Vec::new();
568        marshal_ref_to_vec(&node_ref, &mut payload_vec_writer)?;
569
570        let mut payload_writer = Vec::new();
571        marshal_ref_to(&node_ref, &mut payload_writer)?;
572
573        assert_eq!(payload_vec_writer, payload_writer);
574        Ok(())
575    }
576
577    #[test]
578    fn test_marshal_exact_matches_marshal_to_bytes() -> TestResult {
579        let node = fixture_node();
580
581        let payload_exact = marshal_exact(&node)?;
582
583        let mut payload_writer = Vec::new();
584        marshal_to(&node, &mut payload_writer)?;
585
586        assert_eq!(payload_exact, payload_writer);
587        Ok(())
588    }
589
590    #[test]
591    fn test_marshal_ref_exact_matches_marshal_ref_to_bytes() -> TestResult {
592        let node = fixture_node();
593        let node_ref = node.as_node_ref();
594
595        let payload_exact = marshal_ref_exact(&node_ref)?;
596
597        let mut payload_writer = Vec::new();
598        marshal_ref_to(&node_ref, &mut payload_writer)?;
599
600        assert_eq!(payload_exact, payload_writer);
601        Ok(())
602    }
603
604    #[test]
605    fn test_marshal_auto_matches_marshal_to_bytes() -> TestResult {
606        let node = fixture_node();
607        let payload_auto = marshal_auto(&node)?;
608
609        let mut payload_writer = Vec::new();
610        marshal_to(&node, &mut payload_writer)?;
611
612        assert_eq!(payload_auto, payload_writer);
613        Ok(())
614    }
615
616    #[test]
617    fn test_marshal_ref_auto_matches_marshal_ref_to_bytes() -> TestResult {
618        let node = fixture_node();
619        let node_ref = node.as_node_ref();
620        let payload_auto = marshal_ref_auto(&node_ref)?;
621
622        let mut payload_writer = Vec::new();
623        marshal_ref_to(&node_ref, &mut payload_writer)?;
624
625        assert_eq!(payload_auto, payload_writer);
626        Ok(())
627    }
628
629    #[test]
630    fn test_marshal_auto_large_binary_matches_marshal_to_bytes() -> TestResult {
631        let node = large_binary_fixture();
632        let payload_auto = marshal_auto(&node)?;
633
634        let mut payload_writer = Vec::new();
635        marshal_to(&node, &mut payload_writer)?;
636
637        assert_eq!(payload_auto, payload_writer);
638        Ok(())
639    }
640
641    #[test]
642    fn test_marshal_ref_auto_large_binary_matches_marshal_ref_to_bytes() -> TestResult {
643        let node = large_binary_fixture();
644        let node_ref = node.as_node_ref();
645        let payload_auto = marshal_ref_auto(&node_ref)?;
646
647        let mut payload_writer = Vec::new();
648        marshal_ref_to(&node_ref, &mut payload_writer)?;
649
650        assert_eq!(payload_auto, payload_writer);
651        Ok(())
652    }
653}