1use std::collections::{HashMap, VecDeque};
15use std::sync::atomic::{AtomicU64, Ordering};
16
17use thiserror::Error;
18
19pub const ENCODING: &str = "utf-8";
25
26pub const DEFAULT_B64_LINE_LENGTH: usize = 65536;
28
29pub const GW_PICKLE_PROTOCOL: u8 = 4;
42
43pub const SNAME_WRITER: &str = "ch-writer";
46pub const SNAME_READER: &str = "ch-reader";
48pub const SNAME_ERROR: &str = "ch-error";
50
51const IDENT_GEN: &str = "GEN";
53const IDENT_CFG: &str = "CFG";
54const IDENT_CTL: &str = "CTL";
55const IDENT_ACK: &str = "ACK";
56const IDENT_ERR: &str = "ERR";
57const IDENT_OUT: &str = "OUT";
58const IDENT_SER: &str = "SER";
59const IDENT_RET: &str = "RET";
60const IDENT_TIM: &str = "TIM";
61const IDENT_RTR: &str = "RTR";
62const IDENT_CHA: &str = "CHA";
63const IDENT_END: &str = "END";
64
65#[derive(Error, Debug)]
71pub enum MessageProcessingError {
72 #[error("Unknown message type {0}")]
75 UnknownType(String),
76
77 #[error("Unknown message with no type")]
80 NoType,
81
82 #[error("Invalid message attributes: missing key \"{0}\"")]
84 MissingAttribute(String),
85
86 #[error("Got unexpected payload for Message {0}")]
88 UnexpectedPayload(String),
89
90 #[error("unexpected message: {0}")]
93 UnexpectedMessage(String),
94
95 #[error("Message {0} has an invalid payload")]
97 InvalidPayload(String),
98
99 #[error("Message {0} has an invalid payload ({1})")]
103 InvalidPayloadDetail(String, String),
104
105 #[error("Parse error: {0}")]
107 ParseError(String),
108
109 #[error("Invalid starting tag {0}")]
111 InvalidTag(String),
112}
113
114static MSG_COUNTER: AtomicU64 = AtomicU64::new(0);
119
120fn next_msg_id() -> u64 {
121 MSG_COUNTER.fetch_add(1, Ordering::Relaxed)
122}
123
124#[cfg(test)]
126fn reset_msg_counter() {
127 MSG_COUNTER.store(0, Ordering::Relaxed);
128}
129
130const B64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
136
137pub fn base64_encode(input: &[u8]) -> String {
139 let mut out = String::with_capacity((input.len() + 2) / 3 * 4);
140 for chunk in input.chunks(3) {
141 let b0 = chunk[0] as u32;
142 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
143 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
144 let triple = (b0 << 16) | (b1 << 8) | b2;
145
146 out.push(B64_CHARS[((triple >> 18) & 0x3F) as usize] as char);
147 out.push(B64_CHARS[((triple >> 12) & 0x3F) as usize] as char);
148 if chunk.len() > 1 {
149 out.push(B64_CHARS[((triple >> 6) & 0x3F) as usize] as char);
150 } else {
151 out.push('=');
152 }
153 if chunk.len() > 2 {
154 out.push(B64_CHARS[(triple & 0x3F) as usize] as char);
155 } else {
156 out.push('=');
157 }
158 }
159 out
160}
161
162pub fn base64_decode(input: &str) -> Result<Vec<u8>, MessageProcessingError> {
168 base64_decode_reason(input).map_err(MessageProcessingError::InvalidPayload)
169}
170
171fn base64_decode_reason(input: &str) -> Result<Vec<u8>, String> {
175 fn b64_val(c: u8) -> Result<u8, String> {
176 match c {
177 b'A'..=b'Z' => Ok(c - b'A'),
178 b'a'..=b'z' => Ok(c - b'a' + 26),
179 b'0'..=b'9' => Ok(c - b'0' + 52),
180 b'+' => Ok(62),
181 b'/' => Ok(63),
182 _ => Err("invalid base64 character".to_string()),
183 }
184 }
185
186 let clean: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
188 if clean.is_empty() {
189 return Ok(Vec::new());
190 }
191
192 if !clean.len().is_multiple_of(4) {
195 return Err("Incorrect padding".to_string());
196 }
197 if let Some(pos) = clean.iter().position(|&b| b == b'=') {
198 if clean.len() - pos > 2 || clean[pos..].iter().any(|&b| b != b'=') {
199 return Err("Invalid padding".to_string());
200 }
201 }
202
203 let mut out = Vec::with_capacity(clean.len() * 3 / 4);
204 for chunk in clean.chunks(4) {
205 if chunk.len() < 2 {
206 return Err("truncated base64".to_string());
207 }
208 let a = b64_val(chunk[0])?;
209 let b = b64_val(chunk[1])?;
210 out.push((a << 2) | (b >> 4));
211 if chunk.len() > 2 && chunk[2] != b'=' {
212 let c = b64_val(chunk[2])?;
213 out.push((b << 4) | (c >> 2));
214 if chunk.len() > 3 && chunk[3] != b'=' {
215 let d = b64_val(chunk[3])?;
216 out.push((c << 6) | d);
217 }
218 }
219 }
220 Ok(out)
221}
222
223pub fn validate_payload_protocol(ident: &str, raw: &[u8]) -> Result<(), MessageProcessingError> {
234 if raw.len() >= 2 && raw[0] == 0x80 && raw[1] > GW_PICKLE_PROTOCOL {
235 return Err(MessageProcessingError::InvalidPayloadDetail(
236 ident.to_string(),
237 format!("unsupported pickle protocol: {}", raw[1]),
238 ));
239 }
240 Ok(())
241}
242
243fn xml_escape(s: &str) -> String {
248 let mut out = String::with_capacity(s.len());
249 for c in s.chars() {
250 match c {
251 '&' => out.push_str("&"),
252 '<' => out.push_str("<"),
253 '>' => out.push_str(">"),
254 '"' => out.push_str("""),
255 '\'' => out.push_str("'"),
256 _ => out.push(c),
257 }
258 }
259 out
260}
261
262fn xml_unescape(s: &str) -> String {
263 s.replace("&", "&")
264 .replace("<", "<")
265 .replace(">", ">")
266 .replace(""", "\"")
267 .replace("'", "'")
268}
269
270#[derive(Debug, Clone)]
280pub enum Message {
281 General { msgid: u64, data: Option<Vec<u8>> },
283
284 Configuration {
287 msgid: u64,
288 gateway: String,
289 data: Option<Vec<u8>>,
290 },
291
292 Control {
295 msgid: u64,
296 srcid: u64,
297 action: String,
298 target: String,
299 data: Option<Vec<u8>>,
300 },
301
302 Ack { msgid: u64, ack: u64 },
304
305 Error { msgid: u64, reason: String },
307
308 StdOut {
311 msgid: u64,
312 srcid: u64,
313 nodes: String,
314 data: Option<Vec<u8>>,
315 },
316
317 StdErr {
320 msgid: u64,
321 srcid: u64,
322 nodes: String,
323 data: Option<Vec<u8>>,
324 },
325
326 Retcode {
328 msgid: u64,
329 srcid: u64,
330 retcode: i32,
331 nodes: String,
332 },
333
334 Timeout {
336 msgid: u64,
337 srcid: u64,
338 nodes: String,
339 },
340
341 Routing {
343 msgid: u64,
344 srcid: u64,
345 event: String,
346 gateway: String,
347 targets: String,
348 },
349
350 Start { msgid: u64 },
352
353 End { msgid: u64 },
355}
356
357impl Message {
358 pub fn general() -> Self {
362 Message::General {
363 msgid: next_msg_id(),
364 data: None,
365 }
366 }
367
368 pub fn configuration(gateway: &str) -> Self {
370 Message::Configuration {
371 msgid: next_msg_id(),
372 gateway: gateway.to_string(),
373 data: None,
374 }
375 }
376
377 pub fn control(srcid: u64) -> Self {
379 Message::Control {
380 msgid: next_msg_id(),
381 srcid,
382 action: String::new(),
383 target: String::new(),
384 data: None,
385 }
386 }
387
388 pub fn ack(ackid: u64) -> Self {
390 Message::Ack {
391 msgid: next_msg_id(),
392 ack: ackid,
393 }
394 }
395
396 pub fn error(reason: &str) -> Self {
398 Message::Error {
399 msgid: next_msg_id(),
400 reason: reason.to_string(),
401 }
402 }
403
404 pub fn stdout(nodes: &str, srcid: u64) -> Self {
406 Message::StdOut {
407 msgid: next_msg_id(),
408 srcid,
409 nodes: nodes.to_string(),
410 data: None,
411 }
412 }
413
414 pub fn stderr(nodes: &str, srcid: u64) -> Self {
416 Message::StdErr {
417 msgid: next_msg_id(),
418 srcid,
419 nodes: nodes.to_string(),
420 data: None,
421 }
422 }
423
424 pub fn retcode(nodes: &str, retcode: i32, srcid: u64) -> Self {
426 Message::Retcode {
427 msgid: next_msg_id(),
428 srcid,
429 retcode,
430 nodes: nodes.to_string(),
431 }
432 }
433
434 pub fn timeout(nodes: &str, srcid: u64) -> Self {
436 Message::Timeout {
437 msgid: next_msg_id(),
438 srcid,
439 nodes: nodes.to_string(),
440 }
441 }
442
443 pub fn routing(event: &str, gateway: &str, targets: &str, srcid: u64) -> Self {
445 Message::Routing {
446 msgid: next_msg_id(),
447 srcid,
448 event: event.to_string(),
449 gateway: gateway.to_string(),
450 targets: targets.to_string(),
451 }
452 }
453
454 pub fn start() -> Self {
456 Message::Start {
457 msgid: next_msg_id(),
458 }
459 }
460
461 pub fn end() -> Self {
463 Message::End {
464 msgid: next_msg_id(),
465 }
466 }
467
468 pub fn ident(&self) -> &str {
472 match self {
473 Message::General { .. } => IDENT_GEN,
474 Message::Configuration { .. } => IDENT_CFG,
475 Message::Control { .. } => IDENT_CTL,
476 Message::Ack { .. } => IDENT_ACK,
477 Message::Error { .. } => IDENT_ERR,
478 Message::StdOut { .. } => IDENT_OUT,
479 Message::StdErr { .. } => IDENT_SER,
480 Message::Retcode { .. } => IDENT_RET,
481 Message::Timeout { .. } => IDENT_TIM,
482 Message::Routing { .. } => IDENT_RTR,
483 Message::Start { .. } => IDENT_CHA,
484 Message::End { .. } => IDENT_END,
485 }
486 }
487
488 pub fn msgid(&self) -> u64 {
490 match self {
491 Message::General { msgid, .. }
492 | Message::Configuration { msgid, .. }
493 | Message::Control { msgid, .. }
494 | Message::Ack { msgid, .. }
495 | Message::Error { msgid, .. }
496 | Message::StdOut { msgid, .. }
497 | Message::StdErr { msgid, .. }
498 | Message::Retcode { msgid, .. }
499 | Message::Timeout { msgid, .. }
500 | Message::Routing { msgid, .. }
501 | Message::Start { msgid, .. }
502 | Message::End { msgid, .. } => *msgid,
503 }
504 }
505
506 pub fn has_payload(&self) -> bool {
508 matches!(
509 self,
510 Message::General { .. }
511 | Message::Configuration { .. }
512 | Message::Control { .. }
513 | Message::StdOut { .. }
514 | Message::StdErr { .. }
515 )
516 }
517
518 pub fn data(&self) -> Option<&[u8]> {
520 match self {
521 Message::General { data, .. }
522 | Message::Configuration { data, .. }
523 | Message::Control { data, .. }
524 | Message::StdOut { data, .. }
525 | Message::StdErr { data, .. } => data.as_deref(),
526 _ => None,
527 }
528 }
529
530 pub fn set_data(&mut self, payload: Vec<u8>) {
532 match self {
533 Message::General { data, .. }
534 | Message::Configuration { data, .. }
535 | Message::Control { data, .. }
536 | Message::StdOut { data, .. }
537 | Message::StdErr { data, .. } => {
538 *data = Some(payload);
539 }
540 _ => {}
541 }
542 }
543
544 pub fn data_encode(&mut self, raw: &[u8]) {
546 let encoded = base64_encode(raw);
547 self.set_data(encoded.into_bytes());
548 }
549
550 pub fn data_decode(&self) -> Result<Vec<u8>, MessageProcessingError> {
557 match self.data() {
558 Some(d) => {
559 let ident = self.ident().to_string();
560 let s = std::str::from_utf8(d)
561 .map_err(|_| MessageProcessingError::InvalidPayload(ident.clone()))?;
562 let raw = base64_decode_reason(s).map_err(|reason| {
563 MessageProcessingError::InvalidPayloadDetail(ident.clone(), reason)
564 })?;
565 validate_payload_protocol(&ident, &raw)?;
566 Ok(raw)
567 }
568 None => Err(MessageProcessingError::InvalidPayload(self.ident().into())),
569 }
570 }
571
572 pub fn data_update(&mut self, raw: &[u8]) -> Result<(), MessageProcessingError> {
574 if !self.has_payload() {
575 return Err(MessageProcessingError::UnexpectedPayload(
576 self.ident().into(),
577 ));
578 }
579 match self {
580 Message::General { data, .. }
581 | Message::Configuration { data, .. }
582 | Message::Control { data, .. }
583 | Message::StdOut { data, .. }
584 | Message::StdErr { data, .. } => {
585 if let Some(ref mut d) = data {
586 d.extend_from_slice(raw);
587 } else {
588 *data = Some(raw.to_vec());
589 }
590 }
591 _ => {}
592 }
593 Ok(())
594 }
595
596 pub fn srcid(&self) -> Option<u64> {
598 match self {
599 Message::Control { srcid, .. }
600 | Message::StdOut { srcid, .. }
601 | Message::StdErr { srcid, .. }
602 | Message::Retcode { srcid, .. }
603 | Message::Timeout { srcid, .. }
604 | Message::Routing { srcid, .. } => Some(*srcid),
605 _ => None,
606 }
607 }
608
609 pub fn nodes(&self) -> Option<&str> {
611 match self {
612 Message::StdOut { nodes, .. }
613 | Message::StdErr { nodes, .. }
614 | Message::Retcode { nodes, .. }
615 | Message::Timeout { nodes, .. } => Some(nodes.as_str()),
616 _ => None,
617 }
618 }
619
620 pub fn xml(&self) -> Vec<u8> {
627 let mut attrs = Vec::new();
628
629 attrs.push(format!("type=\"{}\"", xml_escape(self.ident())));
631 attrs.push(format!("msgid=\"{}\"", self.msgid()));
632
633 match self {
635 Message::Configuration { gateway, .. } => {
636 attrs.push(format!("gateway=\"{}\"", xml_escape(gateway)));
637 }
638 Message::Control {
639 srcid,
640 action,
641 target,
642 ..
643 } => {
644 attrs.push(format!("srcid=\"{}\"", srcid));
645 attrs.push(format!("action=\"{}\"", xml_escape(action)));
646 attrs.push(format!("target=\"{}\"", xml_escape(target)));
647 }
648 Message::Ack { ack, .. } => {
649 attrs.push(format!("ack=\"{}\"", ack));
650 }
651 Message::Error { reason, .. } => {
652 attrs.push(format!("reason=\"{}\"", xml_escape(reason)));
653 }
654 Message::StdOut { srcid, nodes, .. } => {
655 attrs.push(format!("srcid=\"{}\"", srcid));
656 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
657 }
658 Message::StdErr { srcid, nodes, .. } => {
659 attrs.push(format!("srcid=\"{}\"", srcid));
660 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
661 }
662 Message::Retcode {
663 srcid,
664 retcode,
665 nodes,
666 ..
667 } => {
668 attrs.push(format!("srcid=\"{}\"", srcid));
669 attrs.push(format!("retcode=\"{}\"", retcode));
670 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
671 }
672 Message::Timeout { srcid, nodes, .. } => {
673 attrs.push(format!("srcid=\"{}\"", srcid));
674 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
675 }
676 Message::Routing {
677 srcid,
678 event,
679 gateway,
680 targets,
681 ..
682 } => {
683 attrs.push(format!("srcid=\"{}\"", srcid));
684 attrs.push(format!("event=\"{}\"", xml_escape(event)));
685 attrs.push(format!("gateway=\"{}\"", xml_escape(gateway)));
686 attrs.push(format!("targets=\"{}\"", xml_escape(targets)));
687 }
688 _ => {}
689 }
690
691 let attr_str = attrs.join(" ");
692 let payload = self.data().unwrap_or(&[]);
693
694 if payload.is_empty() {
695 format!("<message {}/>", attr_str).into_bytes()
696 } else {
697 let payload_str = String::from_utf8_lossy(payload);
698 format!("<message {}>{}</message>", attr_str, payload_str).into_bytes()
699 }
700 }
701
702 pub fn from_attrs(attrs: &HashMap<String, String>) -> Result<Message, MessageProcessingError> {
711 let msg_type = match attrs.get("type") {
712 Some(t) if !t.is_empty() => t.as_str(),
713 _ => return Err(MessageProcessingError::NoType),
715 };
716
717 let msgid: u64 = attrs
718 .get("msgid")
719 .and_then(|v| v.parse().ok())
720 .unwrap_or_else(next_msg_id);
721
722 match msg_type {
723 IDENT_CFG => {
724 let gateway = attrs.get("gateway").cloned().unwrap_or_default();
725 Ok(Message::Configuration {
726 msgid,
727 gateway,
728 data: None,
729 })
730 }
731 IDENT_CTL => {
732 let srcid = attr_u64(attrs, "srcid")?;
733 let action = attrs.get("action").cloned().unwrap_or_default();
734 let target = attrs.get("target").cloned().unwrap_or_default();
735 Ok(Message::Control {
736 msgid,
737 srcid,
738 action,
739 target,
740 data: None,
741 })
742 }
743 IDENT_ACK => {
744 let ack = attr_u64(attrs, "ack")?;
745 Ok(Message::Ack { msgid, ack })
746 }
747 IDENT_ERR => {
748 let reason = attrs.get("reason").cloned().unwrap_or_default();
749 Ok(Message::Error { msgid, reason })
750 }
751 IDENT_OUT => {
752 let srcid = attr_u64(attrs, "srcid")?;
753 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
754 Ok(Message::StdOut {
755 msgid,
756 srcid,
757 nodes,
758 data: None,
759 })
760 }
761 IDENT_SER => {
762 let srcid = attr_u64(attrs, "srcid")?;
763 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
764 Ok(Message::StdErr {
765 msgid,
766 srcid,
767 nodes,
768 data: None,
769 })
770 }
771 IDENT_RET => {
772 let srcid = attr_u64(attrs, "srcid")?;
773 let retcode: i32 = attrs
774 .get("retcode")
775 .and_then(|v| v.parse().ok())
776 .ok_or_else(|| MessageProcessingError::MissingAttribute("retcode".into()))?;
777 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
778 Ok(Message::Retcode {
779 msgid,
780 srcid,
781 retcode,
782 nodes,
783 })
784 }
785 IDENT_TIM => {
786 let srcid = attr_u64(attrs, "srcid")?;
787 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
788 Ok(Message::Timeout {
789 msgid,
790 srcid,
791 nodes,
792 })
793 }
794 IDENT_RTR => {
795 let srcid = attr_u64(attrs, "srcid")?;
796 let event = attrs.get("event").cloned().unwrap_or_default();
797 let gateway = attrs.get("gateway").cloned().unwrap_or_default();
798 let targets = attrs.get("targets").cloned().unwrap_or_default();
799 Ok(Message::Routing {
800 msgid,
801 srcid,
802 event,
803 gateway,
804 targets,
805 })
806 }
807 other => Err(MessageProcessingError::UnknownType(other.into())),
808 }
809 }
810}
811
812impl std::fmt::Display for Message {
813 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
814 write!(
815 f,
816 "Message {} (type: {}, msgid: {})",
817 self.ident(),
818 self.ident(),
819 self.msgid()
820 )
821 }
822}
823
824fn attr_u64(attrs: &HashMap<String, String>, key: &str) -> Result<u64, MessageProcessingError> {
826 attrs
827 .get(key)
828 .and_then(|v| v.parse().ok())
829 .ok_or_else(|| MessageProcessingError::MissingAttribute(key.into()))
830}
831
832#[derive(Debug, Clone, Copy, PartialEq)]
838enum ParserState {
839 Text,
841 TagName,
843 CloseTagName,
845 Attributes,
847 SelfClose,
849}
850
851#[derive(Debug)]
856pub struct XmlReader {
857 msg_queue: VecDeque<Message>,
858 pub version: Option<String>,
860 draft: Option<Message>,
862 state: ParserState,
864 tag_buf: String,
865 attr_name: String,
866 attr_val: String,
867 attrs: HashMap<String, String>,
868 text_buf: String,
869 in_attr_value: bool,
870 attr_quote: char,
871}
872
873impl XmlReader {
874 pub fn new() -> Self {
876 Self {
877 msg_queue: VecDeque::new(),
878 version: None,
879 draft: None,
880 state: ParserState::Text,
881 tag_buf: String::new(),
882 attr_name: String::new(),
883 attr_val: String::new(),
884 attrs: HashMap::new(),
885 text_buf: String::new(),
886 in_attr_value: false,
887 attr_quote: '"',
888 }
889 }
890
891 pub fn msg_available(&self) -> bool {
893 !self.msg_queue.is_empty()
894 }
895
896 pub fn pop_msg(&mut self) -> Option<Message> {
898 self.msg_queue.pop_front()
899 }
900
901 pub fn feed(&mut self, data: &str) -> Result<(), MessageProcessingError> {
906 for ch in data.chars() {
907 match self.state {
908 ParserState::Text => {
909 if ch == '<' {
910 if !self.text_buf.is_empty() {
912 let text = std::mem::take(&mut self.text_buf);
913 self.characters(&text)?;
914 }
915 self.tag_buf.clear();
916 self.state = ParserState::TagName;
917 } else {
918 self.text_buf.push(ch);
919 }
920 }
921 ParserState::TagName => {
922 if ch == '<' {
923 return Err(MessageProcessingError::ParseError(
927 "not well-formed (invalid token)".into(),
928 ));
929 } else if ch == '/' && self.tag_buf.is_empty() {
930 self.tag_buf.clear();
932 self.state = ParserState::CloseTagName;
933 } else if ch == ' ' || ch == '\t' {
934 self.attrs.clear();
936 self.attr_name.clear();
937 self.state = ParserState::Attributes;
938 } else if ch == '>' {
939 self.attrs.clear();
941 self.start_element(&self.tag_buf.clone(), &self.attrs.clone())?;
942 self.state = ParserState::Text;
943 } else if ch == '/' {
944 self.attrs.clear();
945 self.state = ParserState::SelfClose;
946 } else {
947 self.tag_buf.push(ch);
948 }
949 }
950 ParserState::CloseTagName => {
951 if ch == '>' {
952 self.end_element(&self.tag_buf.clone())?;
953 self.state = ParserState::Text;
954 } else if ch == '<' {
955 return Err(MessageProcessingError::ParseError(
956 "not well-formed (invalid token)".into(),
957 ));
958 } else {
959 self.tag_buf.push(ch);
960 }
961 }
962 ParserState::Attributes => {
963 if self.in_attr_value {
964 if ch == self.attr_quote {
965 let name = std::mem::take(&mut self.attr_name);
967 let val = xml_unescape(&std::mem::take(&mut self.attr_val));
968 self.attrs.insert(name, val);
969 self.in_attr_value = false;
970 } else {
971 self.attr_val.push(ch);
972 }
973 } else if ch == '<' {
974 return Err(MessageProcessingError::ParseError(
977 "not well-formed (invalid token)".into(),
978 ));
979 } else if ch == '=' {
980 } else if ch == '"' || ch == '\'' {
982 self.attr_quote = ch;
983 self.attr_val.clear();
984 self.in_attr_value = true;
985 } else if ch == '>' {
986 self.start_element(&self.tag_buf.clone(), &self.attrs.clone())?;
988 self.state = ParserState::Text;
989 } else if ch == '/' {
990 self.state = ParserState::SelfClose;
991 } else if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
992 } else {
994 self.attr_name.push(ch);
995 }
996 }
997 ParserState::SelfClose => {
998 if ch == '>' {
999 let tag = self.tag_buf.clone();
1001 let attrs = self.attrs.clone();
1002 self.start_element(&tag, &attrs)?;
1003 self.end_element(&tag)?;
1004 self.state = ParserState::Text;
1005 } else {
1006 return Err(MessageProcessingError::ParseError(
1008 "not well-formed (invalid token)".into(),
1009 ));
1010 }
1011 }
1012 }
1013 }
1014 Ok(())
1015 }
1016
1017 fn start_element(
1018 &mut self,
1019 name: &str,
1020 attrs: &HashMap<String, String>,
1021 ) -> Result<(), MessageProcessingError> {
1022 match name {
1023 "channel" => {
1024 self.version = attrs.get("version").cloned();
1025 self.msg_queue.push_back(Message::start());
1026 }
1027 "message" => {
1028 let msg = Message::from_attrs(attrs)?;
1029 self.draft = Some(msg);
1030 }
1031 _ => {
1032 return Err(MessageProcessingError::InvalidTag(name.into()));
1033 }
1034 }
1035 Ok(())
1036 }
1037
1038 fn end_element(&mut self, name: &str) -> Result<(), MessageProcessingError> {
1039 match name {
1040 "message" => {
1041 if let Some(msg) = self.draft.take() {
1042 self.msg_queue.push_back(msg);
1043 }
1044 }
1045 "channel" => {
1046 self.msg_queue.push_back(Message::end());
1047 }
1048 _ => {}
1049 }
1050 Ok(())
1051 }
1052
1053 fn characters(&mut self, content: &str) -> Result<(), MessageProcessingError> {
1054 if let Some(ref mut draft) = self.draft {
1055 let trimmed = content.trim();
1056 if !trimmed.is_empty() {
1057 draft.data_update(trimmed.as_bytes())?;
1058 }
1059 }
1060 Ok(())
1061 }
1062}
1063
1064impl Default for XmlReader {
1065 fn default() -> Self {
1066 Self::new()
1067 }
1068}
1069
1070pub trait ChannelHandler: Send {
1079 fn start(&mut self, channel: &mut Channel);
1081
1082 fn recv(&mut self, channel: &mut Channel, msg: Message);
1084}
1085
1086#[derive(Debug)]
1092pub struct Channel {
1093 pub opened: bool,
1095 pub setup: bool,
1097 pub initiator: bool,
1099 pub closed: bool,
1101 pub xml_reader: XmlReader,
1103 pub outbox: VecDeque<Vec<u8>>,
1105}
1106
1107impl Channel {
1108 pub fn new(initiator: bool) -> Self {
1113 Self {
1114 opened: false,
1115 setup: false,
1116 initiator,
1117 closed: false,
1118 xml_reader: XmlReader::new(),
1119 outbox: VecDeque::new(),
1120 }
1121 }
1122
1123 pub fn init_xml(&self) -> Vec<u8> {
1125 b"<?xml version='1.0' encoding='UTF-8'?>".to_vec()
1126 }
1127
1128 pub fn open_xml(&self, version: &str) -> Vec<u8> {
1130 format!("<channel version=\"{}\">", xml_escape(version)).into_bytes()
1131 }
1132
1133 pub fn close_xml(&self) -> Vec<u8> {
1135 b"</channel>".to_vec()
1136 }
1137
1138 pub fn send(&mut self, msg: &Message) {
1140 let mut xml = msg.xml();
1141 xml.push(b'\n');
1142 self.outbox.push_back(xml);
1143 }
1144
1145 pub fn feed(&mut self, data: &str) -> Result<Vec<Message>, MessageProcessingError> {
1147 self.xml_reader.feed(data)?;
1148 let mut messages = Vec::new();
1149 while let Some(msg) = self.xml_reader.pop_msg() {
1150 messages.push(msg);
1151 }
1152 Ok(messages)
1153 }
1154
1155 pub fn take_outgoing(&mut self) -> Option<Vec<u8>> {
1157 self.outbox.pop_front()
1158 }
1159
1160 pub fn close(&mut self) {
1162 self.closed = true;
1163 self.opened = false;
1164 self.setup = false;
1165 }
1166
1167 pub fn handle_channel_error(
1187 &mut self,
1188 node: &str,
1189 err: &MessageProcessingError,
1190 ) -> Option<Message> {
1191 let report = if self.initiator {
1192 let text = match err {
1194 MessageProcessingError::ParseError(inner) => inner.clone(),
1195 other => other.to_string(),
1196 };
1197 let mut msg = Message::stderr(node, 0);
1198 msg.data_encode(text.as_bytes());
1199 Some(msg)
1200 } else {
1201 let reason = match err {
1203 MessageProcessingError::ParseError(inner) => format!("Parse error: {}", inner),
1204 other => other.to_string(),
1205 };
1206 self.send(&Message::error(&reason));
1207 None
1208 };
1209 self.close();
1211 report
1212 }
1213}
1214
1215#[cfg(test)]
1220mod tests {
1221 use super::*;
1222
1223 #[test]
1226 fn test_base64_roundtrip() {
1227 let data = b"Hello, ClusterShell!";
1228 let encoded = base64_encode(data);
1229 let decoded = base64_decode(&encoded).unwrap();
1230 assert_eq!(decoded, data);
1231 }
1232
1233 #[test]
1234 fn test_base64_empty() {
1235 assert_eq!(base64_encode(b""), "");
1236 assert_eq!(base64_decode("").unwrap(), Vec::<u8>::new());
1237 }
1238
1239 #[test]
1240 fn test_base64_padding() {
1241 let e1 = base64_encode(b"A");
1243 assert!(e1.ends_with("=="));
1244 assert_eq!(base64_decode(&e1).unwrap(), b"A");
1245
1246 let e2 = base64_encode(b"AB");
1248 assert!(e2.ends_with('=') && !e2.ends_with("=="));
1249 assert_eq!(base64_decode(&e2).unwrap(), b"AB");
1250
1251 let e3 = base64_encode(b"ABC");
1253 assert!(!e3.ends_with('='));
1254 assert_eq!(base64_decode(&e3).unwrap(), b"ABC");
1255 }
1256
1257 #[test]
1258 fn test_base64_with_newlines() {
1259 let encoded = "SGVs\nbG8=";
1261 let decoded = base64_decode(encoded).unwrap();
1262 assert_eq!(decoded, b"Hello");
1263 }
1264
1265 #[test]
1268 fn test_xml_escape() {
1269 assert_eq!(xml_escape("hello"), "hello");
1270 assert_eq!(xml_escape("a&b"), "a&b");
1271 assert_eq!(xml_escape("<tag>"), "<tag>");
1272 assert_eq!(xml_escape("x=\"y\""), "x="y"");
1273 }
1274
1275 #[test]
1276 fn test_xml_unescape() {
1277 assert_eq!(xml_unescape("a&b"), "a&b");
1278 assert_eq!(xml_unescape("<tag>"), "<tag>");
1279 }
1280
1281 #[test]
1284 fn test_message_idents() {
1285 assert_eq!(Message::general().ident(), "GEN");
1286 assert_eq!(Message::configuration("gw1").ident(), "CFG");
1287 assert_eq!(Message::control(0).ident(), "CTL");
1288 assert_eq!(Message::ack(0).ident(), "ACK");
1289 assert_eq!(Message::error("oops").ident(), "ERR");
1290 assert_eq!(Message::stdout("node1", 0).ident(), "OUT");
1291 assert_eq!(Message::stderr("node1", 0).ident(), "SER");
1292 assert_eq!(Message::retcode("node1", 0, 0).ident(), "RET");
1293 assert_eq!(Message::timeout("node1", 0).ident(), "TIM");
1294 assert_eq!(Message::routing("ev", "gw", "tgt", 0).ident(), "RTR");
1295 assert_eq!(Message::start().ident(), "CHA");
1296 assert_eq!(Message::end().ident(), "END");
1297 }
1298
1299 #[test]
1300 fn test_message_has_payload() {
1301 assert!(Message::general().has_payload());
1302 assert!(Message::configuration("gw1").has_payload());
1303 assert!(Message::control(0).has_payload());
1304 assert!(Message::stdout("n1", 0).has_payload());
1305 assert!(Message::stderr("n1", 0).has_payload());
1306 assert!(!Message::ack(0).has_payload());
1307 assert!(!Message::error("x").has_payload());
1308 assert!(!Message::retcode("n1", 0, 0).has_payload());
1309 assert!(!Message::timeout("n1", 0).has_payload());
1310 assert!(!Message::start().has_payload());
1311 assert!(!Message::end().has_payload());
1312 }
1313
1314 #[test]
1315 fn test_message_srcid() {
1316 assert_eq!(Message::control(42).srcid(), Some(42));
1317 assert_eq!(Message::stdout("n", 7).srcid(), Some(7));
1318 assert_eq!(Message::stderr("n", 8).srcid(), Some(8));
1319 assert_eq!(Message::retcode("n", 0, 9).srcid(), Some(9));
1320 assert_eq!(Message::timeout("n", 10).srcid(), Some(10));
1321 assert_eq!(Message::routing("e", "g", "t", 11).srcid(), Some(11));
1322 assert_eq!(Message::general().srcid(), None);
1323 assert_eq!(Message::ack(0).srcid(), None);
1324 assert_eq!(Message::configuration("gw").srcid(), None);
1325 }
1326
1327 #[test]
1328 fn test_message_nodes() {
1329 assert_eq!(Message::stdout("node[1-3]", 0).nodes(), Some("node[1-3]"));
1330 assert_eq!(Message::stderr("node5", 0).nodes(), Some("node5"));
1331 assert_eq!(Message::retcode("node1", 0, 0).nodes(), Some("node1"));
1332 assert_eq!(Message::timeout("node2", 0).nodes(), Some("node2"));
1333 assert_eq!(Message::general().nodes(), None);
1334 assert_eq!(Message::ack(0).nodes(), None);
1335 }
1336
1337 #[test]
1340 fn test_data_encode_decode() {
1341 let mut msg = Message::configuration("gw1");
1342 let payload = b"some topology data here";
1343 msg.data_encode(payload);
1344 assert!(msg.data().is_some());
1345 let decoded = msg.data_decode().unwrap();
1346 assert_eq!(decoded, payload);
1347 }
1348
1349 #[test]
1350 fn test_data_update() {
1351 let mut msg = Message::configuration("gw");
1352 msg.data_update(b"SGVs").unwrap();
1353 msg.data_update(b"bG8=").unwrap();
1354 assert_eq!(msg.data().unwrap(), b"SGVsbG8=");
1355 let decoded = msg.data_decode().unwrap();
1356 assert_eq!(decoded, b"Hello");
1357 }
1358
1359 #[test]
1360 fn test_data_update_unexpected() {
1361 let mut msg = Message::ack(0);
1362 let result = msg.data_update(b"data");
1363 assert!(result.is_err());
1364 assert!(matches!(
1365 result.unwrap_err(),
1366 MessageProcessingError::UnexpectedPayload(_)
1367 ));
1368 }
1369
1370 #[test]
1373 fn test_xml_ack() {
1374 let msg = Message::Ack { msgid: 5, ack: 3 };
1375 let xml = String::from_utf8(msg.xml()).unwrap();
1376 assert!(xml.contains("type=\"ACK\""));
1377 assert!(xml.contains("msgid=\"5\""));
1378 assert!(xml.contains("ack=\"3\""));
1379 assert!(xml.contains("/>") || xml.contains("</message>"));
1380 }
1381
1382 #[test]
1383 fn test_xml_configuration_with_payload() {
1384 let mut msg = Message::Configuration {
1385 msgid: 0,
1386 gateway: "gateway1".into(),
1387 data: None,
1388 };
1389 msg.data_encode(b"topology");
1390 let xml = String::from_utf8(msg.xml()).unwrap();
1391 assert!(xml.contains("type=\"CFG\""));
1392 assert!(xml.contains("gateway=\"gateway1\""));
1393 assert!(xml.contains("</message>"));
1394 assert!(xml.contains(&base64_encode(b"topology")));
1396 }
1397
1398 #[test]
1399 fn test_xml_error() {
1400 let msg = Message::Error {
1401 msgid: 1,
1402 reason: "something broke".into(),
1403 };
1404 let xml = String::from_utf8(msg.xml()).unwrap();
1405 assert!(xml.contains("type=\"ERR\""));
1406 assert!(xml.contains("reason=\"something broke\""));
1407 }
1408
1409 #[test]
1410 fn test_xml_retcode() {
1411 let msg = Message::Retcode {
1412 msgid: 2,
1413 srcid: 10,
1414 retcode: -1,
1415 nodes: "node[1-5]".into(),
1416 };
1417 let xml = String::from_utf8(msg.xml()).unwrap();
1418 assert!(xml.contains("type=\"RET\""));
1419 assert!(xml.contains("retcode=\"-1\""));
1420 assert!(xml.contains("nodes=\"node[1-5]\""));
1421 }
1422
1423 #[test]
1424 fn test_xml_control() {
1425 let msg = Message::Control {
1426 msgid: 3,
1427 srcid: 1,
1428 action: "shell".into(),
1429 target: "node[1-10]".into(),
1430 data: None,
1431 };
1432 let xml = String::from_utf8(msg.xml()).unwrap();
1433 assert!(xml.contains("type=\"CTL\""));
1434 assert!(xml.contains("action=\"shell\""));
1435 assert!(xml.contains("target=\"node[1-10]\""));
1436 }
1437
1438 #[test]
1439 fn test_xml_routing() {
1440 let msg = Message::Routing {
1441 msgid: 4,
1442 srcid: 2,
1443 event: "connect".into(),
1444 gateway: "gw2".into(),
1445 targets: "node[20-30]".into(),
1446 };
1447 let xml = String::from_utf8(msg.xml()).unwrap();
1448 assert!(xml.contains("type=\"RTR\""));
1449 assert!(xml.contains("event=\"connect\""));
1450 assert!(xml.contains("gateway=\"gw2\""));
1451 assert!(xml.contains("targets=\"node[20-30]\""));
1452 }
1453
1454 #[test]
1457 fn test_xmlreader_channel_start_end() {
1458 let mut reader = XmlReader::new();
1459 reader.feed("<channel version=\"1.0\"></channel>").unwrap();
1460 assert!(reader.msg_available());
1461 let m1 = reader.pop_msg().unwrap();
1462 assert_eq!(m1.ident(), "CHA");
1463 assert_eq!(reader.version.as_deref(), Some("1.0"));
1464
1465 let m2 = reader.pop_msg().unwrap();
1466 assert_eq!(m2.ident(), "END");
1467 assert!(!reader.msg_available());
1468 }
1469
1470 #[test]
1471 fn test_xmlreader_ack_message() {
1472 let mut reader = XmlReader::new();
1473 reader
1474 .feed(
1475 "<channel version=\"1.0\"><message type=\"ACK\" msgid=\"0\" ack=\"5\"/></channel>",
1476 )
1477 .unwrap();
1478
1479 let start = reader.pop_msg().unwrap();
1480 assert_eq!(start.ident(), "CHA");
1481
1482 let ack = reader.pop_msg().unwrap();
1483 assert_eq!(ack.ident(), "ACK");
1484 if let Message::Ack { ack, msgid, .. } = ack {
1485 assert_eq!(ack, 5);
1486 assert_eq!(msgid, 0);
1487 } else {
1488 panic!("expected Ack variant");
1489 }
1490
1491 let end = reader.pop_msg().unwrap();
1492 assert_eq!(end.ident(), "END");
1493 }
1494
1495 #[test]
1496 fn test_xmlreader_message_with_payload() {
1497 let payload_b64 = base64_encode(b"hello world");
1498 let xml = format!(
1499 "<channel version=\"2.0\"><message type=\"CFG\" msgid=\"1\" gateway=\"gw1\">{}</message></channel>",
1500 payload_b64
1501 );
1502
1503 let mut reader = XmlReader::new();
1504 reader.feed(&xml).unwrap();
1505
1506 let _start = reader.pop_msg().unwrap();
1507 let cfg = reader.pop_msg().unwrap();
1508 assert_eq!(cfg.ident(), "CFG");
1509 let decoded = cfg.data_decode().unwrap();
1510 assert_eq!(decoded, b"hello world");
1511
1512 if let Message::Configuration { gateway, .. } = &cfg {
1513 assert_eq!(gateway, "gw1");
1514 } else {
1515 panic!("expected Configuration variant");
1516 }
1517 }
1518
1519 #[test]
1520 fn test_xmlreader_incremental_feed() {
1521 let mut reader = XmlReader::new();
1522
1523 reader.feed("<chan").unwrap();
1525 assert!(!reader.msg_available());
1526 reader.feed("nel version=\"1.0\">").unwrap();
1527 assert!(reader.msg_available());
1528 let start = reader.pop_msg().unwrap();
1529 assert_eq!(start.ident(), "CHA");
1530
1531 reader
1532 .feed("<message type=\"ACK\" msgid=\"0\" ack=\"1\"/>")
1533 .unwrap();
1534 let ack = reader.pop_msg().unwrap();
1535 assert_eq!(ack.ident(), "ACK");
1536
1537 reader.feed("</channel>").unwrap();
1538 let end = reader.pop_msg().unwrap();
1539 assert_eq!(end.ident(), "END");
1540 }
1541
1542 #[test]
1543 fn test_xmlreader_unknown_type() {
1544 let mut reader = XmlReader::new();
1545 let result =
1546 reader.feed("<channel version=\"1\"><message type=\"BOGUS\" msgid=\"0\"/></channel>");
1547 assert!(result.is_err());
1548 }
1549
1550 #[test]
1551 fn test_xmlreader_error_message() {
1552 let mut reader = XmlReader::new();
1553 reader
1554 .feed("<channel version=\"1\"><message type=\"ERR\" msgid=\"0\" reason=\"parse failed\"/></channel>")
1555 .unwrap();
1556
1557 let _start = reader.pop_msg().unwrap();
1558 let err = reader.pop_msg().unwrap();
1559 assert_eq!(err.ident(), "ERR");
1560 if let Message::Error { reason, .. } = &err {
1561 assert_eq!(reason, "parse failed");
1562 } else {
1563 panic!("expected Error variant");
1564 }
1565 }
1566
1567 #[test]
1568 fn test_xmlreader_retcode() {
1569 let mut reader = XmlReader::new();
1570 reader
1571 .feed(
1572 "<message type=\"RET\" msgid=\"7\" srcid=\"3\" retcode=\"0\" nodes=\"node[1-5]\"/>",
1573 )
1574 .unwrap();
1575
1576 let msg = reader.pop_msg().unwrap();
1577 if let Message::Retcode {
1578 srcid,
1579 retcode,
1580 nodes,
1581 ..
1582 } = &msg
1583 {
1584 assert_eq!(*srcid, 3);
1585 assert_eq!(*retcode, 0);
1586 assert_eq!(nodes, "node[1-5]");
1587 } else {
1588 panic!("expected Retcode variant");
1589 }
1590 }
1591
1592 #[test]
1593 fn test_xmlreader_routing() {
1594 let mut reader = XmlReader::new();
1595 reader
1596 .feed("<message type=\"RTR\" msgid=\"0\" srcid=\"1\" event=\"connect\" gateway=\"gw1\" targets=\"node[1-10]\"/>")
1597 .unwrap();
1598
1599 let msg = reader.pop_msg().unwrap();
1600 if let Message::Routing {
1601 event,
1602 gateway,
1603 targets,
1604 ..
1605 } = &msg
1606 {
1607 assert_eq!(event, "connect");
1608 assert_eq!(gateway, "gw1");
1609 assert_eq!(targets, "node[1-10]");
1610 } else {
1611 panic!("expected Routing variant");
1612 }
1613 }
1614
1615 #[test]
1616 fn test_xmlreader_stdout_stderr() {
1617 let payload = base64_encode(b"output line");
1618 let xml = format!(
1619 "<message type=\"OUT\" msgid=\"0\" srcid=\"5\" nodes=\"node1\">{}</message>",
1620 payload
1621 );
1622 let mut reader = XmlReader::new();
1623 reader.feed(&xml).unwrap();
1624
1625 let msg = reader.pop_msg().unwrap();
1626 assert_eq!(msg.ident(), "OUT");
1627 assert_eq!(msg.srcid(), Some(5));
1628 assert_eq!(msg.nodes(), Some("node1"));
1629 assert_eq!(msg.data_decode().unwrap(), b"output line");
1630
1631 let xml2 = format!(
1633 "<message type=\"SER\" msgid=\"1\" srcid=\"6\" nodes=\"node2\">{}</message>",
1634 base64_encode(b"error line")
1635 );
1636 reader.feed(&xml2).unwrap();
1637 let msg2 = reader.pop_msg().unwrap();
1638 assert_eq!(msg2.ident(), "SER");
1639 assert_eq!(msg2.data_decode().unwrap(), b"error line");
1640 }
1641
1642 #[test]
1643 fn test_xmlreader_timeout() {
1644 let mut reader = XmlReader::new();
1645 reader
1646 .feed("<message type=\"TIM\" msgid=\"0\" srcid=\"1\" nodes=\"node[5-8]\"/>")
1647 .unwrap();
1648 let msg = reader.pop_msg().unwrap();
1649 assert_eq!(msg.ident(), "TIM");
1650 assert_eq!(msg.nodes(), Some("node[5-8]"));
1651 }
1652
1653 #[test]
1654 fn test_xmlreader_control() {
1655 let payload = base64_encode(b"command data");
1656 let xml = format!(
1657 "<message type=\"CTL\" msgid=\"0\" srcid=\"1\" action=\"shell\" target=\"node[1-3]\">{}</message>",
1658 payload
1659 );
1660 let mut reader = XmlReader::new();
1661 reader.feed(&xml).unwrap();
1662
1663 let msg = reader.pop_msg().unwrap();
1664 if let Message::Control { action, target, .. } = &msg {
1665 assert_eq!(action, "shell");
1666 assert_eq!(target, "node[1-3]");
1667 } else {
1668 panic!("expected Control variant");
1669 }
1670 assert_eq!(msg.data_decode().unwrap(), b"command data");
1671 }
1672
1673 #[test]
1676 fn test_xml_roundtrip_ack() {
1677 let original = Message::Ack { msgid: 10, ack: 7 };
1678 let xml = String::from_utf8(original.xml()).unwrap();
1679 let mut reader = XmlReader::new();
1680 reader.feed(&xml).unwrap();
1681 let parsed = reader.pop_msg().unwrap();
1682 assert_eq!(parsed.ident(), "ACK");
1683 if let Message::Ack { msgid, ack, .. } = parsed {
1684 assert_eq!(msgid, 10);
1685 assert_eq!(ack, 7);
1686 }
1687 }
1688
1689 #[test]
1690 fn test_xml_roundtrip_cfg_with_data() {
1691 let mut original = Message::Configuration {
1692 msgid: 0,
1693 gateway: "my-gateway".into(),
1694 data: None,
1695 };
1696 original.data_encode(b"topology bytes here");
1697 let xml = String::from_utf8(original.xml()).unwrap();
1698
1699 let mut reader = XmlReader::new();
1700 reader.feed(&xml).unwrap();
1701 let parsed = reader.pop_msg().unwrap();
1702 assert_eq!(parsed.ident(), "CFG");
1703 if let Message::Configuration { gateway, .. } = &parsed {
1704 assert_eq!(gateway, "my-gateway");
1705 }
1706 assert_eq!(parsed.data_decode().unwrap(), b"topology bytes here");
1707 }
1708
1709 #[test]
1710 fn test_xml_roundtrip_error() {
1711 let original = Message::Error {
1712 msgid: 3,
1713 reason: "test <error> & reason".into(),
1714 };
1715 let xml = String::from_utf8(original.xml()).unwrap();
1716 let mut reader = XmlReader::new();
1717 reader.feed(&xml).unwrap();
1718 let parsed = reader.pop_msg().unwrap();
1719 if let Message::Error { reason, .. } = &parsed {
1720 assert_eq!(reason, "test <error> & reason");
1721 } else {
1722 panic!("expected Error variant");
1723 }
1724 }
1725
1726 #[test]
1727 fn test_xml_roundtrip_retcode() {
1728 let original = Message::Retcode {
1729 msgid: 5,
1730 srcid: 2,
1731 retcode: 127,
1732 nodes: "node[1-100]".into(),
1733 };
1734 let xml = String::from_utf8(original.xml()).unwrap();
1735 let mut reader = XmlReader::new();
1736 reader.feed(&xml).unwrap();
1737 let parsed = reader.pop_msg().unwrap();
1738 if let Message::Retcode { retcode, nodes, .. } = &parsed {
1739 assert_eq!(*retcode, 127);
1740 assert_eq!(nodes, "node[1-100]");
1741 } else {
1742 panic!("expected Retcode variant");
1743 }
1744 }
1745
1746 #[test]
1749 fn test_channel_creation() {
1750 let ch = Channel::new(true);
1751 assert!(!ch.opened);
1752 assert!(!ch.setup);
1753 assert!(ch.initiator);
1754 }
1755
1756 #[test]
1757 fn test_channel_init_xml() {
1758 let ch = Channel::new(false);
1759 let xml = ch.init_xml();
1760 assert_eq!(
1761 String::from_utf8(xml).unwrap(),
1762 "<?xml version='1.0' encoding='UTF-8'?>"
1763 );
1764 }
1765
1766 #[test]
1767 fn test_channel_open_close_xml() {
1768 let ch = Channel::new(true);
1769 let open = String::from_utf8(ch.open_xml("3.0")).unwrap();
1770 assert_eq!(open, "<channel version=\"3.0\">");
1771 let close = String::from_utf8(ch.close_xml()).unwrap();
1772 assert_eq!(close, "</channel>");
1773 }
1774
1775 #[test]
1776 fn test_channel_send() {
1777 let mut ch = Channel::new(true);
1778 let msg = Message::ack(42);
1779 ch.send(&msg);
1780 let outgoing = ch.take_outgoing().unwrap();
1781 let xml = String::from_utf8(outgoing).unwrap();
1782 assert!(xml.contains("type=\"ACK\""));
1783 assert!(xml.ends_with('\n'));
1784 }
1785
1786 #[test]
1787 fn test_channel_feed_and_receive() {
1788 let mut ch = Channel::new(false);
1789 let messages = ch
1790 .feed(
1791 "<channel version=\"1.0\"><message type=\"ACK\" msgid=\"0\" ack=\"1\"/></channel>",
1792 )
1793 .unwrap();
1794 assert_eq!(messages.len(), 3); assert_eq!(messages[0].ident(), "CHA");
1796 assert_eq!(messages[1].ident(), "ACK");
1797 assert_eq!(messages[2].ident(), "END");
1798 }
1799
1800 #[test]
1801 fn test_channel_outbox_ordering() {
1802 let mut ch = Channel::new(true);
1803 ch.send(&Message::ack(1));
1804 ch.send(&Message::ack(2));
1805 ch.send(&Message::ack(3));
1806
1807 let m1 = ch.take_outgoing().unwrap();
1808 let m2 = ch.take_outgoing().unwrap();
1809 let m3 = ch.take_outgoing().unwrap();
1810 assert!(ch.take_outgoing().is_none());
1811
1812 assert!(String::from_utf8(m1).unwrap().contains("ACK"));
1814 assert!(String::from_utf8(m2).unwrap().contains("ACK"));
1815 assert!(String::from_utf8(m3).unwrap().contains("ACK"));
1816 }
1817
1818 #[test]
1821 fn test_message_display() {
1822 let msg = Message::ack(0);
1823 let display = format!("{}", msg);
1824 assert!(display.contains("ACK"));
1825 }
1826
1827 #[test]
1830 fn test_from_attrs_missing_type() {
1831 let attrs = HashMap::new();
1832 assert!(Message::from_attrs(&attrs).is_err());
1833 }
1834
1835 #[test]
1836 fn test_from_attrs_missing_srcid() {
1837 let mut attrs = HashMap::new();
1838 attrs.insert("type".into(), "CTL".into());
1839 attrs.insert("msgid".into(), "0".into());
1840 assert!(Message::from_attrs(&attrs).is_err());
1842 }
1843
1844 #[test]
1845 fn test_data_decode_no_data() {
1846 let msg = Message::configuration("gw");
1847 let result = msg.data_decode();
1848 assert!(result.is_err());
1849 }
1850
1851 #[test]
1852 fn test_xml_special_characters_roundtrip() {
1853 let msg = Message::Error {
1854 msgid: 0,
1855 reason: "node<1>&\"2\"".into(),
1856 };
1857 let xml = String::from_utf8(msg.xml()).unwrap();
1858 assert!(xml.contains("<"));
1860 assert!(xml.contains("&"));
1861
1862 let mut reader = XmlReader::new();
1864 reader.feed(&xml).unwrap();
1865 let parsed = reader.pop_msg().unwrap();
1866 if let Message::Error { reason, .. } = &parsed {
1867 assert_eq!(reason, "node<1>&\"2\"");
1868 }
1869 }
1870
1871 #[test]
1879 fn test_msg_pickle_protocol_pin() {
1880 assert_eq!(GW_PICKLE_PROTOCOL, 4);
1882
1883 let mut msg = Message::configuration("n1");
1885 let raw = b"\x80\x04some-payload-bytes";
1886 msg.data_encode(raw);
1887 let encoded = msg.data().unwrap();
1889 let decoded_b64 = base64_decode(std::str::from_utf8(encoded).unwrap()).unwrap();
1890 assert_eq!(decoded_b64[0], 0x80);
1891 assert!(decoded_b64[1] <= GW_PICKLE_PROTOCOL);
1892 assert_eq!(msg.data_decode().unwrap(), raw);
1893 }
1894
1895 #[test]
1901 fn test_channel_err_pickle_proto_pl() {
1902 let payload = base64_encode(b"\x80\x07spam");
1904 let xml = format!(
1905 "<channel version=\"1.10.1\"><message msgid=\"14\" type=\"CFG\" gateway=\"n1\">{}</message></channel>",
1906 payload
1907 );
1908 let mut reader = XmlReader::new();
1909 reader.feed(&xml).unwrap();
1910 let _start = reader.pop_msg().unwrap();
1911 let cfg = reader.pop_msg().unwrap();
1912 assert_eq!(cfg.ident(), "CFG");
1913 let err = cfg.data_decode().unwrap_err();
1914 assert_eq!(
1915 err.to_string(),
1916 "Message CFG has an invalid payload (unsupported pickle protocol: 7)"
1917 );
1918 }
1919
1920 #[test]
1922 fn test_channel_err_no_type_msg() {
1923 let mut reader = XmlReader::new();
1924 let err = reader
1925 .feed("<channel version=\"1\"><message msgid=\"24\"></message></channel>")
1926 .unwrap_err();
1927 assert_eq!(err.to_string(), "Unknown message with no type");
1928 }
1929
1930 #[test]
1932 fn test_channel_err_empty_type_msg() {
1933 let mut reader = XmlReader::new();
1934 let err = reader
1935 .feed("<channel version=\"1\"><message msgid=\"24\" type=\"\"></message></channel>")
1936 .unwrap_err();
1937 assert_eq!(err.to_string(), "Unknown message with no type");
1938 }
1939
1940 #[test]
1943 fn test_channel_err_unknown_msg_names_type() {
1944 let mut reader = XmlReader::new();
1945 let err = reader
1946 .feed("<channel version=\"1\"><message msgid=\"24\" type=\"ABC\"></message></channel>")
1947 .unwrap_err();
1948 assert_eq!(err.to_string(), "Unknown message type ABC");
1949 }
1950
1951 #[test]
1955 fn test_channel_err_invalid_payload_includes_cause() {
1956 let mut msg = Message::configuration("n1");
1959 msg.set_data(b"bar".to_vec());
1960 let err = msg.data_decode().unwrap_err();
1961 assert_eq!(
1962 err.to_string(),
1963 "Message CFG has an invalid payload (Incorrect padding)"
1964 );
1965
1966 let mut msg2 = Message::configuration("n1");
1971 msg2.set_data(b"barm".to_vec());
1972 assert!(msg2.data_decode().is_ok());
1973
1974 let msg3 = Message::configuration("n1");
1976 let err3 = msg3.data_decode().unwrap_err();
1977 assert_eq!(err3.to_string(), "Message CFG has an invalid payload");
1978 }
1979
1980 #[test]
1984 fn test_xmlreader_parse_error_not_well_formed() {
1985 let mut reader = XmlReader::new();
1986 let err = reader.feed("<message type=\"ABC\"</message>").unwrap_err();
1987 assert!(matches!(err, MessageProcessingError::ParseError(_)));
1988 assert!(err.to_string().contains("not well-formed"));
1989 }
1990
1991 #[test]
1996 fn test_handle_channel_error_initiator_reports_stderr() {
1997 let mut chan = Channel::new(true); chan.opened = true;
1999
2000 let err = MessageProcessingError::InvalidTag("foo".into());
2001 let msg = chan.handle_channel_error("gw1", &err).unwrap();
2002
2003 assert_eq!(msg.ident(), "SER");
2005 assert_eq!(msg.srcid(), Some(0));
2006 assert_eq!(msg.nodes(), Some("gw1"));
2007 assert_eq!(msg.data_decode().unwrap(), b"Invalid starting tag foo");
2009
2010 assert!(chan.closed);
2012 assert!(!chan.opened);
2013 }
2014
2015 #[test]
2018 fn test_handle_channel_error_initiator_parse_error_bare_text() {
2019 let mut chan = Channel::new(true);
2020 let err = MessageProcessingError::ParseError("not well-formed (invalid token)".into());
2021 let msg = chan.handle_channel_error("gw1", &err).unwrap();
2022 assert_eq!(
2023 msg.data_decode().unwrap(),
2024 b"not well-formed (invalid token)"
2025 );
2026 assert!(chan.closed);
2027 }
2028
2029 #[test]
2032 fn test_handle_channel_error_target_sends_error_message() {
2033 let mut chan = Channel::new(false); chan.opened = true;
2035
2036 let err = MessageProcessingError::NoType;
2037 let report = chan.handle_channel_error("n1", &err);
2038 assert!(report.is_none());
2039
2040 let out = String::from_utf8(chan.take_outgoing().unwrap()).unwrap();
2041 assert!(out.contains("type=\"ERR\""));
2042 assert!(out.contains("reason=\"Unknown message with no type\""));
2043 assert!(chan.closed);
2044
2045 let mut chan2 = Channel::new(false);
2047 let err2 = MessageProcessingError::ParseError("not well-formed (invalid token)".into());
2048 chan2.handle_channel_error("n1", &err2);
2049 let out2 = String::from_utf8(chan2.take_outgoing().unwrap()).unwrap();
2050 assert!(out2.contains("reason=\"Parse error: not well-formed (invalid token)\""));
2051 }
2052}