1use std::io::Write;
2
3#[cfg(feature = "simd")]
4use core::simd::Select;
5#[cfg(feature = "simd")]
6use core::simd::prelude::*;
7#[cfg(feature = "simd")]
8use core::simd::{Simd, u8x16};
9
10use crate::error::{BinaryError, Result};
11use crate::jid::{self, Jid, JidRef};
12use crate::node::{Node, NodeContent, NodeContentRef, NodeRef, NodeValue, ValueRef};
13use crate::token;
14
15pub trait ByteWriter {
16 fn write_u8(&mut self, value: u8) -> Result<()>;
17 fn write_bytes(&mut self, bytes: &[u8]) -> Result<()>;
18}
19
20pub(crate) struct IoByteWriter<W: Write> {
21 writer: W,
22}
23
24impl<W: Write> IoByteWriter<W> {
25 fn new(writer: W) -> Self {
26 Self { writer }
27 }
28}
29
30impl<W: Write> ByteWriter for IoByteWriter<W> {
31 #[inline]
32 fn write_u8(&mut self, value: u8) -> Result<()> {
33 self.writer.write_all(&[value])?;
34 Ok(())
35 }
36
37 #[inline]
38 fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
39 self.writer.write_all(bytes)?;
40 Ok(())
41 }
42}
43
44pub struct VecByteWriter<'a> {
45 buffer: &'a mut Vec<u8>,
46}
47
48impl<'a> VecByteWriter<'a> {
49 fn new(buffer: &'a mut Vec<u8>) -> Self {
50 Self { buffer }
51 }
52}
53
54impl ByteWriter for VecByteWriter<'_> {
55 #[inline]
56 fn write_u8(&mut self, value: u8) -> Result<()> {
57 self.buffer.push(value);
58 Ok(())
59 }
60
61 #[inline]
62 fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
63 self.buffer.extend_from_slice(bytes);
64 Ok(())
65 }
66}
67
68pub(crate) struct SliceByteWriter<'a> {
69 buffer: &'a mut [u8],
70 position: usize,
71}
72
73impl<'a> SliceByteWriter<'a> {
74 fn new(buffer: &'a mut [u8]) -> Self {
75 Self {
76 buffer,
77 position: 0,
78 }
79 }
80
81 #[inline]
82 fn bytes_written(&self) -> usize {
83 self.position
84 }
85}
86
87impl ByteWriter for SliceByteWriter<'_> {
88 #[inline]
89 fn write_u8(&mut self, value: u8) -> Result<()> {
90 if self.position >= self.buffer.len() {
91 return Err(BinaryError::UnexpectedEof);
92 }
93 self.buffer[self.position] = value;
94 self.position += 1;
95 Ok(())
96 }
97
98 #[inline]
99 fn write_bytes(&mut self, bytes: &[u8]) -> Result<()> {
100 let end = self.position + bytes.len();
101 if end > self.buffer.len() {
102 return Err(BinaryError::UnexpectedEof);
103 }
104 self.buffer[self.position..end].copy_from_slice(bytes);
105 self.position = end;
106 Ok(())
107 }
108}
109
110pub trait EncodeNode {
114 fn tag(&self) -> &str;
115 fn attrs_len(&self) -> usize;
116 fn has_content(&self) -> bool;
117
118 fn encode_attrs<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()>;
120
121 fn encode_content<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()>;
123}
124
125impl EncodeNode for Node {
126 fn tag(&self) -> &str {
127 &self.tag
128 }
129
130 fn attrs_len(&self) -> usize {
131 self.attrs.len()
132 }
133
134 fn has_content(&self) -> bool {
135 self.content.is_some()
136 }
137
138 fn encode_attrs<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()> {
139 for (k, v) in &self.attrs {
140 encoder.write_string(k)?;
141 match v {
142 NodeValue::String(s) => encoder.write_string(s)?,
143 NodeValue::Jid(jid) => encoder.write_jid_owned(jid)?,
144 }
145 }
146 Ok(())
147 }
148
149 fn encode_content<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()> {
150 if let Some(content) = &self.content {
151 match content {
152 NodeContent::String(s) => encoder.write_string(s)?,
153 NodeContent::Bytes(b) => encoder.write_bytes_with_len(b)?,
154 NodeContent::Nodes(nodes) => {
155 encoder.write_list_start(nodes.len())?;
156 for node in nodes {
157 encoder.write_node(node)?;
158 }
159 }
160 }
161 }
162 Ok(())
163 }
164}
165
166impl EncodeNode for NodeRef<'_> {
167 fn tag(&self) -> &str {
168 &self.tag
169 }
170
171 fn attrs_len(&self) -> usize {
172 self.attrs.len()
173 }
174
175 fn has_content(&self) -> bool {
176 self.content.is_some()
177 }
178
179 fn encode_attrs<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()> {
180 for (k, v) in self.attrs.iter() {
181 encoder.write_string(k)?;
182 match v {
183 ValueRef::String(s) => encoder.write_string(s)?,
184 ValueRef::Jid(jid) => encoder.write_jid_ref(jid)?,
185 }
186 }
187 Ok(())
188 }
189
190 fn encode_content<'a, W: ByteWriter>(&self, encoder: &mut Encoder<'a, W>) -> Result<()> {
191 if let Some(content) = self.content.as_ref() {
192 match content {
193 NodeContentRef::String(s) => encoder.write_string(s)?,
194 NodeContentRef::Bytes(b) => encoder.write_bytes_with_len(b)?,
195 NodeContentRef::Nodes(nodes) => {
196 encoder.write_list_start(nodes.len())?;
197 for node in nodes.iter() {
198 encoder.write_node(node)?;
199 }
200 }
201 }
202 }
203 Ok(())
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211struct ParsedJidMeta {
212 user_end: u8,
213 server_start: u8,
214 domain_type: u8,
215 device: Option<u8>,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219enum StringHint {
220 Empty,
221 SingleToken(u8),
222 DoubleToken { dict: u8, token: u8 },
223 PackedNibble,
224 PackedHex,
225 Jid(ParsedJidMeta),
226 RawBytes,
227}
228
229#[derive(Debug, Default)]
233pub(crate) struct StringHintCache {
234 hints: smallvec::SmallVec<[StringHint; 32]>,
235 cursor: std::cell::Cell<usize>,
236}
237
238impl StringHintCache {
239 #[inline]
241 fn record(&mut self, s: &str) -> StringHint {
242 let hint = if s.len() > token::PACKED_MAX as usize {
245 StringHint::RawBytes
246 } else {
247 classify_string_hint(s)
248 };
249 self.hints.push(hint);
250 hint
251 }
252
253 #[inline]
255 fn next(&self) -> Option<StringHint> {
256 let i = self.cursor.get();
257 let hint = self.hints.get(i).copied();
258 if hint.is_some() {
259 self.cursor.set(i + 1);
260 }
261 hint
262 }
263
264 #[inline]
269 pub(crate) fn fully_consumed(&self) -> bool {
270 self.cursor.get() == self.hints.len()
271 }
272}
273
274#[derive(Debug)]
275pub(crate) struct MarshaledSizePlan {
276 pub(crate) size: usize,
277 pub(crate) hints: StringHintCache,
278}
279
280fn parse_jid_meta(input: &str) -> Option<ParsedJidMeta> {
281 let sep_idx = input.find('@')?;
282 let server_start = sep_idx + 1;
283 let server = &input[server_start..];
284 let user_combined = &input[..sep_idx];
285
286 let (user_agent, device) = if let Some(colon_idx) = user_combined.find(':')
287 && let Ok(parsed_device) = user_combined[colon_idx + 1..].parse::<u8>()
288 {
289 (&user_combined[..colon_idx], Some(parsed_device))
290 } else {
291 (user_combined, None)
292 };
293
294 let user_end = if let Some(underscore_idx) = user_agent.find('_')
295 && user_agent[underscore_idx + 1..].parse::<u8>().is_ok()
296 {
297 underscore_idx
298 } else {
299 user_agent.len()
300 };
301
302 let server_kind = jid::Server::parse_known(server);
303 let domain_type = match server_kind {
304 Some(jid::Server::Pn) => 0,
305 Some(jid::Server::Lid) => 1,
306 Some(jid::Server::Hosted) => 128,
307 Some(jid::Server::HostedLid) => 129,
308 _ => 0,
309 };
310
311 let device = server_kind
315 .filter(|s| server_supports_ad_jid(*s))
316 .and(device);
317
318 Some(ParsedJidMeta {
319 user_end: u8::try_from(user_end).ok()?,
320 server_start: u8::try_from(server_start).ok()?,
321 domain_type,
322 device,
323 })
324}
325
326#[inline]
327fn split_jid_from_meta(input: &str, meta: ParsedJidMeta) -> (&str, &str) {
328 (
329 &input[..meta.user_end as usize],
330 &input[meta.server_start as usize..],
331 )
332}
333
334#[inline]
348fn server_to_domain_type(server: jid::Server) -> u8 {
349 match server {
350 jid::Server::Pn => 0,
351 jid::Server::Lid => 1,
352 jid::Server::Hosted => 128,
353 jid::Server::HostedLid => 129,
354 _ => 0,
355 }
356}
357
358#[inline]
371fn needs_interop_jid(server: jid::Server, integrator: u16) -> bool {
372 server == jid::Server::Interop && integrator != 0
373}
374
375#[inline]
382fn server_supports_ad_jid(server: jid::Server) -> bool {
383 matches!(
384 server,
385 jid::Server::Pn | jid::Server::Lid | jid::Server::Hosted | jid::Server::HostedLid
386 )
387}
388
389#[inline]
390fn classify_string_hint(s: &str) -> StringHint {
391 if s.is_empty() {
392 return StringHint::Empty;
393 }
394
395 let is_likely_jid = s.len() <= 48;
396
397 if let Some(kind) = token::index_of_token(s) {
398 return match kind {
399 token::TokenKind::Single(token) => StringHint::SingleToken(token),
400 token::TokenKind::Double(dict, token) => StringHint::DoubleToken { dict, token },
401 };
402 }
403
404 if validate_nibble(s) {
405 StringHint::PackedNibble
406 } else if validate_hex(s) {
407 StringHint::PackedHex
408 } else if is_likely_jid {
409 parse_jid_meta(s).map_or(StringHint::RawBytes, StringHint::Jid)
410 } else {
411 StringHint::RawBytes
412 }
413}
414
415pub(crate) fn build_marshaled_node_plan(node: &Node) -> MarshaledSizePlan {
416 let mut hints = StringHintCache::default();
417 let size = 1 + node_encoded_size_with_cache(node, &mut hints);
418 MarshaledSizePlan { size, hints }
419}
420
421pub(crate) fn build_marshaled_node_ref_plan(node: &NodeRef<'_>) -> MarshaledSizePlan {
422 let mut hints = StringHintCache::default();
423 let size = 1 + node_ref_encoded_size_with_cache(node, &mut hints);
424 MarshaledSizePlan { size, hints }
425}
426
427#[inline]
428fn list_start_encoded_size(len: usize) -> usize {
429 if len == 0 {
430 1
431 } else if len < 256 {
432 2
433 } else {
434 3
435 }
436}
437
438#[inline]
439fn binary_len_prefix_size(len: usize) -> usize {
440 if len < 256 {
441 2
442 } else if len < (1 << 20) {
443 4
444 } else {
445 5
446 }
447}
448
449#[inline]
450fn bytes_with_len_encoded_size(len: usize) -> usize {
451 binary_len_prefix_size(len) + len
452}
453
454#[inline]
455fn packed_encoded_size(value_len: usize) -> usize {
456 2 + value_len.div_ceil(2)
457}
458
459fn node_encoded_size_with_cache(node: &Node, hints: &mut StringHintCache) -> usize {
463 let content_len = usize::from(node.content.is_some());
464 let list_len = 1 + (node.attrs.len() * 2) + content_len;
465
466 let mut size =
467 list_start_encoded_size(list_len) + string_encoded_size_with_cache(&node.tag, hints);
468
469 for (k, v) in &node.attrs {
470 size += string_encoded_size_with_cache(k, hints);
471 size += match v {
472 NodeValue::String(s) => string_encoded_size_with_cache(s, hints),
473 NodeValue::Jid(jid) => owned_jid_encoded_size_with_cache(jid, hints),
474 };
475 }
476
477 size += match &node.content {
478 Some(NodeContent::String(s)) => string_encoded_size_with_cache(s, hints),
479 Some(NodeContent::Bytes(b)) => bytes_with_len_encoded_size(b.len()),
480 Some(NodeContent::Nodes(nodes)) => {
481 list_start_encoded_size(nodes.len())
482 + nodes
483 .iter()
484 .map(|child| node_encoded_size_with_cache(child, hints))
485 .sum::<usize>()
486 }
487 None => 0,
488 };
489 size
490}
491
492fn node_ref_encoded_size_with_cache(node: &NodeRef<'_>, hints: &mut StringHintCache) -> usize {
494 let content_len = usize::from(node.content.is_some());
495 let list_len = 1 + (node.attrs.len() * 2) + content_len;
496
497 let mut size = list_start_encoded_size(list_len)
498 + string_encoded_size_with_cache(node.tag.as_ref(), hints);
499
500 for (k, v) in node.attrs.iter() {
501 size += string_encoded_size_with_cache(k, hints);
502 size += match v {
503 ValueRef::String(s) => string_encoded_size_with_cache(s, hints),
504 ValueRef::Jid(jid) => jid_ref_encoded_size_with_cache(jid, hints),
505 };
506 }
507
508 size += match node.content.as_ref() {
509 Some(NodeContentRef::String(s)) => string_encoded_size_with_cache(s, hints),
510 Some(NodeContentRef::Bytes(b)) => bytes_with_len_encoded_size(b.len()),
511 Some(NodeContentRef::Nodes(nodes)) => {
512 list_start_encoded_size(nodes.len())
513 + nodes
514 .iter()
515 .map(|child| node_ref_encoded_size_with_cache(child, hints))
516 .sum::<usize>()
517 }
518 None => 0,
519 };
520 size
521}
522
523#[inline]
524fn string_encoded_size_with_cache(s: &str, hints: &mut StringHintCache) -> usize {
525 let hint = hints.record(s);
526 string_encoded_size_from_hint_with_cache(s, hint, hints)
527}
528
529#[inline]
530fn string_encoded_size_from_hint_with_cache(
531 s: &str,
532 hint: StringHint,
533 hints: &mut StringHintCache,
534) -> usize {
535 match hint {
536 StringHint::Empty => 2,
537 StringHint::SingleToken(_) => 1,
538 StringHint::DoubleToken { .. } => 2,
539 StringHint::PackedNibble | StringHint::PackedHex => packed_encoded_size(s.len()),
540 StringHint::RawBytes => bytes_with_len_encoded_size(s.len()),
541 StringHint::Jid(meta) => parsed_jid_encoded_size_with_cache(s, meta, hints),
542 }
543}
544
545#[inline]
546fn parsed_jid_encoded_size_with_cache(
547 jid: &str,
548 meta: ParsedJidMeta,
549 hints: &mut StringHintCache,
550) -> usize {
551 let (user, server) = split_jid_from_meta(jid, meta);
552 if meta.device.is_some() {
553 3 + string_encoded_size_with_cache(user, hints)
554 } else {
555 let user_size = if user.is_empty() {
556 1
557 } else {
558 string_encoded_size_with_cache(user, hints)
559 };
560 1 + user_size + string_encoded_size_with_cache(server, hints)
561 }
562}
563
564#[inline]
572fn jid_encoded_size_with_cache(
573 user: &str,
574 server: jid::Server,
575 device: u16,
576 integrator: u16,
577 hints: &mut StringHintCache,
578) -> usize {
579 if needs_interop_jid(server, integrator) {
580 return 1 + string_encoded_size_with_cache(user, hints) + 2 + 2;
583 }
584 if device > 0 && server_supports_ad_jid(server) {
585 return 3 + string_encoded_size_with_cache(user, hints);
586 }
587 let user_size = if user.is_empty() {
588 1
589 } else {
590 string_encoded_size_with_cache(user, hints)
591 };
592 1 + user_size + string_encoded_size_with_cache(server.as_str(), hints)
593}
594
595#[inline]
596fn owned_jid_encoded_size_with_cache(jid: &Jid, hints: &mut StringHintCache) -> usize {
597 jid_encoded_size_with_cache(&jid.user, jid.server, jid.device, jid.integrator, hints)
598}
599
600#[inline]
601fn jid_ref_encoded_size_with_cache(jid: &JidRef<'_>, hints: &mut StringHintCache) -> usize {
602 jid_encoded_size_with_cache(&jid.user, jid.server, jid.device, jid.integrator, hints)
603}
604
605#[inline]
606fn validate_nibble(value: &str) -> bool {
607 if value.len() > token::PACKED_MAX as usize {
608 return false;
609 }
610 value
611 .as_bytes()
612 .iter()
613 .all(|&b| b.is_ascii_digit() || b == b'-' || b == b'.')
614}
615
616#[inline]
617fn validate_hex(value: &str) -> bool {
618 if value.len() > token::PACKED_MAX as usize {
619 return false;
620 }
621 value
622 .as_bytes()
623 .iter()
624 .all(|&b| b.is_ascii_digit() || (b'A'..=b'F').contains(&b))
625}
626
627pub struct Encoder<'a, W: ByteWriter> {
628 writer: W,
629 string_hints: Option<&'a StringHintCache>,
630}
631
632impl<W: Write> Encoder<'static, IoByteWriter<W>> {
633 pub fn new(writer: W) -> Result<Self> {
634 let mut enc = Self {
635 writer: IoByteWriter::new(writer),
636 string_hints: None,
637 };
638 enc.write_u8(0)?;
639 Ok(enc)
640 }
641}
642
643impl<'v> Encoder<'static, VecByteWriter<'v>> {
644 pub fn new_vec(buffer: &'v mut Vec<u8>) -> Result<Self> {
645 buffer.clear();
646 let mut enc = Self {
647 writer: VecByteWriter::new(buffer),
648 string_hints: None,
649 };
650 enc.write_u8(0)?;
651 Ok(enc)
652 }
653}
654
655impl<'a> Encoder<'a, SliceByteWriter<'a>> {
656 pub(crate) fn new_slice(
657 buffer: &'a mut [u8],
658 string_hints: Option<&'a StringHintCache>,
659 ) -> Result<Self> {
660 let mut enc = Self {
661 writer: SliceByteWriter::new(buffer),
662 string_hints,
663 };
664 enc.write_u8(0)?;
665 Ok(enc)
666 }
667
668 #[inline]
669 pub(crate) fn bytes_written(&self) -> usize {
670 self.writer.bytes_written()
671 }
672}
673
674impl<'a, W: ByteWriter> Encoder<'a, W> {
675 #[inline(always)]
676 fn write_u8(&mut self, val: u8) -> Result<()> {
677 self.writer.write_u8(val)
678 }
679
680 #[inline(always)]
681 fn write_u16_be(&mut self, val: u16) -> Result<()> {
682 self.writer.write_bytes(&val.to_be_bytes())
683 }
684
685 #[inline(always)]
686 fn write_u32_be(&mut self, val: u32) -> Result<()> {
687 self.writer.write_bytes(&val.to_be_bytes())
688 }
689
690 #[inline(always)]
691 fn write_u20_be(&mut self, value: u32) -> Result<()> {
692 let bytes = [
693 ((value >> 16) & 0x0F) as u8,
694 ((value >> 8) & 0xFF) as u8,
695 (value & 0xFF) as u8,
696 ];
697 self.writer.write_bytes(&bytes)
698 }
699
700 #[inline(always)]
701 fn write_raw_bytes(&mut self, bytes: &[u8]) -> Result<()> {
702 self.writer.write_bytes(bytes)
703 }
704
705 #[inline(always)]
706 pub fn write_bytes_with_len(&mut self, bytes: &[u8]) -> Result<()> {
707 let len = bytes.len();
708 if len < 256 {
709 self.write_u8(token::BINARY_8)?;
710 self.write_u8(len as u8)?;
711 } else if len < (1 << 20) {
712 self.write_u8(token::BINARY_20)?;
713 self.write_u20_be(len as u32)?;
714 } else {
715 self.write_u8(token::BINARY_32)?;
716 self.write_u32_be(len as u32)?;
717 }
718 self.write_raw_bytes(bytes)
719 }
720
721 #[inline(always)]
722 pub fn write_string(&mut self, s: &str) -> Result<()> {
723 if let Some(string_hints) = self.string_hints
724 && let Some(hint) = string_hints.next()
725 {
726 debug_assert_eq!(
727 hint,
728 if s.len() > token::PACKED_MAX as usize {
729 StringHint::RawBytes
730 } else {
731 classify_string_hint(s)
732 },
733 "hint tape misaligned at {s:?}: plan and encode no longer \
734 traverse strings in the same order"
735 );
736 return self.write_string_with_hint(s, hint);
737 }
738 self.write_string_uncached(s)
739 }
740
741 #[inline(always)]
742 fn write_string_uncached(&mut self, s: &str) -> Result<()> {
743 if s.len() > token::PACKED_MAX as usize {
746 return self.write_bytes_with_len(s.as_bytes());
747 }
748 self.write_string_with_hint(s, classify_string_hint(s))
749 }
750
751 #[inline(always)]
752 fn write_string_with_hint(&mut self, s: &str, hint: StringHint) -> Result<()> {
753 match hint {
754 StringHint::Empty => {
755 self.write_u8(token::BINARY_8)?;
756 self.write_u8(0)?;
757 }
758 StringHint::SingleToken(token) => self.write_u8(token)?,
759 StringHint::DoubleToken { dict, token } => {
760 self.write_u8(token::DICTIONARY_0 + dict)?;
761 self.write_u8(token)?;
762 }
763 StringHint::PackedNibble => self.write_packed_bytes(s, token::NIBBLE_8)?,
764 StringHint::PackedHex => self.write_packed_bytes(s, token::HEX_8)?,
765 StringHint::Jid(meta) => self.write_jid_from_meta(s, meta)?,
766 StringHint::RawBytes => self.write_bytes_with_len(s.as_bytes())?,
767 }
768 Ok(())
769 }
770
771 #[inline(always)]
772 fn write_jid_from_meta(&mut self, jid: &str, meta: ParsedJidMeta) -> Result<()> {
773 let (user, server) = split_jid_from_meta(jid, meta);
774 if let Some(device) = meta.device {
775 self.write_u8(token::AD_JID)?;
776 self.write_u8(meta.domain_type)?;
777 self.write_u8(device)?;
778 self.write_string(user)?;
779 } else {
780 self.write_u8(token::JID_PAIR)?;
781 if user.is_empty() {
782 self.write_u8(token::LIST_EMPTY)?;
783 } else {
784 self.write_string(user)?;
785 }
786 self.write_string(server)?;
787 }
788 Ok(())
789 }
790
791 fn write_interop_jid(&mut self, user: &str, device: u16, integrator: u16) -> Result<()> {
810 self.write_u8(token::INTEROP_JID)?;
811 self.write_string(user)?;
812 self.write_u16_be(device)?;
813 self.write_u16_be(integrator)
814 }
815
816 pub fn write_jid_ref(&mut self, jid: &JidRef<'_>) -> Result<()> {
817 if needs_interop_jid(jid.server, jid.integrator) {
818 return self.write_interop_jid(&jid.user, jid.device, jid.integrator);
819 }
820 if jid.device > 0 && server_supports_ad_jid(jid.server) {
821 let device = u8::try_from(jid.device).map_err(|_| {
823 BinaryError::AttrParse(format!("AD_JID device id out of range: {}", jid.device))
824 })?;
825 self.write_u8(token::AD_JID)?;
826 self.write_u8(server_to_domain_type(jid.server))?;
827 self.write_u8(device)?;
828 self.write_string(&jid.user)?;
829 } else {
830 self.write_u8(token::JID_PAIR)?;
832 if jid.user.is_empty() {
833 self.write_u8(token::LIST_EMPTY)?;
834 } else {
835 self.write_string(&jid.user)?;
836 }
837 self.write_string(jid.server.as_str())?;
838 }
839 Ok(())
840 }
841
842 pub fn write_jid_owned(&mut self, jid: &Jid) -> Result<()> {
845 if needs_interop_jid(jid.server, jid.integrator) {
846 return self.write_interop_jid(&jid.user, jid.device, jid.integrator);
847 }
848 if jid.device > 0 && server_supports_ad_jid(jid.server) {
849 let device = u8::try_from(jid.device).map_err(|_| {
851 BinaryError::AttrParse(format!("AD_JID device id out of range: {}", jid.device))
852 })?;
853 self.write_u8(token::AD_JID)?;
854 self.write_u8(server_to_domain_type(jid.server))?;
855 self.write_u8(device)?;
856 self.write_string(&jid.user)?;
857 } else {
858 self.write_u8(token::JID_PAIR)?;
860 if jid.user.is_empty() {
861 self.write_u8(token::LIST_EMPTY)?;
862 } else {
863 self.write_string(&jid.user)?;
864 }
865 self.write_string(jid.server.as_str())?;
866 }
867 Ok(())
868 }
869
870 #[inline(always)]
871 fn pack_nibble(value: u8) -> u8 {
872 match value {
873 b'-' => 10,
874 b'.' => 11,
875 0 => 15,
876 c if c.is_ascii_digit() => c - b'0',
877 _ => panic!("Invalid char for nibble packing: {value}"),
878 }
879 }
880
881 #[inline(always)]
882 fn pack_hex(value: u8) -> u8 {
883 match value {
884 c if c.is_ascii_digit() => c - b'0',
885 c if (b'A'..=b'F').contains(&c) => 10 + (c - b'A'),
886 0 => 15,
887 _ => panic!("Invalid char for hex packing: {value}"),
888 }
889 }
890
891 #[inline(always)]
892 fn pack_byte_pair(packer: fn(u8) -> u8, part1: u8, part2: u8) -> u8 {
893 (packer(part1) << 4) | packer(part2)
894 }
895
896 fn write_packed_bytes(&mut self, value: &str, data_type: u8) -> Result<()> {
897 if value.len() > token::PACKED_MAX as usize {
898 panic!("String too long to be packed: {}", value.len());
899 }
900
901 self.write_u8(data_type)?;
902
903 let mut rounded_len = value.len().div_ceil(2) as u8;
904 if !value.len().is_multiple_of(2) {
905 rounded_len |= 0x80;
906 }
907 self.write_u8(rounded_len)?;
908
909 #[allow(unused_mut)]
910 let mut input_bytes = value.as_bytes();
911
912 if data_type == token::NIBBLE_8 {
913 #[cfg(feature = "simd")]
914 {
915 const NIBBLE_LOOKUP: [u8; 16] =
916 [10, 11, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255];
917 let lookup = Simd::from_array(NIBBLE_LOOKUP);
918 let nibble_base = Simd::splat(b'-');
919
920 while input_bytes.len() >= 16 {
921 let (chunk, rest) = input_bytes.split_at(16);
922 let input = u8x16::from_slice(chunk);
923 let indices = input.saturating_sub(nibble_base);
924 let nibbles = lookup.swizzle_dyn(indices);
925
926 let (evens, odds) = nibbles.deinterleave(nibbles.rotate_elements_left::<1>());
927 let packed: Simd<u8, 16> = (evens << Simd::splat(4)) | odds;
928 let packed_bytes = packed.to_array();
929 self.write_raw_bytes(&packed_bytes[..8])?;
930
931 input_bytes = rest;
932 }
933 }
934
935 let mut bytes_iter = input_bytes.iter().copied();
936 while let Some(part1) = bytes_iter.next() {
937 let part2 = bytes_iter.next().unwrap_or(0);
938 self.write_u8(Self::pack_byte_pair(Self::pack_nibble, part1, part2))?;
939 }
940 } else {
941 #[cfg(feature = "simd")]
942 {
943 let ascii_0 = Simd::splat(b'0');
944 let ascii_a = Simd::splat(b'A');
945 let ten = Simd::splat(10);
946
947 while input_bytes.len() >= 16 {
948 let (chunk, rest) = input_bytes.split_at(16);
949 let input = u8x16::from_slice(chunk);
950
951 let digit_vals = input - ascii_0;
952 let letter_vals = input - ascii_a + ten;
953 let is_letter = input.simd_ge(ascii_a);
954 let nibbles = is_letter.select(letter_vals, digit_vals);
955
956 let (evens, odds) = nibbles.deinterleave(nibbles.rotate_elements_left::<1>());
957 let packed: Simd<u8, 16> = (evens << Simd::splat(4)) | odds;
958 let packed_bytes = packed.to_array();
959 self.write_raw_bytes(&packed_bytes[..8])?;
960
961 input_bytes = rest;
962 }
963 }
964
965 let mut bytes_iter = input_bytes.iter().copied();
966 while let Some(part1) = bytes_iter.next() {
967 let part2 = bytes_iter.next().unwrap_or(0);
968 self.write_u8(Self::pack_byte_pair(Self::pack_hex, part1, part2))?;
969 }
970 }
971 Ok(())
972 }
973
974 pub fn write_list_start(&mut self, len: usize) -> Result<()> {
975 if len == 0 {
976 self.write_u8(token::LIST_EMPTY)?;
977 } else if len < 256 {
978 self.write_u8(248)?;
979 self.write_u8(len as u8)?;
980 } else if len <= u16::MAX as usize {
981 self.write_u8(249)?;
982 self.write_u16_be(len as u16)?;
983 } else {
984 return Err(BinaryError::InvalidNode);
985 }
986 Ok(())
987 }
988
989 pub fn write_node<N: EncodeNode>(&mut self, node: &N) -> Result<()> {
991 let content_len = if node.has_content() { 1 } else { 0 };
992 let list_len = 1 + (node.attrs_len() * 2) + content_len;
993
994 self.write_list_start(list_len)?;
995 self.write_string(node.tag())?;
996 node.encode_attrs(self)?;
997 node.encode_content(self)?;
998 Ok(())
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use super::*;
1005 use crate::builder::NodeBuilder;
1006 use crate::node::Attrs;
1007 use std::io::Cursor;
1008
1009 type TestResult = Result<()>;
1010
1011 #[test]
1012 fn test_encode_node() -> TestResult {
1013 let node = Node::new(
1014 "message",
1015 Attrs::new(),
1016 Some(NodeContent::String("receipt".into())),
1017 );
1018
1019 let mut buffer = Vec::new();
1020 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1021 encoder.write_node(&node)?;
1022
1023 let expected = vec![0, 248, 2, 19, 7];
1024 assert_eq!(buffer, expected);
1025 assert_eq!(buffer.len(), 5);
1026 Ok(())
1027 }
1028
1029 #[test]
1030 fn test_nibble_packing() -> TestResult {
1031 let test_str = "-.0123456789";
1033 let node = Node::new(
1034 "test",
1035 Attrs::new(),
1036 Some(NodeContent::String(test_str.into())),
1037 );
1038
1039 let mut buffer = Vec::new();
1040 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1041 encoder.write_node(&node)?;
1042
1043 let expected = vec![
1044 0, 248, 2, 252, 4, 116, 101, 115, 116, 255, 6, 171, 1, 35, 69, 103, 137,
1045 ];
1046 assert_eq!(buffer, expected);
1047 assert_eq!(buffer.len(), 17);
1048 Ok(())
1049 }
1050
1051 #[test]
1053 fn test_list_size_list8_boundary() -> TestResult {
1054 let mut buffer = Vec::new();
1055 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1056
1057 encoder.write_list_start(255)?;
1059
1060 assert_eq!(buffer[1], token::LIST_8);
1062 assert_eq!(buffer[2], 255);
1063 Ok(())
1064 }
1065
1066 #[test]
1068 fn test_list_size_list16_boundary() -> TestResult {
1069 let mut buffer = Vec::new();
1070 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1071
1072 encoder.write_list_start(256)?;
1074
1075 assert_eq!(buffer[1], token::LIST_16);
1077 assert_eq!(buffer[2], 0x01); assert_eq!(buffer[3], 0x00); Ok(())
1080 }
1081
1082 #[test]
1084 fn test_list_size_empty() -> TestResult {
1085 let mut buffer = Vec::new();
1086 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1087
1088 encoder.write_list_start(0)?;
1089
1090 assert_eq!(buffer[1], token::LIST_EMPTY);
1092 Ok(())
1093 }
1094
1095 #[test]
1097 fn test_hex_validation() {
1098 assert!(validate_hex("0123456789ABCDEF"));
1100 assert!(validate_hex("DEADBEEF"));
1101 assert!(validate_hex("1234"));
1102
1103 assert!(!validate_hex("abcdef"));
1105 assert!(!validate_hex("DeadBeef"));
1106
1107 assert!(!validate_hex("-"));
1109 assert!(!validate_hex("."));
1110 assert!(!validate_hex(" "));
1111
1112 assert!(validate_hex(""));
1114 }
1115
1116 #[test]
1118 fn test_nibble_validation() {
1119 assert!(validate_nibble("0123456789"));
1121 assert!(validate_nibble("-"));
1122 assert!(validate_nibble("."));
1123 assert!(validate_nibble("123-456.789"));
1124
1125 assert!(!validate_nibble("abc"));
1127 assert!(!validate_nibble("123abc"));
1128
1129 assert!(!validate_nibble("ABC"));
1131
1132 assert!(!validate_nibble("123!456"));
1134 assert!(!validate_nibble("@"));
1135 }
1136
1137 #[test]
1139 fn test_binary_length_boundaries() -> TestResult {
1140 let short_data = vec![0x42; 255];
1142 let mut buffer = Vec::new();
1143 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1144 encoder.write_bytes_with_len(&short_data)?;
1145 assert_eq!(buffer[1], token::BINARY_8);
1146 assert_eq!(buffer[2], 255);
1147
1148 let medium_data = vec![0x42; 256];
1150 let mut buffer = Vec::new();
1151 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1152 encoder.write_bytes_with_len(&medium_data)?;
1153 assert_eq!(buffer[1], token::BINARY_20);
1154 assert_eq!(buffer[2], 0x00);
1156 assert_eq!(buffer[3], 0x01);
1157 assert_eq!(buffer[4], 0x00);
1158
1159 Ok(())
1160 }
1161
1162 #[test]
1164 fn test_node_with_255_children() -> TestResult {
1165 let children: Vec<Node> = (0..255)
1166 .map(|_| Node::new("child", Attrs::new(), None))
1167 .collect();
1168
1169 let parent = Node::new("parent", Attrs::new(), Some(NodeContent::Nodes(children)));
1170
1171 let mut buffer = Vec::new();
1172 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1173 encoder.write_node(&parent)?;
1174
1175 assert!(!buffer.is_empty());
1177 Ok(())
1178 }
1179
1180 #[test]
1182 fn test_node_with_256_children() -> TestResult {
1183 let children: Vec<Node> = (0..256)
1184 .map(|_| Node::new("x", Attrs::new(), None))
1185 .collect();
1186
1187 let parent = Node::new("parent", Attrs::new(), Some(NodeContent::Nodes(children)));
1188
1189 let mut buffer = Vec::new();
1190 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1191 encoder.write_node(&parent)?;
1192
1193 assert!(!buffer.is_empty());
1195 Ok(())
1196 }
1197
1198 #[test]
1200 fn test_packed_max_boundary() {
1201 let max_nibble = "0".repeat(token::PACKED_MAX as usize);
1203 assert!(validate_nibble(&max_nibble));
1204
1205 let over_max = "0".repeat(token::PACKED_MAX as usize + 1);
1207 assert!(!validate_nibble(&over_max));
1208 }
1209
1210 #[test]
1212 fn test_empty_string_encoding() -> TestResult {
1213 let mut buffer = Vec::new();
1214 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1215 encoder.write_string("")?;
1216
1217 println!("Empty string encoding: {:?}", &buffer[1..]);
1221 assert_eq!(
1222 buffer.len(),
1223 3,
1224 "Empty string should encode to 2 bytes (plus leading 0)"
1225 );
1226 assert_eq!(
1227 buffer[1],
1228 token::BINARY_8,
1229 "First byte should be BINARY_8 (252)"
1230 );
1231 assert_eq!(buffer[2], 0, "Second byte should be 0 (length)");
1232 Ok(())
1233 }
1234
1235 #[test]
1237 fn test_empty_string_roundtrip() -> TestResult {
1238 use crate::decoder::Decoder;
1239
1240 let mut attrs = Attrs::new();
1241 attrs.insert("key", ""); attrs.insert("", "value"); let node = Node::new("test", attrs, Some(NodeContent::String("".into())));
1245
1246 let mut buffer = Vec::new();
1247 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1248 encoder.write_node(&node)?;
1249
1250 let mut decoder = Decoder::new(&buffer[1..]);
1251 let decoded = decoder.read_node_ref()?.to_owned();
1252
1253 assert_eq!(decoded.tag, "test");
1254 assert_eq!(
1255 decoded.attrs.get("key"),
1256 Some(&NodeValue::String("".into()))
1257 );
1258 assert_eq!(
1259 decoded.attrs.get(""),
1260 Some(&NodeValue::String("value".into()))
1261 );
1262
1263 match &decoded.content {
1265 Some(NodeContent::Bytes(b)) => assert!(b.is_empty(), "Content should be empty bytes"),
1266 other => panic!("Expected empty bytes, got {:?}", other),
1267 }
1268 Ok(())
1269 }
1270
1271 #[test]
1274 fn test_jid_length_heuristic() -> TestResult {
1275 use crate::decoder::Decoder;
1276 use crate::token;
1277
1278 let short_jid = "user@s.whatsapp.net";
1280 let mut buffer = Vec::new();
1281 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1282 encoder.write_string(short_jid)?;
1283
1284 assert_eq!(
1286 buffer[1],
1287 token::JID_PAIR,
1288 "Short JID should be encoded as JID_PAIR token"
1289 );
1290
1291 let long_text = "x".repeat(300) + "@s.whatsapp.net";
1293 let mut buffer = Vec::new();
1294 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1295 encoder.write_string(&long_text)?;
1296
1297 assert_eq!(
1299 buffer[1],
1300 token::BINARY_20,
1301 "Long string should be encoded as BINARY_20, not as JID"
1302 );
1303
1304 let node = Node::new(
1306 "msg",
1307 Attrs::new(),
1308 Some(NodeContent::String(long_text.as_str().into())),
1309 );
1310 let mut buffer = Vec::new();
1311 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1312 encoder.write_node(&node)?;
1313
1314 let mut decoder = Decoder::new(&buffer[1..]);
1315 let decoded = decoder.read_node_ref()?.to_owned();
1316 match &decoded.content {
1317 Some(NodeContent::Bytes(b)) => {
1318 assert_eq!(
1319 String::from_utf8_lossy(b),
1320 long_text,
1321 "Long string should round-trip correctly"
1322 );
1323 }
1324 other => panic!("Expected bytes content, got {:?}", other),
1325 }
1326
1327 Ok(())
1328 }
1329
1330 #[test]
1331 fn test_jid_parser_preserves_non_numeric_device_suffix() -> TestResult {
1332 use crate::decoder::Decoder;
1333
1334 let value = "foo:bar@s.whatsapp.net";
1335 let node = Node::new("msg", Attrs::new(), Some(NodeContent::String(value.into())));
1336
1337 let mut buffer = Vec::new();
1338 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1339 encoder.write_node(&node)?;
1340
1341 let mut decoder = Decoder::new(&buffer[1..]);
1342 let decoded = decoder.read_node_ref()?.to_owned();
1343 match decoded.content {
1344 Some(NodeContent::String(s)) => assert_eq!(s, value),
1345 other => panic!("Expected string content, got {:?}", other),
1346 }
1347 Ok(())
1348 }
1349
1350 #[test]
1351 fn test_jid_parser_preserves_non_numeric_agent_suffix() -> TestResult {
1352 use crate::decoder::Decoder;
1353
1354 let value = "hello_world@s.whatsapp.net";
1355 let node = Node::new("msg", Attrs::new(), Some(NodeContent::String(value.into())));
1356
1357 let mut buffer = Vec::new();
1358 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1359 encoder.write_node(&node)?;
1360
1361 let mut decoder = Decoder::new(&buffer[1..]);
1362 let decoded = decoder.read_node_ref()?.to_owned();
1363 match decoded.content {
1364 Some(NodeContent::String(s)) => assert_eq!(s, value),
1365 other => panic!("Expected string content, got {:?}", other),
1366 }
1367 Ok(())
1368 }
1369
1370 #[test]
1381 fn test_ad_jid_domain_type_lid() -> TestResult {
1382 let lid_jid = Jid::lid_device("236395184570386", 39);
1384 let node = NodeBuilder::new("to").attr("jid", lid_jid).build();
1385
1386 let mut buffer = Vec::new();
1387 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1388 encoder.write_node(&node)?;
1389
1390 let ad_jid_pos = buffer
1392 .iter()
1393 .position(|&b| b == token::AD_JID)
1394 .expect("AD_JID token (0xF7) must be present for device JID");
1395
1396 let domain_type = buffer[ad_jid_pos + 1];
1398 assert_eq!(
1399 domain_type, 1,
1400 "LID JID must encode domain_type=1 (lid), got {domain_type} (0=whatsapp, 128=hosted)"
1401 );
1402
1403 let device = buffer[ad_jid_pos + 2];
1405 assert_eq!(device, 39, "Device byte must be 39");
1406
1407 Ok(())
1408 }
1409
1410 #[test]
1411 fn test_ad_jid_domain_type_whatsapp() -> TestResult {
1412 let pn_jid = Jid::pn_device("551199887766", 33);
1413 let node = NodeBuilder::new("to").attr("jid", pn_jid).build();
1414
1415 let mut buffer = Vec::new();
1416 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1417 encoder.write_node(&node)?;
1418
1419 let ad_jid_pos = buffer
1420 .iter()
1421 .position(|&b| b == token::AD_JID)
1422 .expect("AD_JID token must be present for device JID");
1423
1424 let domain_type = buffer[ad_jid_pos + 1];
1425 assert_eq!(
1426 domain_type, 0,
1427 "s.whatsapp.net JID must encode domain_type=0, got {domain_type}"
1428 );
1429
1430 Ok(())
1431 }
1432
1433 #[test]
1434 fn test_ad_jid_domain_type_whatsapp_ignores_hidden_agent() -> TestResult {
1435 use crate::decoder::Decoder;
1436
1437 let mut pn_jid = Jid::pn_device("551199887766", 33);
1438 pn_jid.agent = 2;
1439 let node = NodeBuilder::new("to").attr("jid", pn_jid.clone()).build();
1440
1441 let mut buffer = Vec::new();
1442 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1443 encoder.write_node(&node)?;
1444
1445 let ad_jid_pos = buffer
1446 .iter()
1447 .position(|&b| b == token::AD_JID)
1448 .expect("AD_JID token must be present for device JID");
1449
1450 assert_eq!(
1451 buffer[ad_jid_pos + 1],
1452 0,
1453 "PN JID must encode the WA Web domain_type=0 even if a hidden agent is present"
1454 );
1455
1456 let decoded = Decoder::new(&buffer[1..]).read_node_ref()?.to_owned();
1457 let decoded_jid = decoded
1458 .attrs()
1459 .optional_jid("jid")
1460 .expect("jid attr must decode");
1461 assert!(pn_jid.is_same_chat_as(&decoded_jid));
1462 assert_eq!(decoded_jid.agent, 0);
1463
1464 Ok(())
1465 }
1466
1467 #[test]
1472 fn test_jid_string_vs_direct_encoding_matches() -> TestResult {
1473 use crate::decoder::Decoder;
1474
1475 let test_cases: Vec<Jid> = vec![
1476 Jid::lid_device("236395184570386", 39), Jid::pn_device("551199887766", 33), Jid::lid("236395184570386"), Jid::pn("551199887766"), "5511999887766:99@hosted".parse().unwrap(), "100000012345678:99@hosted.lid".parse().unwrap(), ];
1483
1484 for jid in test_cases {
1485 let node_str = NodeBuilder::new("to").attr("jid", jid.to_string()).build();
1487
1488 let node_jid = NodeBuilder::new("to").attr("jid", jid.clone()).build();
1490
1491 let mut buf_str = Vec::new();
1492 Encoder::new(Cursor::new(&mut buf_str))?.write_node(&node_str)?;
1493
1494 let mut buf_jid = Vec::new();
1495 Encoder::new(Cursor::new(&mut buf_jid))?.write_node(&node_jid)?;
1496
1497 assert_eq!(
1498 buf_str, buf_jid,
1499 "String vs direct Jid encoding must produce identical bytes for {jid}"
1500 );
1501
1502 let mut decoder = Decoder::new(&buf_jid[1..]);
1505 let decoded_node = decoder.read_node_ref()?.to_owned();
1506 let decoded_jid: Jid = decoded_node
1507 .attrs()
1508 .optional_jid("jid")
1509 .expect("jid attr must round-trip as JID");
1510
1511 assert_eq!(
1512 jid.user, decoded_jid.user,
1513 "Round-trip user mismatch for {jid}"
1514 );
1515 assert_eq!(
1516 jid.device, decoded_jid.device,
1517 "Round-trip device mismatch for {jid}"
1518 );
1519 assert_eq!(
1520 jid.server, decoded_jid.server,
1521 "Round-trip server mismatch for {jid}"
1522 );
1523 }
1524
1525 Ok(())
1526 }
1527
1528 #[test]
1531 fn test_direct_constructed_hosted_encodes_correct_domain_type() -> TestResult {
1532 let mut hosted = Jid::new("100000000000001", jid::Server::Hosted);
1533 hosted.device = 99;
1534 assert_eq!(
1535 hosted.agent, 0,
1536 "default agent for direct construction is 0"
1537 );
1538
1539 let mut hosted_lid = Jid::new("100000000000002", jid::Server::HostedLid);
1540 hosted_lid.device = 99;
1541 assert_eq!(hosted_lid.agent, 0);
1542
1543 for (jid, expected) in [(&hosted, 128u8), (&hosted_lid, 129u8)] {
1544 let node = NodeBuilder::new("to").attr("jid", jid.clone()).build();
1545 let mut buf = Vec::new();
1546 Encoder::new(Cursor::new(&mut buf))?.write_node(&node)?;
1547
1548 let pos = buf
1549 .iter()
1550 .position(|&b| b == token::AD_JID)
1551 .expect("AD_JID marker present");
1552 assert_eq!(
1553 buf[pos + 1],
1554 expected,
1555 "direct-constructed {jid} must emit domain_type {expected} \
1556 (pre-#391 would have emitted agent=0)"
1557 );
1558 }
1559 Ok(())
1560 }
1561
1562 #[test]
1566 fn test_long_string_skips_classification() -> TestResult {
1567 use crate::decoder::Decoder;
1568 use crate::marshal::marshal;
1569
1570 let at_boundary = "0".repeat(token::PACKED_MAX as usize); let over_boundary = "0".repeat(token::PACKED_MAX as usize + 1); let node_at = Node::new(
1575 "test",
1576 Attrs::new(),
1577 Some(NodeContent::String(at_boundary.as_str().into())),
1578 );
1579 let encoded_at = marshal(&node_at)?;
1580
1581 let node_over = Node::new(
1583 "test",
1584 Attrs::new(),
1585 Some(NodeContent::String(over_boundary.as_str().into())),
1586 );
1587 let encoded_over = marshal(&node_over)?;
1588
1589 assert!(
1591 encoded_at.len() < encoded_over.len(),
1592 "127-char nibble string should pack smaller than 128-char raw: {} vs {}",
1593 encoded_at.len(),
1594 encoded_over.len(),
1595 );
1596
1597 let has_raw_128 = encoded_over
1600 .windows(2)
1601 .any(|w| w[0] == token::BINARY_8 && w[1] == 128);
1602 assert!(
1603 has_raw_128,
1604 "128-char string must contain BINARY_8 + length=128 sequence"
1605 );
1606
1607 let decoded_at = Decoder::new(&encoded_at[1..]).read_node_ref()?.to_owned();
1609 let decoded_over = Decoder::new(&encoded_over[1..]).read_node_ref()?.to_owned();
1610
1611 match &decoded_at.content {
1612 Some(NodeContent::String(s)) => assert_eq!(s.as_str(), at_boundary),
1613 Some(NodeContent::Bytes(b)) => {
1614 assert_eq!(std::str::from_utf8(b).unwrap(), at_boundary)
1615 }
1616 other => panic!("Expected string/bytes content, got {:?}", other),
1617 }
1618 match &decoded_over.content {
1619 Some(NodeContent::Bytes(b)) => {
1620 assert_eq!(std::str::from_utf8(b).unwrap(), over_boundary)
1621 }
1622 other => panic!(
1623 "Expected bytes content for 128-char string, got {:?}",
1624 other
1625 ),
1626 }
1627
1628 Ok(())
1629 }
1630
1631 #[test]
1637 fn test_bot_jid_with_device_round_trips_via_jid_pair() -> TestResult {
1638 use crate::decoder::Decoder;
1639
1640 for value in [
1641 "867051314767696@bot",
1642 "867051314767696:0@bot",
1643 "120363021033254949@g.us",
1644 "12345@broadcast",
1645 "12345@newsletter",
1646 ] {
1647 let node = NodeBuilder::new("msg").attr("from", value).build();
1648
1649 let mut buffer = Vec::new();
1650 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1651 encoder.write_node(&node)?;
1652
1653 assert!(
1656 !buffer.contains(&token::AD_JID),
1657 "AD_JID must not be emitted for {value} (would lose the server)"
1658 );
1659
1660 let decoded = Decoder::new(&buffer[1..]).read_node_ref()?.to_owned();
1661 let from_attr = decoded
1662 .attrs
1663 .get("from")
1664 .expect("from attr must survive the round-trip");
1665 let got = from_attr.to_string();
1666 let expected_user_server = value.split(':').next().unwrap_or(value);
1669 let expected_server = value.split('@').nth(1).unwrap();
1670 assert!(
1671 got.ends_with(&format!("@{expected_server}")),
1672 "round-trip lost the server for {value}: got {got}",
1673 );
1674 assert!(
1675 got.starts_with(expected_user_server.split('@').next().unwrap())
1676 || got.starts_with(value.split('@').next().unwrap()),
1677 "round-trip lost the user for {value}: got {got}",
1678 );
1679 }
1680 Ok(())
1681 }
1682
1683 #[test]
1685 fn test_call_jid_round_trips_via_jid_pair() -> TestResult {
1686 use crate::decoder::Decoder;
1687
1688 let node = NodeBuilder::new("call").attr("from", "12345@call").build();
1689 let mut buffer = Vec::new();
1690 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1691 encoder.write_node(&node)?;
1692 assert!(
1693 !buffer.contains(&token::AD_JID),
1694 "AD_JID must not be emitted for @call (would lose the server)"
1695 );
1696
1697 let decoded = Decoder::new(&buffer[1..]).read_node_ref()?.to_owned();
1698 let from = decoded
1699 .attrs
1700 .get("from")
1701 .expect("from attr must survive the round-trip");
1702 assert_eq!(from.to_string(), "12345@call");
1703 Ok(())
1704 }
1705
1706 #[test]
1711 fn test_typed_non_ad_jid_with_device_round_trips_via_jid_pair() -> TestResult {
1712 use crate::decoder::Decoder;
1713 use std::str::FromStr;
1714
1715 for value in [
1716 "867051314767696:0@bot",
1720 "12345:5@broadcast",
1721 "67890:9@newsletter",
1722 ] {
1723 let jid = Jid::from_str(value)?;
1724 let node = NodeBuilder::new("msg").attr("from", jid.clone()).build();
1725
1726 let mut buffer = Vec::new();
1727 let mut encoder = Encoder::new(Cursor::new(&mut buffer))?;
1728 encoder.write_node(&node)?;
1729
1730 assert!(
1731 !buffer.contains(&token::AD_JID),
1732 "typed JID {value} must NOT emit AD_JID (decoder would drop the server)"
1733 );
1734
1735 let decoded = Decoder::new(&buffer[1..]).read_node_ref()?.to_owned();
1736 let from = decoded
1737 .attrs
1738 .get("from")
1739 .expect("from attr must survive round-trip")
1740 .to_jid()
1741 .expect("from attr decodes back to a Jid");
1742 assert_eq!(
1743 from.server, jid.server,
1744 "round-trip lost the server for typed {value}"
1745 );
1746 assert_eq!(
1747 from.user, jid.user,
1748 "round-trip lost the user for typed {value}"
1749 );
1750 }
1751 Ok(())
1752 }
1753}