c854c74eb0a46fb97cef5911f7e2fcedf66ac13b
[stakingWatchdogWatchdog.git] / src / main.rs
1 use std::{
2 net::UdpSocket,
3 thread,
4 time::{self, Duration},
5 };
6
7 use anyhow::Result;
8 use lettre::{
9 message::header::ContentType, transport::smtp::authentication::Credentials, Message,
10 SmtpTransport, Transport,
11 };
12 use rand::{rngs::ThreadRng, Rng};
13
14 use crate::config::Config;
15
16 mod config;
17
18 const FILE_CONF: &str = "config.ron";
19 const PING_PERIOD: Duration = Duration::from_secs(5); // 5 s.
20 const EMAIL_RESEND_PERIOD: Duration = Duration::from_secs(2 * 60 * 60); // 2 h.
21 const STATE_PRINT_PERIOD: Duration = Duration::from_secs(15 * 60); // 15 min.
22 const SOCKET_TIMEOUT: Duration = Duration::from_secs(7);
23
24 fn main() -> Result<()> {
25 println!("Staking Watchdog Watchdog");
26
27 let config = Config::read(FILE_CONF)?;
28
29 println!(
30 "Configuration: {:?}",
31 Config {
32 smtp_password: "*****".to_string(),
33 ..config.clone()
34 }
35 );
36
37 let mut time_last_email_send = time::Instant::now() - EMAIL_RESEND_PERIOD;
38 let mut time_last_state_printed = time::Instant::now() - STATE_PRINT_PERIOD;
39 let mut error_state = false;
40
41 let mut rng = rand::thread_rng();
42
43 let mut number_of_pings = 0;
44 let mut total_ping_duration = Duration::default();
45
46 let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
47 socket.connect("192.168.2.102:8739").unwrap();
48 socket.set_nonblocking(false).unwrap();
49 socket.set_read_timeout(Some(SOCKET_TIMEOUT)).unwrap();
50 socket.set_write_timeout(Some(SOCKET_TIMEOUT)).unwrap();
51
52 loop {
53 let time_beginning_loop = time::Instant::now();
54
55 match ping(&socket, &mut rng) {
56 Ok(t) => {
57 total_ping_duration += t;
58 number_of_pings += 1;
59
60 if error_state {
61 error_state = false;
62 println!("End of erroneous state");
63 }
64
65 if time::Instant::now() - time_last_state_printed >= STATE_PRINT_PERIOD {
66 println!(
67 "No error detected. Mean of ping time: {} μs",
68 total_ping_duration.as_micros() / number_of_pings
69 );
70 total_ping_duration = Duration::default();
71 number_of_pings = 0;
72 time_last_state_printed = time::Instant::now();
73 }
74 }
75 Err(error) => {
76 error_state = true;
77 println!("Error: {:?}", error);
78 if time::Instant::now() - time_last_email_send >= EMAIL_RESEND_PERIOD {
79 // Send e-mail.
80 println!("Sending email...");
81 match send_email(
82 "Watchdog Watchdog ERROR",
83 &format!("Error: {:?}", error),
84 &config.smtp_login,
85 &config.smtp_password,
86 ) {
87 Err(email_error) => println!("Error sending email: {:?}", email_error),
88 _ => {
89 println!("Email successfully sent");
90 time_last_email_send = time::Instant::now();
91 }
92 }
93 }
94 }
95 }
96
97 let elapsed = time::Instant::now() - time_beginning_loop;
98
99 if elapsed < PING_PERIOD {
100 let to_wait = PING_PERIOD - elapsed;
101 thread::sleep(to_wait);
102 }
103 }
104 }
105
106 #[derive(Debug)]
107 enum PingError {
108 SocketReceiveError(std::io::Error),
109 SocketSendError(std::io::Error),
110 WrongMessageReceived(String),
111 }
112
113 fn ping(socket: &UdpSocket, rng: &mut ThreadRng) -> std::result::Result<Duration, PingError> {
114 loop {
115 let number: u64 = rng.gen();
116 let mut buffer = number.to_le_bytes();
117 let now = time::Instant::now();
118 match socket.send(&buffer) {
119 Ok(_size_sent) => {
120 buffer.fill(0);
121 match socket.recv(&mut buffer) {
122 Ok(size_received) => {
123 if size_received == 8 {
124 let number_received = u64::from_le_bytes(buffer);
125 if number_received == number {
126 return Ok(time::Instant::now() - now);
127 } else {
128 return Err(PingError::WrongMessageReceived(format!(
129 "Message number receceived ({}) is not equal to the one sent ({})",
130 number_received, number
131 )));
132 }
133 } else {
134 return Err(PingError::WrongMessageReceived(format!(
135 "Size of packet must be 8, received size: {}",
136 size_received
137 )));
138 }
139 }
140 // FIXME.
141 // Test the kind because sometime 'recv' returns
142 // '(Os { code: 11, kind: WouldBlock, message: "Resource temporarily unavailable" }'.
143 // Try again in this case.
144 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
145 println!("WouldBlock error: {}", error)
146 }
147 Err(error) => return Err(PingError::SocketReceiveError(error)),
148 }
149 }
150 Err(error) => return Err(PingError::SocketSendError(error)),
151 }
152 }
153 }
154
155 fn send_email(title: &str, body: &str, login: &str, pass: &str) -> Result<()> {
156 let email = Message::builder()
157 .message_id(None)
158 .from("Staking Watchdog Watchdog <redmine@d-lan.net>".parse()?)
159 .to("Greg Burri <greg.burri@gmail.com>".parse()?)
160 .subject(title)
161 .header(ContentType::TEXT_PLAIN)
162 .body(body.to_string())?;
163
164 let creds = Credentials::new(login.to_string(), pass.to_string());
165
166 // Open a remote connection to gmail
167 let mailer = SmtpTransport::relay("mail.gandi.net")?
168 .credentials(creds)
169 .build();
170
171 // Send the email
172 let response = mailer.send(&email)?;
173
174 println!("{:?}", response);
175
176 Ok(())
177 }