use crate::communication::{Channel, Message, MessageProcessingError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoutingEvent {
pub event: String,
pub gateway: String,
pub targets: String,
}
#[derive(Debug)]
pub struct TreeWorkerResponder {
srcwkr: u64,
}
impl TreeWorkerResponder {
pub fn new(srcwkr: u64) -> Self {
Self { srcwkr }
}
pub fn srcwkr(&self) -> u64 {
self.srcwkr
}
pub fn routing_message(&self, arg: &RoutingEvent) -> Message {
Message::routing(&arg.event, &arg.gateway, &arg.targets, self.srcwkr)
}
pub fn ev_routing(&self, chan: &mut Channel, arg: &RoutingEvent) {
chan.send(&self.routing_message(arg));
}
}
fn short_hostname() -> String {
hostname::get()
.map(|h| h.to_string_lossy().into_owned())
.unwrap_or_default()
.split('.')
.next()
.unwrap_or_default()
.to_string()
}
#[derive(Debug)]
pub struct GatewayChannel {
pub channel: Channel,
pub nodename: Option<String>,
pub topology: Option<Vec<u8>>,
}
impl Default for GatewayChannel {
fn default() -> Self {
Self::new()
}
}
impl GatewayChannel {
pub fn new() -> Self {
Self {
channel: Channel::new(false), nodename: None,
topology: None,
}
}
pub fn close(&mut self) {
self.channel.close();
}
pub fn feed(&mut self, data: &str) {
match self.channel.feed(data) {
Ok(messages) => {
for msg in messages {
self.recv(msg);
}
}
Err(err) => {
self.channel.handle_channel_error("", &err);
}
}
}
pub fn recv(&mut self, msg: Message) {
if let Err(err) = self.dispatch(&msg) {
self.channel.send(&Message::error(&err.to_string()));
self.close();
}
}
fn dispatch(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
if msg.ident() == "END" {
self.close();
return Ok(());
}
if self.channel.setup {
return self.recv_ctl(msg);
}
if self.channel.opened {
return self.recv_cfg(msg);
}
if msg.ident() == "CHA" {
self.channel.opened = true;
return Ok(());
}
Err(MessageProcessingError::UnexpectedMessage(msg.to_string()))
}
fn recv_cfg(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
if msg.ident() != "CFG" {
return Err(MessageProcessingError::UnexpectedMessage(msg.to_string()));
}
let gateway = match msg {
Message::Configuration { gateway, .. } => gateway.clone(),
_ => String::new(),
};
self.nodename = Some(if gateway.is_empty() {
short_hostname()
} else {
gateway
});
self.topology = Some(msg.data_decode()?);
self.channel.setup = true;
self.channel.send(&Message::ack(msg.msgid()));
Ok(())
}
fn recv_ctl(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
if msg.has_payload() && msg.data().is_some() {
let _ = msg.data_decode()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::communication::{base64_encode, GW_PICKLE_PROTOCOL};
#[test]
fn test_responder_routing_message_stamps_srcid() {
let responder = TreeWorkerResponder::new(42);
let arg = RoutingEvent {
event: "connect".into(),
gateway: "gw2".into(),
targets: "node[10-20]".into(),
};
let msg = responder.routing_message(&arg);
assert_eq!(msg.ident(), "RTR");
assert_eq!(msg.srcid(), Some(42));
match msg {
Message::Routing {
event,
gateway,
targets,
..
} => {
assert_eq!(event, "connect");
assert_eq!(gateway, "gw2");
assert_eq!(targets, "node[10-20]");
}
other => panic!("expected Routing, got {:?}", other),
}
}
#[test]
fn test_routing_event_has_no_srcid_field() {
let arg = RoutingEvent {
event: "reroute".into(),
gateway: "gw1".into(),
targets: "node1".into(),
};
let responder = TreeWorkerResponder::new(7);
let msg = responder.routing_message(&arg);
assert_eq!(msg.srcid(), Some(7));
}
#[test]
fn test_responder_ev_routing_sends_on_channel() {
let responder = TreeWorkerResponder::new(3);
let mut chan = Channel::new(false);
let arg = RoutingEvent {
event: "connect".into(),
gateway: "gw1".into(),
targets: "node[1-5]".into(),
};
responder.ev_routing(&mut chan, &arg);
let out = String::from_utf8(chan.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"RTR\""));
assert!(out.contains("srcid=\"3\""));
assert!(out.contains("event=\"connect\""));
assert!(out.contains("targets=\"node[1-5]\""));
}
#[test]
fn test_gateway_channel_open_close() {
let mut gw = GatewayChannel::new();
assert!(!gw.channel.opened);
assert!(!gw.channel.setup);
gw.feed("<channel version=\"1.10.1\">");
assert!(gw.channel.opened);
assert!(!gw.channel.setup);
gw.feed("</channel>");
assert!(gw.channel.closed);
assert!(!gw.channel.opened);
}
#[test]
fn test_gateway_channel_cfg_ack() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
let mut cfg = Message::configuration("n1");
cfg.data_encode(b"topology bytes");
let cfg_xml = String::from_utf8(cfg.xml()).unwrap();
gw.feed(&cfg_xml);
assert!(gw.channel.setup);
assert_eq!(gw.nodename.as_deref(), Some("n1"));
assert_eq!(gw.topology.as_deref(), Some(b"topology bytes".as_ref()));
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ACK\""));
assert!(out.contains(&format!("ack=\"{}\"", cfg.msgid())));
}
#[test]
fn test_gateway_channel_err_pickle_proto_payload() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
let payload = base64_encode(b"\x80\x07spam");
gw.feed(&format!(
"<message msgid=\"14\" type=\"CFG\" gateway=\"n1\">{}</message>",
payload
));
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(
out.contains(
"reason=\"Message CFG has an invalid payload (unsupported pickle protocol: 7)\""
),
"unexpected reason in: {}",
out
);
assert!(gw.channel.closed);
assert!(!gw.channel.setup);
assert!(7 > GW_PICKLE_PROTOCOL);
}
#[test]
fn test_gateway_channel_err_no_type_msg() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<message msgid=\"24\"></message>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(out.contains("reason=\"Unknown message with no type\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_empty_type_msg() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<message msgid=\"24\" type=\"\"></message>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("reason=\"Unknown message with no type\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_unknown_msg() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<message msgid=\"24\" type=\"ABC\"></message>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("reason=\"Unknown message type ABC\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_unknown_tag() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<foo></foo>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(out.contains("reason=\"Invalid starting tag foo\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_malformed_xml() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<message type=\"ABC\"</message>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(out.contains("reason=\"Parse error: not well-formed (invalid token)\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_dup_start() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
assert!(gw.channel.opened);
gw.feed("<channel version=\"1.10.1\">");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(out.contains("unexpected message: Message CHA"));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_err_missing_payload() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
gw.feed("<message msgid=\"14\" type=\"CFG\" gateway=\"n1\"></message>");
let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
assert!(out.contains("type=\"ERR\""));
assert!(out.contains("reason=\"Message CFG has an invalid payload\""));
assert!(gw.channel.closed);
}
#[test]
fn test_gateway_channel_cfg_hostname_fallback() {
let mut gw = GatewayChannel::new();
gw.feed("<channel version=\"1.10.1\">");
let mut cfg = Message::configuration("");
cfg.data_encode(b"topo");
gw.feed(&String::from_utf8_lossy(&cfg.xml()));
assert!(gw.channel.setup);
assert_eq!(gw.nodename.as_deref(), Some(short_hostname().as_str()));
}
}