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
124const B64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
130
131pub fn base64_encode(input: &[u8]) -> String {
133 let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
134 for chunk in input.chunks(3) {
135 let b0 = chunk[0] as u32;
136 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
137 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
138 let triple = (b0 << 16) | (b1 << 8) | b2;
139
140 out.push(B64_CHARS[((triple >> 18) & 0x3F) as usize] as char);
141 out.push(B64_CHARS[((triple >> 12) & 0x3F) as usize] as char);
142 if chunk.len() > 1 {
143 out.push(B64_CHARS[((triple >> 6) & 0x3F) as usize] as char);
144 } else {
145 out.push('=');
146 }
147 if chunk.len() > 2 {
148 out.push(B64_CHARS[(triple & 0x3F) as usize] as char);
149 } else {
150 out.push('=');
151 }
152 }
153 out
154}
155
156pub fn base64_decode(input: &str) -> Result<Vec<u8>, MessageProcessingError> {
162 base64_decode_reason(input).map_err(MessageProcessingError::InvalidPayload)
163}
164
165fn base64_decode_reason(input: &str) -> Result<Vec<u8>, String> {
169 fn b64_val(c: u8) -> Result<u8, String> {
170 match c {
171 b'A'..=b'Z' => Ok(c - b'A'),
172 b'a'..=b'z' => Ok(c - b'a' + 26),
173 b'0'..=b'9' => Ok(c - b'0' + 52),
174 b'+' => Ok(62),
175 b'/' => Ok(63),
176 _ => Err("invalid base64 character".to_string()),
177 }
178 }
179
180 let clean: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
182 if clean.is_empty() {
183 return Ok(Vec::new());
184 }
185
186 if !clean.len().is_multiple_of(4) {
189 return Err("Incorrect padding".to_string());
190 }
191 if let Some(pos) = clean.iter().position(|&b| b == b'=') {
192 if clean.len() - pos > 2 || clean[pos..].iter().any(|&b| b != b'=') {
193 return Err("Invalid padding".to_string());
194 }
195 }
196
197 let mut out = Vec::with_capacity(clean.len() * 3 / 4);
198 for chunk in clean.chunks(4) {
199 if chunk.len() < 2 {
200 return Err("truncated base64".to_string());
201 }
202 let a = b64_val(chunk[0])?;
203 let b = b64_val(chunk[1])?;
204 out.push((a << 2) | (b >> 4));
205 if chunk.len() > 2 && chunk[2] != b'=' {
206 let c = b64_val(chunk[2])?;
207 out.push((b << 4) | (c >> 2));
208 if chunk.len() > 3 && chunk[3] != b'=' {
209 let d = b64_val(chunk[3])?;
210 out.push((c << 6) | d);
211 }
212 }
213 }
214 Ok(out)
215}
216
217pub fn validate_payload_protocol(ident: &str, raw: &[u8]) -> Result<(), MessageProcessingError> {
228 if raw.len() >= 2 && raw[0] == 0x80 && raw[1] > GW_PICKLE_PROTOCOL {
229 return Err(MessageProcessingError::InvalidPayloadDetail(
230 ident.to_string(),
231 format!("unsupported pickle protocol: {}", raw[1]),
232 ));
233 }
234 Ok(())
235}
236
237fn xml_escape(s: &str) -> String {
242 let mut out = String::with_capacity(s.len());
243 for c in s.chars() {
244 match c {
245 '&' => out.push_str("&"),
246 '<' => out.push_str("<"),
247 '>' => out.push_str(">"),
248 '"' => out.push_str("""),
249 '\'' => out.push_str("'"),
250 _ => out.push(c),
251 }
252 }
253 out
254}
255
256fn xml_unescape(s: &str) -> String {
257 s.replace("&", "&")
258 .replace("<", "<")
259 .replace(">", ">")
260 .replace(""", "\"")
261 .replace("'", "'")
262}
263
264#[derive(Debug, Clone)]
274pub enum Message {
275 General { msgid: u64, data: Option<Vec<u8>> },
277
278 Configuration {
281 msgid: u64,
282 gateway: String,
283 data: Option<Vec<u8>>,
284 },
285
286 Control {
289 msgid: u64,
290 srcid: u64,
291 action: String,
292 target: String,
293 data: Option<Vec<u8>>,
294 },
295
296 Ack { msgid: u64, ack: u64 },
298
299 Error { msgid: u64, reason: String },
301
302 StdOut {
305 msgid: u64,
306 srcid: u64,
307 nodes: String,
308 data: Option<Vec<u8>>,
309 },
310
311 StdErr {
314 msgid: u64,
315 srcid: u64,
316 nodes: String,
317 data: Option<Vec<u8>>,
318 },
319
320 Retcode {
322 msgid: u64,
323 srcid: u64,
324 retcode: i32,
325 nodes: String,
326 },
327
328 Timeout {
330 msgid: u64,
331 srcid: u64,
332 nodes: String,
333 },
334
335 Routing {
337 msgid: u64,
338 srcid: u64,
339 event: String,
340 gateway: String,
341 targets: String,
342 },
343
344 Start { msgid: u64 },
346
347 End { msgid: u64 },
349}
350
351impl Message {
352 pub fn general() -> Self {
356 Message::General {
357 msgid: next_msg_id(),
358 data: None,
359 }
360 }
361
362 pub fn configuration(gateway: &str) -> Self {
364 Message::Configuration {
365 msgid: next_msg_id(),
366 gateway: gateway.to_string(),
367 data: None,
368 }
369 }
370
371 pub fn control(srcid: u64) -> Self {
373 Message::Control {
374 msgid: next_msg_id(),
375 srcid,
376 action: String::new(),
377 target: String::new(),
378 data: None,
379 }
380 }
381
382 pub fn ack(ackid: u64) -> Self {
384 Message::Ack {
385 msgid: next_msg_id(),
386 ack: ackid,
387 }
388 }
389
390 pub fn error(reason: &str) -> Self {
392 Message::Error {
393 msgid: next_msg_id(),
394 reason: reason.to_string(),
395 }
396 }
397
398 pub fn stdout(nodes: &str, srcid: u64) -> Self {
400 Message::StdOut {
401 msgid: next_msg_id(),
402 srcid,
403 nodes: nodes.to_string(),
404 data: None,
405 }
406 }
407
408 pub fn stderr(nodes: &str, srcid: u64) -> Self {
410 Message::StdErr {
411 msgid: next_msg_id(),
412 srcid,
413 nodes: nodes.to_string(),
414 data: None,
415 }
416 }
417
418 pub fn retcode(nodes: &str, retcode: i32, srcid: u64) -> Self {
420 Message::Retcode {
421 msgid: next_msg_id(),
422 srcid,
423 retcode,
424 nodes: nodes.to_string(),
425 }
426 }
427
428 pub fn timeout(nodes: &str, srcid: u64) -> Self {
430 Message::Timeout {
431 msgid: next_msg_id(),
432 srcid,
433 nodes: nodes.to_string(),
434 }
435 }
436
437 pub fn routing(event: &str, gateway: &str, targets: &str, srcid: u64) -> Self {
439 Message::Routing {
440 msgid: next_msg_id(),
441 srcid,
442 event: event.to_string(),
443 gateway: gateway.to_string(),
444 targets: targets.to_string(),
445 }
446 }
447
448 pub fn start() -> Self {
450 Message::Start {
451 msgid: next_msg_id(),
452 }
453 }
454
455 pub fn end() -> Self {
457 Message::End {
458 msgid: next_msg_id(),
459 }
460 }
461
462 pub fn ident(&self) -> &str {
466 match self {
467 Message::General { .. } => IDENT_GEN,
468 Message::Configuration { .. } => IDENT_CFG,
469 Message::Control { .. } => IDENT_CTL,
470 Message::Ack { .. } => IDENT_ACK,
471 Message::Error { .. } => IDENT_ERR,
472 Message::StdOut { .. } => IDENT_OUT,
473 Message::StdErr { .. } => IDENT_SER,
474 Message::Retcode { .. } => IDENT_RET,
475 Message::Timeout { .. } => IDENT_TIM,
476 Message::Routing { .. } => IDENT_RTR,
477 Message::Start { .. } => IDENT_CHA,
478 Message::End { .. } => IDENT_END,
479 }
480 }
481
482 pub fn msgid(&self) -> u64 {
484 match self {
485 Message::General { msgid, .. }
486 | Message::Configuration { msgid, .. }
487 | Message::Control { msgid, .. }
488 | Message::Ack { msgid, .. }
489 | Message::Error { msgid, .. }
490 | Message::StdOut { msgid, .. }
491 | Message::StdErr { msgid, .. }
492 | Message::Retcode { msgid, .. }
493 | Message::Timeout { msgid, .. }
494 | Message::Routing { msgid, .. }
495 | Message::Start { msgid, .. }
496 | Message::End { msgid, .. } => *msgid,
497 }
498 }
499
500 pub fn has_payload(&self) -> bool {
502 matches!(
503 self,
504 Message::General { .. }
505 | Message::Configuration { .. }
506 | Message::Control { .. }
507 | Message::StdOut { .. }
508 | Message::StdErr { .. }
509 )
510 }
511
512 pub fn data(&self) -> Option<&[u8]> {
514 match self {
515 Message::General { data, .. }
516 | Message::Configuration { data, .. }
517 | Message::Control { data, .. }
518 | Message::StdOut { data, .. }
519 | Message::StdErr { data, .. } => data.as_deref(),
520 _ => None,
521 }
522 }
523
524 pub fn set_data(&mut self, payload: Vec<u8>) {
526 match self {
527 Message::General { data, .. }
528 | Message::Configuration { data, .. }
529 | Message::Control { data, .. }
530 | Message::StdOut { data, .. }
531 | Message::StdErr { data, .. } => {
532 *data = Some(payload);
533 }
534 _ => {}
535 }
536 }
537
538 pub fn data_encode(&mut self, raw: &[u8]) {
540 let encoded = base64_encode(raw);
541 self.set_data(encoded.into_bytes());
542 }
543
544 pub fn data_decode(&self) -> Result<Vec<u8>, MessageProcessingError> {
551 match self.data() {
552 Some(d) => {
553 let ident = self.ident().to_string();
554 let s = std::str::from_utf8(d)
555 .map_err(|_| MessageProcessingError::InvalidPayload(ident.clone()))?;
556 let raw = base64_decode_reason(s).map_err(|reason| {
557 MessageProcessingError::InvalidPayloadDetail(ident.clone(), reason)
558 })?;
559 validate_payload_protocol(&ident, &raw)?;
560 Ok(raw)
561 }
562 None => Err(MessageProcessingError::InvalidPayload(self.ident().into())),
563 }
564 }
565
566 pub fn data_update(&mut self, raw: &[u8]) -> Result<(), MessageProcessingError> {
568 if !self.has_payload() {
569 return Err(MessageProcessingError::UnexpectedPayload(
570 self.ident().into(),
571 ));
572 }
573 match self {
574 Message::General { data, .. }
575 | Message::Configuration { data, .. }
576 | Message::Control { data, .. }
577 | Message::StdOut { data, .. }
578 | Message::StdErr { data, .. } => {
579 if let Some(ref mut d) = data {
580 d.extend_from_slice(raw);
581 } else {
582 *data = Some(raw.to_vec());
583 }
584 }
585 _ => {}
586 }
587 Ok(())
588 }
589
590 pub fn srcid(&self) -> Option<u64> {
592 match self {
593 Message::Control { srcid, .. }
594 | Message::StdOut { srcid, .. }
595 | Message::StdErr { srcid, .. }
596 | Message::Retcode { srcid, .. }
597 | Message::Timeout { srcid, .. }
598 | Message::Routing { srcid, .. } => Some(*srcid),
599 _ => None,
600 }
601 }
602
603 pub fn nodes(&self) -> Option<&str> {
605 match self {
606 Message::StdOut { nodes, .. }
607 | Message::StdErr { nodes, .. }
608 | Message::Retcode { nodes, .. }
609 | Message::Timeout { nodes, .. } => Some(nodes.as_str()),
610 _ => None,
611 }
612 }
613
614 pub fn xml(&self) -> Vec<u8> {
621 let mut attrs = Vec::new();
622
623 attrs.push(format!("type=\"{}\"", xml_escape(self.ident())));
625 attrs.push(format!("msgid=\"{}\"", self.msgid()));
626
627 match self {
629 Message::Configuration { gateway, .. } => {
630 attrs.push(format!("gateway=\"{}\"", xml_escape(gateway)));
631 }
632 Message::Control {
633 srcid,
634 action,
635 target,
636 ..
637 } => {
638 attrs.push(format!("srcid=\"{}\"", srcid));
639 attrs.push(format!("action=\"{}\"", xml_escape(action)));
640 attrs.push(format!("target=\"{}\"", xml_escape(target)));
641 }
642 Message::Ack { ack, .. } => {
643 attrs.push(format!("ack=\"{}\"", ack));
644 }
645 Message::Error { reason, .. } => {
646 attrs.push(format!("reason=\"{}\"", xml_escape(reason)));
647 }
648 Message::StdOut { srcid, nodes, .. } => {
649 attrs.push(format!("srcid=\"{}\"", srcid));
650 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
651 }
652 Message::StdErr { srcid, nodes, .. } => {
653 attrs.push(format!("srcid=\"{}\"", srcid));
654 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
655 }
656 Message::Retcode {
657 srcid,
658 retcode,
659 nodes,
660 ..
661 } => {
662 attrs.push(format!("srcid=\"{}\"", srcid));
663 attrs.push(format!("retcode=\"{}\"", retcode));
664 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
665 }
666 Message::Timeout { srcid, nodes, .. } => {
667 attrs.push(format!("srcid=\"{}\"", srcid));
668 attrs.push(format!("nodes=\"{}\"", xml_escape(nodes)));
669 }
670 Message::Routing {
671 srcid,
672 event,
673 gateway,
674 targets,
675 ..
676 } => {
677 attrs.push(format!("srcid=\"{}\"", srcid));
678 attrs.push(format!("event=\"{}\"", xml_escape(event)));
679 attrs.push(format!("gateway=\"{}\"", xml_escape(gateway)));
680 attrs.push(format!("targets=\"{}\"", xml_escape(targets)));
681 }
682 _ => {}
683 }
684
685 let attr_str = attrs.join(" ");
686 let payload = self.data().unwrap_or(&[]);
687
688 if payload.is_empty() {
689 format!("<message {}/>", attr_str).into_bytes()
690 } else {
691 let payload_str = String::from_utf8_lossy(payload);
692 format!("<message {}>{}</message>", attr_str, payload_str).into_bytes()
693 }
694 }
695
696 pub fn from_attrs(attrs: &HashMap<String, String>) -> Result<Message, MessageProcessingError> {
705 let msg_type = match attrs.get("type") {
706 Some(t) if !t.is_empty() => t.as_str(),
707 _ => return Err(MessageProcessingError::NoType),
709 };
710
711 let msgid: u64 = attrs
712 .get("msgid")
713 .and_then(|v| v.parse().ok())
714 .unwrap_or_else(next_msg_id);
715
716 match msg_type {
717 IDENT_CFG => {
718 let gateway = attrs.get("gateway").cloned().unwrap_or_default();
719 Ok(Message::Configuration {
720 msgid,
721 gateway,
722 data: None,
723 })
724 }
725 IDENT_CTL => {
726 let srcid = attr_u64(attrs, "srcid")?;
727 let action = attrs.get("action").cloned().unwrap_or_default();
728 let target = attrs.get("target").cloned().unwrap_or_default();
729 Ok(Message::Control {
730 msgid,
731 srcid,
732 action,
733 target,
734 data: None,
735 })
736 }
737 IDENT_ACK => {
738 let ack = attr_u64(attrs, "ack")?;
739 Ok(Message::Ack { msgid, ack })
740 }
741 IDENT_ERR => {
742 let reason = attrs.get("reason").cloned().unwrap_or_default();
743 Ok(Message::Error { msgid, reason })
744 }
745 IDENT_OUT => {
746 let srcid = attr_u64(attrs, "srcid")?;
747 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
748 Ok(Message::StdOut {
749 msgid,
750 srcid,
751 nodes,
752 data: None,
753 })
754 }
755 IDENT_SER => {
756 let srcid = attr_u64(attrs, "srcid")?;
757 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
758 Ok(Message::StdErr {
759 msgid,
760 srcid,
761 nodes,
762 data: None,
763 })
764 }
765 IDENT_RET => {
766 let srcid = attr_u64(attrs, "srcid")?;
767 let retcode: i32 = attrs
768 .get("retcode")
769 .and_then(|v| v.parse().ok())
770 .ok_or_else(|| MessageProcessingError::MissingAttribute("retcode".into()))?;
771 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
772 Ok(Message::Retcode {
773 msgid,
774 srcid,
775 retcode,
776 nodes,
777 })
778 }
779 IDENT_TIM => {
780 let srcid = attr_u64(attrs, "srcid")?;
781 let nodes = attrs.get("nodes").cloned().unwrap_or_default();
782 Ok(Message::Timeout {
783 msgid,
784 srcid,
785 nodes,
786 })
787 }
788 IDENT_RTR => {
789 let srcid = attr_u64(attrs, "srcid")?;
790 let event = attrs.get("event").cloned().unwrap_or_default();
791 let gateway = attrs.get("gateway").cloned().unwrap_or_default();
792 let targets = attrs.get("targets").cloned().unwrap_or_default();
793 Ok(Message::Routing {
794 msgid,
795 srcid,
796 event,
797 gateway,
798 targets,
799 })
800 }
801 other => Err(MessageProcessingError::UnknownType(other.into())),
802 }
803 }
804}
805
806impl std::fmt::Display for Message {
807 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
808 write!(
809 f,
810 "Message {} (type: {}, msgid: {})",
811 self.ident(),
812 self.ident(),
813 self.msgid()
814 )
815 }
816}
817
818fn attr_u64(attrs: &HashMap<String, String>, key: &str) -> Result<u64, MessageProcessingError> {
820 attrs
821 .get(key)
822 .and_then(|v| v.parse().ok())
823 .ok_or_else(|| MessageProcessingError::MissingAttribute(key.into()))
824}
825
826#[derive(Debug, Clone, Copy, PartialEq)]
832enum ParserState {
833 Text,
835 TagName,
837 CloseTagName,
839 Attributes,
841 SelfClose,
843}
844
845#[derive(Debug)]
850pub struct XmlReader {
851 msg_queue: VecDeque<Message>,
852 pub version: Option<String>,
854 draft: Option<Message>,
856 state: ParserState,
858 tag_buf: String,
859 attr_name: String,
860 attr_val: String,
861 attrs: HashMap<String, String>,
862 text_buf: String,
863 in_attr_value: bool,
864 attr_quote: char,
865}
866
867impl XmlReader {
868 pub fn new() -> Self {
870 Self {
871 msg_queue: VecDeque::new(),
872 version: None,
873 draft: None,
874 state: ParserState::Text,
875 tag_buf: String::new(),
876 attr_name: String::new(),
877 attr_val: String::new(),
878 attrs: HashMap::new(),
879 text_buf: String::new(),
880 in_attr_value: false,
881 attr_quote: '"',
882 }
883 }
884
885 pub fn msg_available(&self) -> bool {
887 !self.msg_queue.is_empty()
888 }
889
890 pub fn pop_msg(&mut self) -> Option<Message> {
892 self.msg_queue.pop_front()
893 }
894
895 pub fn feed(&mut self, data: &str) -> Result<(), MessageProcessingError> {
900 for ch in data.chars() {
901 match self.state {
902 ParserState::Text => {
903 if ch == '<' {
904 if !self.text_buf.is_empty() {
906 let text = std::mem::take(&mut self.text_buf);
907 self.characters(&text)?;
908 }
909 self.tag_buf.clear();
910 self.state = ParserState::TagName;
911 } else {
912 self.text_buf.push(ch);
913 }
914 }
915 ParserState::TagName => {
916 if ch == '<' {
917 return Err(MessageProcessingError::ParseError(
921 "not well-formed (invalid token)".into(),
922 ));
923 } else if ch == '/' && self.tag_buf.is_empty() {
924 self.tag_buf.clear();
926 self.state = ParserState::CloseTagName;
927 } else if ch == ' ' || ch == '\t' {
928 self.attrs.clear();
930 self.attr_name.clear();
931 self.state = ParserState::Attributes;
932 } else if ch == '>' {
933 self.attrs.clear();
935 self.start_element(&self.tag_buf.clone(), &self.attrs.clone())?;
936 self.state = ParserState::Text;
937 } else if ch == '/' {
938 self.attrs.clear();
939 self.state = ParserState::SelfClose;
940 } else {
941 self.tag_buf.push(ch);
942 }
943 }
944 ParserState::CloseTagName => {
945 if ch == '>' {
946 self.end_element(&self.tag_buf.clone())?;
947 self.state = ParserState::Text;
948 } else if ch == '<' {
949 return Err(MessageProcessingError::ParseError(
950 "not well-formed (invalid token)".into(),
951 ));
952 } else {
953 self.tag_buf.push(ch);
954 }
955 }
956 ParserState::Attributes => {
957 if self.in_attr_value {
958 if ch == self.attr_quote {
959 let name = std::mem::take(&mut self.attr_name);
961 let val = xml_unescape(&std::mem::take(&mut self.attr_val));
962 self.attrs.insert(name, val);
963 self.in_attr_value = false;
964 } else {
965 self.attr_val.push(ch);
966 }
967 } else if ch == '<' {
968 return Err(MessageProcessingError::ParseError(
971 "not well-formed (invalid token)".into(),
972 ));
973 } else if ch == '=' {
974 } else if ch == '"' || ch == '\'' {
976 self.attr_quote = ch;
977 self.attr_val.clear();
978 self.in_attr_value = true;
979 } else if ch == '>' {
980 self.start_element(&self.tag_buf.clone(), &self.attrs.clone())?;
982 self.state = ParserState::Text;
983 } else if ch == '/' {
984 self.state = ParserState::SelfClose;
985 } else if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
986 } else {
988 self.attr_name.push(ch);
989 }
990 }
991 ParserState::SelfClose => {
992 if ch == '>' {
993 let tag = self.tag_buf.clone();
995 let attrs = self.attrs.clone();
996 self.start_element(&tag, &attrs)?;
997 self.end_element(&tag)?;
998 self.state = ParserState::Text;
999 } else {
1000 return Err(MessageProcessingError::ParseError(
1002 "not well-formed (invalid token)".into(),
1003 ));
1004 }
1005 }
1006 }
1007 }
1008 Ok(())
1009 }
1010
1011 fn start_element(
1012 &mut self,
1013 name: &str,
1014 attrs: &HashMap<String, String>,
1015 ) -> Result<(), MessageProcessingError> {
1016 match name {
1017 "channel" => {
1018 self.version = attrs.get("version").cloned();
1019 self.msg_queue.push_back(Message::start());
1020 }
1021 "message" => {
1022 let msg = Message::from_attrs(attrs)?;
1023 self.draft = Some(msg);
1024 }
1025 _ => {
1026 return Err(MessageProcessingError::InvalidTag(name.into()));
1027 }
1028 }
1029 Ok(())
1030 }
1031
1032 fn end_element(&mut self, name: &str) -> Result<(), MessageProcessingError> {
1033 match name {
1034 "message" => {
1035 if let Some(msg) = self.draft.take() {
1036 self.msg_queue.push_back(msg);
1037 }
1038 }
1039 "channel" => {
1040 self.msg_queue.push_back(Message::end());
1041 }
1042 _ => {}
1043 }
1044 Ok(())
1045 }
1046
1047 fn characters(&mut self, content: &str) -> Result<(), MessageProcessingError> {
1048 if let Some(ref mut draft) = self.draft {
1049 let trimmed = content.trim();
1050 if !trimmed.is_empty() {
1051 draft.data_update(trimmed.as_bytes())?;
1052 }
1053 }
1054 Ok(())
1055 }
1056}
1057
1058impl Default for XmlReader {
1059 fn default() -> Self {
1060 Self::new()
1061 }
1062}
1063
1064pub trait ChannelHandler: Send {
1073 fn start(&mut self, channel: &mut Channel);
1075
1076 fn recv(&mut self, channel: &mut Channel, msg: Message);
1078}
1079
1080#[derive(Debug)]
1086pub struct Channel {
1087 pub opened: bool,
1089 pub setup: bool,
1091 pub initiator: bool,
1093 pub closed: bool,
1095 pub xml_reader: XmlReader,
1097 pub outbox: VecDeque<Vec<u8>>,
1099}
1100
1101impl Channel {
1102 pub fn new(initiator: bool) -> Self {
1107 Self {
1108 opened: false,
1109 setup: false,
1110 initiator,
1111 closed: false,
1112 xml_reader: XmlReader::new(),
1113 outbox: VecDeque::new(),
1114 }
1115 }
1116
1117 pub fn init_xml(&self) -> Vec<u8> {
1119 b"<?xml version='1.0' encoding='UTF-8'?>".to_vec()
1120 }
1121
1122 pub fn open_xml(&self, version: &str) -> Vec<u8> {
1124 format!("<channel version=\"{}\">", xml_escape(version)).into_bytes()
1125 }
1126
1127 pub fn close_xml(&self) -> Vec<u8> {
1129 b"</channel>".to_vec()
1130 }
1131
1132 pub fn send(&mut self, msg: &Message) {
1134 let mut xml = msg.xml();
1135 xml.push(b'\n');
1136 self.outbox.push_back(xml);
1137 }
1138
1139 pub fn feed(&mut self, data: &str) -> Result<Vec<Message>, MessageProcessingError> {
1141 self.xml_reader.feed(data)?;
1142 let mut messages = Vec::new();
1143 while let Some(msg) = self.xml_reader.pop_msg() {
1144 messages.push(msg);
1145 }
1146 Ok(messages)
1147 }
1148
1149 pub fn take_outgoing(&mut self) -> Option<Vec<u8>> {
1151 self.outbox.pop_front()
1152 }
1153
1154 pub fn close(&mut self) {
1156 self.closed = true;
1157 self.opened = false;
1158 self.setup = false;
1159 }
1160
1161 pub fn handle_channel_error(
1181 &mut self,
1182 node: &str,
1183 err: &MessageProcessingError,
1184 ) -> Option<Message> {
1185 let report = if self.initiator {
1186 let text = match err {
1188 MessageProcessingError::ParseError(inner) => inner.clone(),
1189 other => other.to_string(),
1190 };
1191 let mut msg = Message::stderr(node, 0);
1192 msg.data_encode(text.as_bytes());
1193 Some(msg)
1194 } else {
1195 let reason = match err {
1197 MessageProcessingError::ParseError(inner) => format!("Parse error: {}", inner),
1198 other => other.to_string(),
1199 };
1200 self.send(&Message::error(&reason));
1201 None
1202 };
1203 self.close();
1205 report
1206 }
1207}
1208
1209#[cfg(test)]
1214mod tests {
1215 use super::*;
1216
1217 #[test]
1220 fn test_base64_roundtrip() {
1221 let data = b"Hello, ClusterShell!";
1222 let encoded = base64_encode(data);
1223 let decoded = base64_decode(&encoded).unwrap();
1224 assert_eq!(decoded, data);
1225 }
1226
1227 #[test]
1228 fn test_base64_empty() {
1229 assert_eq!(base64_encode(b""), "");
1230 assert_eq!(base64_decode("").unwrap(), Vec::<u8>::new());
1231 }
1232
1233 #[test]
1234 fn test_base64_padding() {
1235 let e1 = base64_encode(b"A");
1237 assert!(e1.ends_with("=="));
1238 assert_eq!(base64_decode(&e1).unwrap(), b"A");
1239
1240 let e2 = base64_encode(b"AB");
1242 assert!(e2.ends_with('=') && !e2.ends_with("=="));
1243 assert_eq!(base64_decode(&e2).unwrap(), b"AB");
1244
1245 let e3 = base64_encode(b"ABC");
1247 assert!(!e3.ends_with('='));
1248 assert_eq!(base64_decode(&e3).unwrap(), b"ABC");
1249 }
1250
1251 #[test]
1252 fn test_base64_with_newlines() {
1253 let encoded = "SGVs\nbG8=";
1255 let decoded = base64_decode(encoded).unwrap();
1256 assert_eq!(decoded, b"Hello");
1257 }
1258
1259 #[test]
1262 fn test_xml_escape() {
1263 assert_eq!(xml_escape("hello"), "hello");
1264 assert_eq!(xml_escape("a&b"), "a&b");
1265 assert_eq!(xml_escape("<tag>"), "<tag>");
1266 assert_eq!(xml_escape("x=\"y\""), "x="y"");
1267 }
1268
1269 #[test]
1270 fn test_xml_unescape() {
1271 assert_eq!(xml_unescape("a&b"), "a&b");
1272 assert_eq!(xml_unescape("<tag>"), "<tag>");
1273 }
1274
1275 #[test]
1278 fn test_message_idents() {
1279 assert_eq!(Message::general().ident(), "GEN");
1280 assert_eq!(Message::configuration("gw1").ident(), "CFG");
1281 assert_eq!(Message::control(0).ident(), "CTL");
1282 assert_eq!(Message::ack(0).ident(), "ACK");
1283 assert_eq!(Message::error("oops").ident(), "ERR");
1284 assert_eq!(Message::stdout("node1", 0).ident(), "OUT");
1285 assert_eq!(Message::stderr("node1", 0).ident(), "SER");
1286 assert_eq!(Message::retcode("node1", 0, 0).ident(), "RET");
1287 assert_eq!(Message::timeout("node1", 0).ident(), "TIM");
1288 assert_eq!(Message::routing("ev", "gw", "tgt", 0).ident(), "RTR");
1289 assert_eq!(Message::start().ident(), "CHA");
1290 assert_eq!(Message::end().ident(), "END");
1291 }
1292
1293 #[test]
1294 fn test_message_has_payload() {
1295 assert!(Message::general().has_payload());
1296 assert!(Message::configuration("gw1").has_payload());
1297 assert!(Message::control(0).has_payload());
1298 assert!(Message::stdout("n1", 0).has_payload());
1299 assert!(Message::stderr("n1", 0).has_payload());
1300 assert!(!Message::ack(0).has_payload());
1301 assert!(!Message::error("x").has_payload());
1302 assert!(!Message::retcode("n1", 0, 0).has_payload());
1303 assert!(!Message::timeout("n1", 0).has_payload());
1304 assert!(!Message::start().has_payload());
1305 assert!(!Message::end().has_payload());
1306 }
1307
1308 #[test]
1309 fn test_message_srcid() {
1310 assert_eq!(Message::control(42).srcid(), Some(42));
1311 assert_eq!(Message::stdout("n", 7).srcid(), Some(7));
1312 assert_eq!(Message::stderr("n", 8).srcid(), Some(8));
1313 assert_eq!(Message::retcode("n", 0, 9).srcid(), Some(9));
1314 assert_eq!(Message::timeout("n", 10).srcid(), Some(10));
1315 assert_eq!(Message::routing("e", "g", "t", 11).srcid(), Some(11));
1316 assert_eq!(Message::general().srcid(), None);
1317 assert_eq!(Message::ack(0).srcid(), None);
1318 assert_eq!(Message::configuration("gw").srcid(), None);
1319 }
1320
1321 #[test]
1322 fn test_message_nodes() {
1323 assert_eq!(Message::stdout("node[1-3]", 0).nodes(), Some("node[1-3]"));
1324 assert_eq!(Message::stderr("node5", 0).nodes(), Some("node5"));
1325 assert_eq!(Message::retcode("node1", 0, 0).nodes(), Some("node1"));
1326 assert_eq!(Message::timeout("node2", 0).nodes(), Some("node2"));
1327 assert_eq!(Message::general().nodes(), None);
1328 assert_eq!(Message::ack(0).nodes(), None);
1329 }
1330
1331 #[test]
1334 fn test_data_encode_decode() {
1335 let mut msg = Message::configuration("gw1");
1336 let payload = b"some topology data here";
1337 msg.data_encode(payload);
1338 assert!(msg.data().is_some());
1339 let decoded = msg.data_decode().unwrap();
1340 assert_eq!(decoded, payload);
1341 }
1342
1343 #[test]
1344 fn test_data_update() {
1345 let mut msg = Message::configuration("gw");
1346 msg.data_update(b"SGVs").unwrap();
1347 msg.data_update(b"bG8=").unwrap();
1348 assert_eq!(msg.data().unwrap(), b"SGVsbG8=");
1349 let decoded = msg.data_decode().unwrap();
1350 assert_eq!(decoded, b"Hello");
1351 }
1352
1353 #[test]
1354 fn test_data_update_unexpected() {
1355 let mut msg = Message::ack(0);
1356 let result = msg.data_update(b"data");
1357 assert!(result.is_err());
1358 assert!(matches!(
1359 result.unwrap_err(),
1360 MessageProcessingError::UnexpectedPayload(_)
1361 ));
1362 }
1363
1364 #[test]
1367 fn test_xml_ack() {
1368 let msg = Message::Ack { msgid: 5, ack: 3 };
1369 let xml = String::from_utf8(msg.xml()).unwrap();
1370 assert!(xml.contains("type=\"ACK\""));
1371 assert!(xml.contains("msgid=\"5\""));
1372 assert!(xml.contains("ack=\"3\""));
1373 assert!(xml.contains("/>") || xml.contains("</message>"));
1374 }
1375
1376 #[test]
1377 fn test_xml_configuration_with_payload() {
1378 let mut msg = Message::Configuration {
1379 msgid: 0,
1380 gateway: "gateway1".into(),
1381 data: None,
1382 };
1383 msg.data_encode(b"topology");
1384 let xml = String::from_utf8(msg.xml()).unwrap();
1385 assert!(xml.contains("type=\"CFG\""));
1386 assert!(xml.contains("gateway=\"gateway1\""));
1387 assert!(xml.contains("</message>"));
1388 assert!(xml.contains(&base64_encode(b"topology")));
1390 }
1391
1392 #[test]
1393 fn test_xml_error() {
1394 let msg = Message::Error {
1395 msgid: 1,
1396 reason: "something broke".into(),
1397 };
1398 let xml = String::from_utf8(msg.xml()).unwrap();
1399 assert!(xml.contains("type=\"ERR\""));
1400 assert!(xml.contains("reason=\"something broke\""));
1401 }
1402
1403 #[test]
1404 fn test_xml_retcode() {
1405 let msg = Message::Retcode {
1406 msgid: 2,
1407 srcid: 10,
1408 retcode: -1,
1409 nodes: "node[1-5]".into(),
1410 };
1411 let xml = String::from_utf8(msg.xml()).unwrap();
1412 assert!(xml.contains("type=\"RET\""));
1413 assert!(xml.contains("retcode=\"-1\""));
1414 assert!(xml.contains("nodes=\"node[1-5]\""));
1415 }
1416
1417 #[test]
1418 fn test_xml_control() {
1419 let msg = Message::Control {
1420 msgid: 3,
1421 srcid: 1,
1422 action: "shell".into(),
1423 target: "node[1-10]".into(),
1424 data: None,
1425 };
1426 let xml = String::from_utf8(msg.xml()).unwrap();
1427 assert!(xml.contains("type=\"CTL\""));
1428 assert!(xml.contains("action=\"shell\""));
1429 assert!(xml.contains("target=\"node[1-10]\""));
1430 }
1431
1432 #[test]
1433 fn test_xml_routing() {
1434 let msg = Message::Routing {
1435 msgid: 4,
1436 srcid: 2,
1437 event: "connect".into(),
1438 gateway: "gw2".into(),
1439 targets: "node[20-30]".into(),
1440 };
1441 let xml = String::from_utf8(msg.xml()).unwrap();
1442 assert!(xml.contains("type=\"RTR\""));
1443 assert!(xml.contains("event=\"connect\""));
1444 assert!(xml.contains("gateway=\"gw2\""));
1445 assert!(xml.contains("targets=\"node[20-30]\""));
1446 }
1447
1448 #[test]
1451 fn test_xmlreader_channel_start_end() {
1452 let mut reader = XmlReader::new();
1453 reader.feed("<channel version=\"1.0\"></channel>").unwrap();
1454 assert!(reader.msg_available());
1455 let m1 = reader.pop_msg().unwrap();
1456 assert_eq!(m1.ident(), "CHA");
1457 assert_eq!(reader.version.as_deref(), Some("1.0"));
1458
1459 let m2 = reader.pop_msg().unwrap();
1460 assert_eq!(m2.ident(), "END");
1461 assert!(!reader.msg_available());
1462 }
1463
1464 #[test]
1465 fn test_xmlreader_ack_message() {
1466 let mut reader = XmlReader::new();
1467 reader
1468 .feed(
1469 "<channel version=\"1.0\"><message type=\"ACK\" msgid=\"0\" ack=\"5\"/></channel>",
1470 )
1471 .unwrap();
1472
1473 let start = reader.pop_msg().unwrap();
1474 assert_eq!(start.ident(), "CHA");
1475
1476 let ack = reader.pop_msg().unwrap();
1477 assert_eq!(ack.ident(), "ACK");
1478 if let Message::Ack { ack, msgid, .. } = ack {
1479 assert_eq!(ack, 5);
1480 assert_eq!(msgid, 0);
1481 } else {
1482 panic!("expected Ack variant");
1483 }
1484
1485 let end = reader.pop_msg().unwrap();
1486 assert_eq!(end.ident(), "END");
1487 }
1488
1489 #[test]
1490 fn test_xmlreader_message_with_payload() {
1491 let payload_b64 = base64_encode(b"hello world");
1492 let xml = format!(
1493 "<channel version=\"2.0\"><message type=\"CFG\" msgid=\"1\" gateway=\"gw1\">{}</message></channel>",
1494 payload_b64
1495 );
1496
1497 let mut reader = XmlReader::new();
1498 reader.feed(&xml).unwrap();
1499
1500 let _start = reader.pop_msg().unwrap();
1501 let cfg = reader.pop_msg().unwrap();
1502 assert_eq!(cfg.ident(), "CFG");
1503 let decoded = cfg.data_decode().unwrap();
1504 assert_eq!(decoded, b"hello world");
1505
1506 if let Message::Configuration { gateway, .. } = &cfg {
1507 assert_eq!(gateway, "gw1");
1508 } else {
1509 panic!("expected Configuration variant");
1510 }
1511 }
1512
1513 #[test]
1514 fn test_xmlreader_incremental_feed() {
1515 let mut reader = XmlReader::new();
1516
1517 reader.feed("<chan").unwrap();
1519 assert!(!reader.msg_available());
1520 reader.feed("nel version=\"1.0\">").unwrap();
1521 assert!(reader.msg_available());
1522 let start = reader.pop_msg().unwrap();
1523 assert_eq!(start.ident(), "CHA");
1524
1525 reader
1526 .feed("<message type=\"ACK\" msgid=\"0\" ack=\"1\"/>")
1527 .unwrap();
1528 let ack = reader.pop_msg().unwrap();
1529 assert_eq!(ack.ident(), "ACK");
1530
1531 reader.feed("</channel>").unwrap();
1532 let end = reader.pop_msg().unwrap();
1533 assert_eq!(end.ident(), "END");
1534 }
1535
1536 #[test]
1537 fn test_xmlreader_unknown_type() {
1538 let mut reader = XmlReader::new();
1539 let result =
1540 reader.feed("<channel version=\"1\"><message type=\"BOGUS\" msgid=\"0\"/></channel>");
1541 assert!(result.is_err());
1542 }
1543
1544 #[test]
1545 fn test_xmlreader_error_message() {
1546 let mut reader = XmlReader::new();
1547 reader
1548 .feed("<channel version=\"1\"><message type=\"ERR\" msgid=\"0\" reason=\"parse failed\"/></channel>")
1549 .unwrap();
1550
1551 let _start = reader.pop_msg().unwrap();
1552 let err = reader.pop_msg().unwrap();
1553 assert_eq!(err.ident(), "ERR");
1554 if let Message::Error { reason, .. } = &err {
1555 assert_eq!(reason, "parse failed");
1556 } else {
1557 panic!("expected Error variant");
1558 }
1559 }
1560
1561 #[test]
1562 fn test_xmlreader_retcode() {
1563 let mut reader = XmlReader::new();
1564 reader
1565 .feed(
1566 "<message type=\"RET\" msgid=\"7\" srcid=\"3\" retcode=\"0\" nodes=\"node[1-5]\"/>",
1567 )
1568 .unwrap();
1569
1570 let msg = reader.pop_msg().unwrap();
1571 if let Message::Retcode {
1572 srcid,
1573 retcode,
1574 nodes,
1575 ..
1576 } = &msg
1577 {
1578 assert_eq!(*srcid, 3);
1579 assert_eq!(*retcode, 0);
1580 assert_eq!(nodes, "node[1-5]");
1581 } else {
1582 panic!("expected Retcode variant");
1583 }
1584 }
1585
1586 #[test]
1587 fn test_xmlreader_routing() {
1588 let mut reader = XmlReader::new();
1589 reader
1590 .feed("<message type=\"RTR\" msgid=\"0\" srcid=\"1\" event=\"connect\" gateway=\"gw1\" targets=\"node[1-10]\"/>")
1591 .unwrap();
1592
1593 let msg = reader.pop_msg().unwrap();
1594 if let Message::Routing {
1595 event,
1596 gateway,
1597 targets,
1598 ..
1599 } = &msg
1600 {
1601 assert_eq!(event, "connect");
1602 assert_eq!(gateway, "gw1");
1603 assert_eq!(targets, "node[1-10]");
1604 } else {
1605 panic!("expected Routing variant");
1606 }
1607 }
1608
1609 #[test]
1610 fn test_xmlreader_stdout_stderr() {
1611 let payload = base64_encode(b"output line");
1612 let xml = format!(
1613 "<message type=\"OUT\" msgid=\"0\" srcid=\"5\" nodes=\"node1\">{}</message>",
1614 payload
1615 );
1616 let mut reader = XmlReader::new();
1617 reader.feed(&xml).unwrap();
1618
1619 let msg = reader.pop_msg().unwrap();
1620 assert_eq!(msg.ident(), "OUT");
1621 assert_eq!(msg.srcid(), Some(5));
1622 assert_eq!(msg.nodes(), Some("node1"));
1623 assert_eq!(msg.data_decode().unwrap(), b"output line");
1624
1625 let xml2 = format!(
1627 "<message type=\"SER\" msgid=\"1\" srcid=\"6\" nodes=\"node2\">{}</message>",
1628 base64_encode(b"error line")
1629 );
1630 reader.feed(&xml2).unwrap();
1631 let msg2 = reader.pop_msg().unwrap();
1632 assert_eq!(msg2.ident(), "SER");
1633 assert_eq!(msg2.data_decode().unwrap(), b"error line");
1634 }
1635
1636 #[test]
1637 fn test_xmlreader_timeout() {
1638 let mut reader = XmlReader::new();
1639 reader
1640 .feed("<message type=\"TIM\" msgid=\"0\" srcid=\"1\" nodes=\"node[5-8]\"/>")
1641 .unwrap();
1642 let msg = reader.pop_msg().unwrap();
1643 assert_eq!(msg.ident(), "TIM");
1644 assert_eq!(msg.nodes(), Some("node[5-8]"));
1645 }
1646
1647 #[test]
1648 fn test_xmlreader_control() {
1649 let payload = base64_encode(b"command data");
1650 let xml = format!(
1651 "<message type=\"CTL\" msgid=\"0\" srcid=\"1\" action=\"shell\" target=\"node[1-3]\">{}</message>",
1652 payload
1653 );
1654 let mut reader = XmlReader::new();
1655 reader.feed(&xml).unwrap();
1656
1657 let msg = reader.pop_msg().unwrap();
1658 if let Message::Control { action, target, .. } = &msg {
1659 assert_eq!(action, "shell");
1660 assert_eq!(target, "node[1-3]");
1661 } else {
1662 panic!("expected Control variant");
1663 }
1664 assert_eq!(msg.data_decode().unwrap(), b"command data");
1665 }
1666
1667 #[test]
1670 fn test_xml_roundtrip_ack() {
1671 let original = Message::Ack { msgid: 10, ack: 7 };
1672 let xml = String::from_utf8(original.xml()).unwrap();
1673 let mut reader = XmlReader::new();
1674 reader.feed(&xml).unwrap();
1675 let parsed = reader.pop_msg().unwrap();
1676 assert_eq!(parsed.ident(), "ACK");
1677 if let Message::Ack { msgid, ack, .. } = parsed {
1678 assert_eq!(msgid, 10);
1679 assert_eq!(ack, 7);
1680 }
1681 }
1682
1683 #[test]
1684 fn test_xml_roundtrip_cfg_with_data() {
1685 let mut original = Message::Configuration {
1686 msgid: 0,
1687 gateway: "my-gateway".into(),
1688 data: None,
1689 };
1690 original.data_encode(b"topology bytes here");
1691 let xml = String::from_utf8(original.xml()).unwrap();
1692
1693 let mut reader = XmlReader::new();
1694 reader.feed(&xml).unwrap();
1695 let parsed = reader.pop_msg().unwrap();
1696 assert_eq!(parsed.ident(), "CFG");
1697 if let Message::Configuration { gateway, .. } = &parsed {
1698 assert_eq!(gateway, "my-gateway");
1699 }
1700 assert_eq!(parsed.data_decode().unwrap(), b"topology bytes here");
1701 }
1702
1703 #[test]
1704 fn test_xml_roundtrip_error() {
1705 let original = Message::Error {
1706 msgid: 3,
1707 reason: "test <error> & reason".into(),
1708 };
1709 let xml = String::from_utf8(original.xml()).unwrap();
1710 let mut reader = XmlReader::new();
1711 reader.feed(&xml).unwrap();
1712 let parsed = reader.pop_msg().unwrap();
1713 if let Message::Error { reason, .. } = &parsed {
1714 assert_eq!(reason, "test <error> & reason");
1715 } else {
1716 panic!("expected Error variant");
1717 }
1718 }
1719
1720 #[test]
1721 fn test_xml_roundtrip_retcode() {
1722 let original = Message::Retcode {
1723 msgid: 5,
1724 srcid: 2,
1725 retcode: 127,
1726 nodes: "node[1-100]".into(),
1727 };
1728 let xml = String::from_utf8(original.xml()).unwrap();
1729 let mut reader = XmlReader::new();
1730 reader.feed(&xml).unwrap();
1731 let parsed = reader.pop_msg().unwrap();
1732 if let Message::Retcode { retcode, nodes, .. } = &parsed {
1733 assert_eq!(*retcode, 127);
1734 assert_eq!(nodes, "node[1-100]");
1735 } else {
1736 panic!("expected Retcode variant");
1737 }
1738 }
1739
1740 #[test]
1743 fn test_channel_creation() {
1744 let ch = Channel::new(true);
1745 assert!(!ch.opened);
1746 assert!(!ch.setup);
1747 assert!(ch.initiator);
1748 }
1749
1750 #[test]
1751 fn test_channel_init_xml() {
1752 let ch = Channel::new(false);
1753 let xml = ch.init_xml();
1754 assert_eq!(
1755 String::from_utf8(xml).unwrap(),
1756 "<?xml version='1.0' encoding='UTF-8'?>"
1757 );
1758 }
1759
1760 #[test]
1761 fn test_channel_open_close_xml() {
1762 let ch = Channel::new(true);
1763 let open = String::from_utf8(ch.open_xml("3.0")).unwrap();
1764 assert_eq!(open, "<channel version=\"3.0\">");
1765 let close = String::from_utf8(ch.close_xml()).unwrap();
1766 assert_eq!(close, "</channel>");
1767 }
1768
1769 #[test]
1770 fn test_channel_send() {
1771 let mut ch = Channel::new(true);
1772 let msg = Message::ack(42);
1773 ch.send(&msg);
1774 let outgoing = ch.take_outgoing().unwrap();
1775 let xml = String::from_utf8(outgoing).unwrap();
1776 assert!(xml.contains("type=\"ACK\""));
1777 assert!(xml.ends_with('\n'));
1778 }
1779
1780 #[test]
1781 fn test_channel_feed_and_receive() {
1782 let mut ch = Channel::new(false);
1783 let messages = ch
1784 .feed(
1785 "<channel version=\"1.0\"><message type=\"ACK\" msgid=\"0\" ack=\"1\"/></channel>",
1786 )
1787 .unwrap();
1788 assert_eq!(messages.len(), 3); assert_eq!(messages[0].ident(), "CHA");
1790 assert_eq!(messages[1].ident(), "ACK");
1791 assert_eq!(messages[2].ident(), "END");
1792 }
1793
1794 #[test]
1795 fn test_channel_outbox_ordering() {
1796 let mut ch = Channel::new(true);
1797 ch.send(&Message::ack(1));
1798 ch.send(&Message::ack(2));
1799 ch.send(&Message::ack(3));
1800
1801 let m1 = ch.take_outgoing().unwrap();
1802 let m2 = ch.take_outgoing().unwrap();
1803 let m3 = ch.take_outgoing().unwrap();
1804 assert!(ch.take_outgoing().is_none());
1805
1806 assert!(String::from_utf8(m1).unwrap().contains("ACK"));
1808 assert!(String::from_utf8(m2).unwrap().contains("ACK"));
1809 assert!(String::from_utf8(m3).unwrap().contains("ACK"));
1810 }
1811
1812 #[test]
1815 fn test_message_display() {
1816 let msg = Message::ack(0);
1817 let display = format!("{}", msg);
1818 assert!(display.contains("ACK"));
1819 }
1820
1821 #[test]
1824 fn test_from_attrs_missing_type() {
1825 let attrs = HashMap::new();
1826 assert!(Message::from_attrs(&attrs).is_err());
1827 }
1828
1829 #[test]
1830 fn test_from_attrs_missing_srcid() {
1831 let mut attrs = HashMap::new();
1832 attrs.insert("type".into(), "CTL".into());
1833 attrs.insert("msgid".into(), "0".into());
1834 assert!(Message::from_attrs(&attrs).is_err());
1836 }
1837
1838 #[test]
1839 fn test_data_decode_no_data() {
1840 let msg = Message::configuration("gw");
1841 let result = msg.data_decode();
1842 assert!(result.is_err());
1843 }
1844
1845 #[test]
1846 fn test_xml_special_characters_roundtrip() {
1847 let msg = Message::Error {
1848 msgid: 0,
1849 reason: "node<1>&\"2\"".into(),
1850 };
1851 let xml = String::from_utf8(msg.xml()).unwrap();
1852 assert!(xml.contains("<"));
1854 assert!(xml.contains("&"));
1855
1856 let mut reader = XmlReader::new();
1858 reader.feed(&xml).unwrap();
1859 let parsed = reader.pop_msg().unwrap();
1860 if let Message::Error { reason, .. } = &parsed {
1861 assert_eq!(reason, "node<1>&\"2\"");
1862 }
1863 }
1864
1865 #[test]
1873 fn test_msg_pickle_protocol_pin() {
1874 assert_eq!(GW_PICKLE_PROTOCOL, 4);
1876
1877 let mut msg = Message::configuration("n1");
1879 let raw = b"\x80\x04some-payload-bytes";
1880 msg.data_encode(raw);
1881 let encoded = msg.data().unwrap();
1883 let decoded_b64 = base64_decode(std::str::from_utf8(encoded).unwrap()).unwrap();
1884 assert_eq!(decoded_b64[0], 0x80);
1885 assert!(decoded_b64[1] <= GW_PICKLE_PROTOCOL);
1886 assert_eq!(msg.data_decode().unwrap(), raw);
1887 }
1888
1889 #[test]
1895 fn test_channel_err_pickle_proto_pl() {
1896 let payload = base64_encode(b"\x80\x07spam");
1898 let xml = format!(
1899 "<channel version=\"1.10.1\"><message msgid=\"14\" type=\"CFG\" gateway=\"n1\">{}</message></channel>",
1900 payload
1901 );
1902 let mut reader = XmlReader::new();
1903 reader.feed(&xml).unwrap();
1904 let _start = reader.pop_msg().unwrap();
1905 let cfg = reader.pop_msg().unwrap();
1906 assert_eq!(cfg.ident(), "CFG");
1907 let err = cfg.data_decode().unwrap_err();
1908 assert_eq!(
1909 err.to_string(),
1910 "Message CFG has an invalid payload (unsupported pickle protocol: 7)"
1911 );
1912 }
1913
1914 #[test]
1916 fn test_channel_err_no_type_msg() {
1917 let mut reader = XmlReader::new();
1918 let err = reader
1919 .feed("<channel version=\"1\"><message msgid=\"24\"></message></channel>")
1920 .unwrap_err();
1921 assert_eq!(err.to_string(), "Unknown message with no type");
1922 }
1923
1924 #[test]
1926 fn test_channel_err_empty_type_msg() {
1927 let mut reader = XmlReader::new();
1928 let err = reader
1929 .feed("<channel version=\"1\"><message msgid=\"24\" type=\"\"></message></channel>")
1930 .unwrap_err();
1931 assert_eq!(err.to_string(), "Unknown message with no type");
1932 }
1933
1934 #[test]
1937 fn test_channel_err_unknown_msg_names_type() {
1938 let mut reader = XmlReader::new();
1939 let err = reader
1940 .feed("<channel version=\"1\"><message msgid=\"24\" type=\"ABC\"></message></channel>")
1941 .unwrap_err();
1942 assert_eq!(err.to_string(), "Unknown message type ABC");
1943 }
1944
1945 #[test]
1949 fn test_channel_err_invalid_payload_includes_cause() {
1950 let mut msg = Message::configuration("n1");
1953 msg.set_data(b"bar".to_vec());
1954 let err = msg.data_decode().unwrap_err();
1955 assert_eq!(
1956 err.to_string(),
1957 "Message CFG has an invalid payload (Incorrect padding)"
1958 );
1959
1960 let mut msg2 = Message::configuration("n1");
1965 msg2.set_data(b"barm".to_vec());
1966 assert!(msg2.data_decode().is_ok());
1967
1968 let msg3 = Message::configuration("n1");
1970 let err3 = msg3.data_decode().unwrap_err();
1971 assert_eq!(err3.to_string(), "Message CFG has an invalid payload");
1972 }
1973
1974 #[test]
1978 fn test_xmlreader_parse_error_not_well_formed() {
1979 let mut reader = XmlReader::new();
1980 let err = reader.feed("<message type=\"ABC\"</message>").unwrap_err();
1981 assert!(matches!(err, MessageProcessingError::ParseError(_)));
1982 assert!(err.to_string().contains("not well-formed"));
1983 }
1984
1985 #[test]
1990 fn test_handle_channel_error_initiator_reports_stderr() {
1991 let mut chan = Channel::new(true); chan.opened = true;
1993
1994 let err = MessageProcessingError::InvalidTag("foo".into());
1995 let msg = chan.handle_channel_error("gw1", &err).unwrap();
1996
1997 assert_eq!(msg.ident(), "SER");
1999 assert_eq!(msg.srcid(), Some(0));
2000 assert_eq!(msg.nodes(), Some("gw1"));
2001 assert_eq!(msg.data_decode().unwrap(), b"Invalid starting tag foo");
2003
2004 assert!(chan.closed);
2006 assert!(!chan.opened);
2007 }
2008
2009 #[test]
2012 fn test_handle_channel_error_initiator_parse_error_bare_text() {
2013 let mut chan = Channel::new(true);
2014 let err = MessageProcessingError::ParseError("not well-formed (invalid token)".into());
2015 let msg = chan.handle_channel_error("gw1", &err).unwrap();
2016 assert_eq!(
2017 msg.data_decode().unwrap(),
2018 b"not well-formed (invalid token)"
2019 );
2020 assert!(chan.closed);
2021 }
2022
2023 #[test]
2026 fn test_handle_channel_error_target_sends_error_message() {
2027 let mut chan = Channel::new(false); chan.opened = true;
2029
2030 let err = MessageProcessingError::NoType;
2031 let report = chan.handle_channel_error("n1", &err);
2032 assert!(report.is_none());
2033
2034 let out = String::from_utf8(chan.take_outgoing().unwrap()).unwrap();
2035 assert!(out.contains("type=\"ERR\""));
2036 assert!(out.contains("reason=\"Unknown message with no type\""));
2037 assert!(chan.closed);
2038
2039 let mut chan2 = Channel::new(false);
2041 let err2 = MessageProcessingError::ParseError("not well-formed (invalid token)".into());
2042 chan2.handle_channel_error("n1", &err2);
2043 let out2 = String::from_utf8(chan2.take_outgoing().unwrap()).unwrap();
2044 assert!(out2.contains("reason=\"Parse error: not well-formed (invalid token)\""));
2045 }
2046}