1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Keep a connection alive
8//!
9//! This example demonstrates that tokio_xmpp will keep a connection alive
10//! as good as it can, transparently reconnecting on interruptions of the TCP
11//! stream.
12
13use core::str::FromStr;
14use core::time::Duration;
15use std::env::args;
16use std::process::exit;
17
18use futures::StreamExt;
19
20#[cfg(feature = "rustls-any-backend")]
21use tokio_xmpp::rustls;
22use tokio_xmpp::{
23 connect::{DnsConfig, StartTlsServerConnector},
24 parsers::{
25 iq::Iq,
26 jid::{BareJid, Jid},
27 ping,
28 },
29 stanzastream::StanzaStream,
30 xmlstream::Timeouts,
31};
32
33#[cfg(all(
34 feature = "rustls-any-backend",
35 not(any(feature = "aws_lc_rs", feature = "ring"))
36))]
37compile_error!("using rustls (e.g. via the ktls feature) needs an enabled rustls backend feature (either aws_lc_rs or ring).");
38
39#[tokio::main]
40async fn main() {
41 env_logger::init();
42
43 #[cfg(all(feature = "aws_lc_rs", not(feature = "ring")))]
44 rustls::crypto::aws_lc_rs::default_provider()
45 .install_default()
46 .expect("failed to install rustls crypto provider");
47
48 #[cfg(all(feature = "ring"))]
49 rustls::crypto::ring::default_provider()
50 .install_default()
51 .expect("failed to install rustls crypto provider");
52
53 let args: Vec<String> = args().collect();
54 if args.len() != 3 {
55 println!("Usage: {} <jid> <password>", args[0]);
56 exit(1);
57 }
58 let jid = BareJid::from_str(&args[1]).expect(&format!("Invalid JID: {}", &args[1]));
59 let password = &args[2];
60
61 let mut timeouts = Timeouts::tight();
62 timeouts.read_timeout = Duration::new(5, 0);
63
64 let mut stream = StanzaStream::new_c2s(
65 StartTlsServerConnector::from(DnsConfig::UseSrv {
66 host: jid.domain().as_str().to_owned(),
67 srv: "_xmpp-client._tcp".to_owned(),
68 fallback_port: 5222,
69 }),
70 jid.clone().into(),
71 password.clone(),
72 timeouts,
73 16,
74 );
75 let domain: Jid = jid.domain().to_owned().into();
76 let mut ping_timer = tokio::time::interval(Duration::new(5, 0));
77 let mut ping_ctr: u64 = rand::random();
78 let signal = tokio::signal::ctrl_c();
79 tokio::pin!(signal);
80 loop {
81 tokio::select! {
82 _ = &mut signal => {
83 log::info!("Ctrl+C pressed, shutting down cleanly.");
84 break;
85 }
86 _ = ping_timer.tick() => {
87 log::info!("sending ping for fun & profit");
88 ping_ctr = ping_ctr.wrapping_add(1);
89 let iq = Iq::from_get(format!("ping-{}", ping_ctr), ping::Ping).with_to(domain.clone());
90 stream.send(Box::new(iq.into())).await;
91 }
92 ev = stream.next() => match ev {
93 Some(ev) => {
94 log::info!("{:?}", ev);
95 }
96 None => {
97 panic!("stream terminated unexpectedly!");
98 }
99 }
100 }
101 }
102 stream.close().await;
103}