1use crate::communication::{Channel, Message, MessageProcessingError};
18
19#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct RoutingEvent {
30 pub event: String,
32 pub gateway: String,
34 pub targets: String,
36}
37
38#[derive(Debug)]
44pub struct TreeWorkerResponder {
45 srcwkr: u64,
47}
48
49impl TreeWorkerResponder {
50 pub fn new(srcwkr: u64) -> Self {
52 Self { srcwkr }
53 }
54
55 pub fn srcwkr(&self) -> u64 {
57 self.srcwkr
58 }
59
60 pub fn routing_message(&self, arg: &RoutingEvent) -> Message {
66 Message::routing(&arg.event, &arg.gateway, &arg.targets, self.srcwkr)
67 }
68
69 pub fn ev_routing(&self, chan: &mut Channel, arg: &RoutingEvent) {
72 chan.send(&self.routing_message(arg));
73 }
74}
75
76fn short_hostname() -> String {
79 hostname::get()
80 .map(|h| h.to_string_lossy().into_owned())
81 .unwrap_or_default()
82 .split('.')
83 .next()
84 .unwrap_or_default()
85 .to_string()
86}
87
88#[derive(Debug)]
97pub struct GatewayChannel {
98 pub channel: Channel,
100 pub nodename: Option<String>,
102 pub topology: Option<Vec<u8>>,
104}
105
106impl Default for GatewayChannel {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112impl GatewayChannel {
113 pub fn new() -> Self {
115 Self {
116 channel: Channel::new(false), nodename: None,
118 topology: None,
119 }
120 }
121
122 pub fn close(&mut self) {
124 self.channel.close();
125 }
126
127 pub fn feed(&mut self, data: &str) {
131 match self.channel.feed(data) {
132 Ok(messages) => {
133 for msg in messages {
134 self.recv(msg);
135 }
136 }
137 Err(err) => {
138 self.channel.handle_channel_error("", &err);
140 }
141 }
142 }
143
144 pub fn recv(&mut self, msg: Message) {
151 if let Err(err) = self.dispatch(&msg) {
152 self.channel.send(&Message::error(&err.to_string()));
153 self.close();
154 }
155 }
156
157 fn dispatch(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
161 if msg.ident() == "END" {
162 self.close();
163 return Ok(());
164 }
165 if self.channel.setup {
166 return self.recv_ctl(msg);
167 }
168 if self.channel.opened {
169 return self.recv_cfg(msg);
170 }
171 if msg.ident() == "CHA" {
172 self.channel.opened = true;
173 return Ok(());
174 }
175 Err(MessageProcessingError::UnexpectedMessage(msg.to_string()))
176 }
177
178 fn recv_cfg(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
185 if msg.ident() != "CFG" {
186 return Err(MessageProcessingError::UnexpectedMessage(msg.to_string()));
187 }
188
189 let gateway = match msg {
192 Message::Configuration { gateway, .. } => gateway.clone(),
193 _ => String::new(),
194 };
195 self.nodename = Some(if gateway.is_empty() {
196 short_hostname()
197 } else {
198 gateway
199 });
200
201 self.topology = Some(msg.data_decode()?);
204
205 self.channel.setup = true;
206 self.channel.send(&Message::ack(msg.msgid()));
208 Ok(())
209 }
210
211 fn recv_ctl(&mut self, msg: &Message) -> Result<(), MessageProcessingError> {
218 if msg.has_payload() && msg.data().is_some() {
219 let _ = msg.data_decode()?;
221 }
222 Ok(())
223 }
224}
225
226#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::communication::{base64_encode, GW_PICKLE_PROTOCOL};
234
235 #[test]
241 fn test_responder_routing_message_stamps_srcid() {
242 let responder = TreeWorkerResponder::new(42);
243 let arg = RoutingEvent {
244 event: "connect".into(),
245 gateway: "gw2".into(),
246 targets: "node[10-20]".into(),
247 };
248 let msg = responder.routing_message(&arg);
249 assert_eq!(msg.ident(), "RTR");
250 assert_eq!(msg.srcid(), Some(42));
251 match msg {
252 Message::Routing {
253 event,
254 gateway,
255 targets,
256 ..
257 } => {
258 assert_eq!(event, "connect");
259 assert_eq!(gateway, "gw2");
260 assert_eq!(targets, "node[10-20]");
261 }
262 other => panic!("expected Routing, got {:?}", other),
263 }
264 }
265
266 #[test]
269 fn test_routing_event_has_no_srcid_field() {
270 let arg = RoutingEvent {
271 event: "reroute".into(),
272 gateway: "gw1".into(),
273 targets: "node1".into(),
274 };
275 let responder = TreeWorkerResponder::new(7);
276 let msg = responder.routing_message(&arg);
277 assert_eq!(msg.srcid(), Some(7));
279 }
280
281 #[test]
283 fn test_responder_ev_routing_sends_on_channel() {
284 let responder = TreeWorkerResponder::new(3);
285 let mut chan = Channel::new(false);
286 let arg = RoutingEvent {
287 event: "connect".into(),
288 gateway: "gw1".into(),
289 targets: "node[1-5]".into(),
290 };
291 responder.ev_routing(&mut chan, &arg);
292 let out = String::from_utf8(chan.take_outgoing().unwrap()).unwrap();
293 assert!(out.contains("type=\"RTR\""));
294 assert!(out.contains("srcid=\"3\""));
295 assert!(out.contains("event=\"connect\""));
296 assert!(out.contains("targets=\"node[1-5]\""));
297 }
298
299 #[test]
303 fn test_gateway_channel_open_close() {
304 let mut gw = GatewayChannel::new();
305 assert!(!gw.channel.opened);
306 assert!(!gw.channel.setup);
307
308 gw.feed("<channel version=\"1.10.1\">");
309 assert!(gw.channel.opened);
310 assert!(!gw.channel.setup);
311
312 gw.feed("</channel>");
313 assert!(gw.channel.closed);
314 assert!(!gw.channel.opened);
315 }
316
317 #[test]
319 fn test_gateway_channel_cfg_ack() {
320 let mut gw = GatewayChannel::new();
321 gw.feed("<channel version=\"1.10.1\">");
322
323 let mut cfg = Message::configuration("n1");
324 cfg.data_encode(b"topology bytes");
325 let cfg_xml = String::from_utf8(cfg.xml()).unwrap();
326 gw.feed(&cfg_xml);
327
328 assert!(gw.channel.setup);
329 assert_eq!(gw.nodename.as_deref(), Some("n1"));
330 assert_eq!(gw.topology.as_deref(), Some(b"topology bytes".as_ref()));
331
332 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
333 assert!(out.contains("type=\"ACK\""));
334 assert!(out.contains(&format!("ack=\"{}\"", cfg.msgid())));
335 }
336
337 #[test]
342 fn test_gateway_channel_err_pickle_proto_payload() {
343 let mut gw = GatewayChannel::new();
344 gw.feed("<channel version=\"1.10.1\">");
345
346 let payload = base64_encode(b"\x80\x07spam");
348 gw.feed(&format!(
349 "<message msgid=\"14\" type=\"CFG\" gateway=\"n1\">{}</message>",
350 payload
351 ));
352
353 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
354 assert!(out.contains("type=\"ERR\""));
355 assert!(
356 out.contains(
357 "reason=\"Message CFG has an invalid payload (unsupported pickle protocol: 7)\""
358 ),
359 "unexpected reason in: {}",
360 out
361 );
362 assert!(gw.channel.closed);
363 assert!(!gw.channel.setup);
364 assert!(7 > GW_PICKLE_PROTOCOL);
366 }
367
368 #[test]
372 fn test_gateway_channel_err_no_type_msg() {
373 let mut gw = GatewayChannel::new();
374 gw.feed("<channel version=\"1.10.1\">");
375 gw.feed("<message msgid=\"24\"></message>");
376
377 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
378 assert!(out.contains("type=\"ERR\""));
379 assert!(out.contains("reason=\"Unknown message with no type\""));
380 assert!(gw.channel.closed);
381 }
382
383 #[test]
385 fn test_gateway_channel_err_empty_type_msg() {
386 let mut gw = GatewayChannel::new();
387 gw.feed("<channel version=\"1.10.1\">");
388 gw.feed("<message msgid=\"24\" type=\"\"></message>");
389
390 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
391 assert!(out.contains("reason=\"Unknown message with no type\""));
392 assert!(gw.channel.closed);
393 }
394
395 #[test]
398 fn test_gateway_channel_err_unknown_msg() {
399 let mut gw = GatewayChannel::new();
400 gw.feed("<channel version=\"1.10.1\">");
401 gw.feed("<message msgid=\"24\" type=\"ABC\"></message>");
402
403 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
404 assert!(out.contains("reason=\"Unknown message type ABC\""));
405 assert!(gw.channel.closed);
406 }
407
408 #[test]
411 fn test_gateway_channel_err_unknown_tag() {
412 let mut gw = GatewayChannel::new();
413 gw.feed("<channel version=\"1.10.1\">");
414 gw.feed("<foo></foo>");
415
416 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
417 assert!(out.contains("type=\"ERR\""));
418 assert!(out.contains("reason=\"Invalid starting tag foo\""));
419 assert!(gw.channel.closed);
420 }
421
422 #[test]
425 fn test_gateway_channel_err_malformed_xml() {
426 let mut gw = GatewayChannel::new();
427 gw.feed("<channel version=\"1.10.1\">");
428 gw.feed("<message type=\"ABC\"</message>");
429
430 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
431 assert!(out.contains("type=\"ERR\""));
432 assert!(out.contains("reason=\"Parse error: not well-formed (invalid token)\""));
433 assert!(gw.channel.closed);
434 }
435
436 #[test]
439 fn test_gateway_channel_err_dup_start() {
440 let mut gw = GatewayChannel::new();
441 gw.feed("<channel version=\"1.10.1\">");
442 assert!(gw.channel.opened);
443
444 gw.feed("<channel version=\"1.10.1\">");
447 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
448 assert!(out.contains("type=\"ERR\""));
449 assert!(out.contains("unexpected message: Message CHA"));
450 assert!(gw.channel.closed);
451 }
452
453 #[test]
456 fn test_gateway_channel_err_missing_payload() {
457 let mut gw = GatewayChannel::new();
458 gw.feed("<channel version=\"1.10.1\">");
459 gw.feed("<message msgid=\"14\" type=\"CFG\" gateway=\"n1\"></message>");
460
461 let out = String::from_utf8(gw.channel.take_outgoing().unwrap()).unwrap();
462 assert!(out.contains("type=\"ERR\""));
463 assert!(out.contains("reason=\"Message CFG has an invalid payload\""));
464 assert!(gw.channel.closed);
465 }
466
467 #[test]
470 fn test_gateway_channel_cfg_hostname_fallback() {
471 let mut gw = GatewayChannel::new();
472 gw.feed("<channel version=\"1.10.1\">");
473 let mut cfg = Message::configuration("");
474 cfg.data_encode(b"topo");
475 gw.feed(&String::from_utf8_lossy(&cfg.xml()));
476
477 assert!(gw.channel.setup);
478 assert_eq!(gw.nodename.as_deref(), Some(short_hostname().as_str()));
480 }
481}