From 52afe6bbd185d8598782f920281657f2e767d741 Mon Sep 17 00:00:00 2001 From: Aram Peres Date: Sat, 31 Jul 2021 03:28:32 -0400 Subject: [PATCH] Add debug mode to rupsc. Fixes to parsing. --- nut-client/examples/blocking.rs | 1 + nut-client/src/blocking/mod.rs | 107 +++++++++++++++++--------------- nut-client/src/cmd.rs | 45 ++++++++++---- nut-client/src/config.rs | 14 ++++- rupsc/src/cmd.rs | 21 ++++--- rupsc/src/main.rs | 16 +++-- rupsc/src/parser.rs | 4 +- 7 files changed, 129 insertions(+), 79 deletions(-) diff --git a/nut-client/examples/blocking.rs b/nut-client/examples/blocking.rs index 4256f71..06cef65 100644 --- a/nut-client/examples/blocking.rs +++ b/nut-client/examples/blocking.rs @@ -19,6 +19,7 @@ fn main() -> nut_client::Result<()> { let config = ConfigBuilder::new() .with_host(Host::Tcp(addr)) .with_auth(auth) + .with_debug(false) // Turn this on for debugging network chatter .build(); let mut conn = Connection::new(config)?; diff --git a/nut-client/src/blocking/mod.rs b/nut-client/src/blocking/mod.rs index a824942..8b9a203 100644 --- a/nut-client/src/blocking/mod.rs +++ b/nut-client/src/blocking/mod.rs @@ -3,7 +3,7 @@ use std::io::{BufRead, BufReader, Write}; use std::net::{SocketAddr, TcpStream}; use crate::cmd::{Command, Response}; -use crate::{ClientError, Config, Host, NutError, Variable}; +use crate::{Config, Host, NutError, Variable}; /// A blocking NUT client connection. pub enum Connection { @@ -72,58 +72,76 @@ impl TcpConnection { fn login(&mut self) -> crate::Result<()> { if let Some(auth) = &self.config.auth { // Pass username and check for 'OK' - Self::write_cmd(&mut self.tcp_stream, Command::SetUsername(&auth.username))?; - Self::read_response(&mut self.tcp_stream)?.expect_ok()?; + Self::write_cmd( + &mut self.tcp_stream, + Command::SetUsername(&auth.username), + self.config.debug, + )?; + Self::read_response(&mut self.tcp_stream, self.config.debug)?.expect_ok()?; // Pass password and check for 'OK' if let Some(password) = &auth.password { - Self::write_cmd(&mut self.tcp_stream, Command::SetPassword(password))?; - Self::read_response(&mut self.tcp_stream)?.expect_ok()?; + Self::write_cmd( + &mut self.tcp_stream, + Command::SetPassword(password), + self.config.debug, + )?; + Self::read_response(&mut self.tcp_stream, self.config.debug)?.expect_ok()?; } } Ok(()) } fn list_ups(&mut self) -> crate::Result> { - Self::write_cmd(&mut self.tcp_stream, Command::List(&["UPS"]))?; - let list = Self::read_list(&mut self.tcp_stream, &["UPS"])?; + Self::write_cmd( + &mut self.tcp_stream, + Command::List(&["UPS"]), + self.config.debug, + )?; + let list = Self::read_list(&mut self.tcp_stream, &["UPS"], self.config.debug)?; - Ok(list - .into_iter() - .map(|mut row| (row.remove(0), row.remove(0))) - .collect()) + list.into_iter().map(|row| row.expect_ups()).collect() } fn list_vars(&mut self, ups_name: &str) -> crate::Result> { let query = &["VAR", ups_name]; - Self::write_cmd(&mut self.tcp_stream, Command::List(query))?; - let list = Self::read_list(&mut self.tcp_stream, query)?; + Self::write_cmd( + &mut self.tcp_stream, + Command::List(query), + self.config.debug, + )?; + let list = Self::read_list(&mut self.tcp_stream, query, self.config.debug)?; - Ok(list - .into_iter() - .map(|mut row| (row.remove(0), row.remove(0))) - .collect()) + list.into_iter().map(|row| row.expect_var()).collect() } fn get_var(&mut self, ups_name: &str, variable: &str) -> crate::Result<(String, String)> { let query = &["VAR", ups_name, variable]; - Self::write_cmd(&mut self.tcp_stream, Command::Get(query))?; + Self::write_cmd(&mut self.tcp_stream, Command::Get(query), self.config.debug)?; - let resp = Self::read_response(&mut self.tcp_stream)?; - let (name, value) = resp.expect_var()?; - Ok((name.into(), value.into())) + let resp = Self::read_response(&mut self.tcp_stream, self.config.debug)?; + resp.expect_var() } - fn write_cmd(stream: &mut TcpStream, line: Command) -> crate::Result<()> { + fn write_cmd(stream: &mut TcpStream, line: Command, debug: bool) -> crate::Result<()> { let line = format!("{}\n", line); + if debug { + eprint!("DEBUG -> {}", line); + } stream.write_all(line.as_bytes())?; stream.flush()?; Ok(()) } - fn parse_line(reader: &mut BufReader<&mut TcpStream>) -> crate::Result> { + fn parse_line( + reader: &mut BufReader<&mut TcpStream>, + debug: bool, + ) -> crate::Result> { let mut raw = String::new(); reader.read_line(&mut raw)?; + if debug { + eprint!("DEBUG <- {}", raw); + } raw = raw[..raw.len() - 1].to_string(); // Strip off \n // Parse args by splitting whitespace, minding quotes for args with multiple words @@ -133,46 +151,35 @@ impl TcpConnection { Ok(args) } - fn read_response(stream: &mut TcpStream) -> crate::Result { + fn read_response(stream: &mut TcpStream, debug: bool) -> crate::Result { let mut reader = io::BufReader::new(stream); - let args = Self::parse_line(&mut reader)?; + let args = Self::parse_line(&mut reader, debug)?; Response::from_args(args) } - fn read_list(stream: &mut TcpStream, query: &[&str]) -> crate::Result>> { + fn read_list( + stream: &mut TcpStream, + query: &[&str], + debug: bool, + ) -> crate::Result> { let mut reader = io::BufReader::new(stream); - let args = Self::parse_line(&mut reader)?; + let args = Self::parse_line(&mut reader, debug)?; Response::from_args(args)?.expect_begin_list(query)?; - let mut lines: Vec> = Vec::new(); + let mut lines: Vec = Vec::new(); loop { - let mut args = Self::parse_line(&mut reader)?; - let resp = Response::from_args(args.clone()); + let args = Self::parse_line(&mut reader, debug)?; + let resp = Response::from_args(args)?; - if let Ok(resp) = resp { - resp.expect_end_list(query)?; - break; - } else { - let err = resp.unwrap_err(); - if let ClientError::Nut(err) = err { - if let NutError::UnknownResponseType(_) = err { - // Likely an item entry, let's check... - if args.len() < query.len() || &args[0..query.len()] != query { - return Err(ClientError::Nut(err)); - } else { - let args = args.drain(query.len()..).collect(); - lines.push(args); - continue; - } - } else { - return Err(ClientError::Nut(err)); - } - } else { - return Err(err); + match resp { + Response::EndList(_) => { + break; } + _ => lines.push(resp), } } + Ok(lines) } } diff --git a/nut-client/src/cmd.rs b/nut-client/src/cmd.rs index 51c8350..c1185f4 100644 --- a/nut-client/src/cmd.rs +++ b/nut-client/src/cmd.rs @@ -51,8 +51,10 @@ pub enum Response { BeginList(String), /// Marks the end of a list response. EndList(String), - /// A variable response. + /// A variable (VAR) response. Var(String, String), + /// A UPS (UPS) response. + Ups(String, String), } impl Response { @@ -115,6 +117,13 @@ impl Response { } } "VAR" => { + let _var_device = if args.is_empty() { + Err(ClientError::from(NutError::Generic( + "Unspecified VAR device name in response".into(), + ))) + } else { + Ok(args.remove(0)) + }?; let var_name = if args.is_empty() { Err(ClientError::from(NutError::Generic( "Unspecified VAR name in response".into(), @@ -131,6 +140,23 @@ impl Response { }?; Ok(Response::Var(var_name, var_value)) } + "UPS" => { + let name = if args.is_empty() { + Err(ClientError::from(NutError::Generic( + "Unspecified UPS name in response".into(), + ))) + } else { + Ok(args.remove(0)) + }?; + let description = if args.is_empty() { + Err(ClientError::from(NutError::Generic( + "Unspecified UPS description in response".into(), + ))) + } else { + Ok(args.remove(0)) + }?; + Ok(Response::Ups(name, description)) + } _ => Err(NutError::UnknownResponseType(cmd_name).into()), } } @@ -155,22 +181,17 @@ impl Response { } } - pub fn expect_end_list(self, expected_args: &[&str]) -> crate::Result { - let expected_args = shell_words::join(expected_args); - if let Self::EndList(args) = &self { - if &expected_args == args { - Ok(self) - } else { - Err(NutError::UnexpectedResponse.into()) - } + pub fn expect_var(&self) -> crate::Result<(String, String)> { + if let Self::Var(name, value) = &self { + Ok((name.to_owned(), value.to_owned())) } else { Err(NutError::UnexpectedResponse.into()) } } - pub fn expect_var(&self) -> crate::Result<(&str, &str)> { - if let Self::Var(name, value) = &self { - Ok((name, value)) + pub fn expect_ups(&self) -> crate::Result<(String, String)> { + if let Self::Ups(name, description) = &self { + Ok((name.to_owned(), description.to_owned())) } else { Err(NutError::UnexpectedResponse.into()) } diff --git a/nut-client/src/config.rs b/nut-client/src/config.rs index bbf45f8..89d6d0b 100644 --- a/nut-client/src/config.rs +++ b/nut-client/src/config.rs @@ -12,7 +12,7 @@ pub enum Host { impl Default for Host { fn default() -> Self { - let addr = (String::from("localhost"), 3493) + let addr = (String::from("127.0.0.1"), 3493) .to_socket_addrs() .expect("Failed to create local UPS socket address. This is a bug.") .next() @@ -58,15 +58,17 @@ pub struct Config { pub(crate) host: Host, pub(crate) auth: Option, pub(crate) timeout: Duration, + pub(crate) debug: bool, } impl Config { /// Creates a connection configuration. - pub fn new(host: Host, auth: Option, timeout: Duration) -> Self { + pub fn new(host: Host, auth: Option, timeout: Duration, debug: bool) -> Self { Config { host, auth, timeout, + debug, } } } @@ -77,6 +79,7 @@ pub struct ConfigBuilder { host: Option, auth: Option, timeout: Option, + debug: Option, } impl ConfigBuilder { @@ -104,12 +107,19 @@ impl ConfigBuilder { self } + /// Enables debugging network calls by printing to stderr. + pub fn with_debug(mut self, debug: bool) -> Self { + self.debug = Some(debug); + self + } + /// Builds the configuration with this builder. pub fn build(self) -> Config { Config::new( self.host.unwrap_or_default(), self.auth, self.timeout.unwrap_or_else(|| Duration::from_secs(5)), + self.debug.unwrap_or(false), ) } } diff --git a/rupsc/src/cmd.rs b/rupsc/src/cmd.rs index d3d90a3..ad911ae 100644 --- a/rupsc/src/cmd.rs +++ b/rupsc/src/cmd.rs @@ -4,11 +4,11 @@ use core::convert::TryInto; use nut_client::blocking::Connection; /// Lists each UPS on the upsd server, one per line. -pub fn list_devices(server: UpsdName, verbose: bool) -> anyhow::Result<()> { - let mut conn = connect(server)?; +pub fn list_devices(server: UpsdName, with_description: bool, debug: bool) -> anyhow::Result<()> { + let mut conn = connect(server, debug)?; for (name, description) in conn.list_ups()? { - if verbose { + if with_description { println!("{}: {}", name, description); } else { println!("{}", name); @@ -18,11 +18,11 @@ pub fn list_devices(server: UpsdName, verbose: bool) -> anyhow::Result<()> { Ok(()) } -pub fn print_variable(server: UpsdName, variable: &str) -> anyhow::Result<()> { +pub fn print_variable(server: UpsdName, variable: &str, debug: bool) -> anyhow::Result<()> { let ups_name = server .upsname .with_context(|| "ups name must be specified: [@[:]]")?; - let mut conn = connect(server)?; + let mut conn = connect(server, debug)?; let variable = conn.get_var(ups_name, variable)?; println!("{}", variable.value()); @@ -30,11 +30,11 @@ pub fn print_variable(server: UpsdName, variable: &str) -> anyhow::Result<()> { Ok(()) } -pub fn list_variables(server: UpsdName) -> anyhow::Result<()> { +pub fn list_variables(server: UpsdName, debug: bool) -> anyhow::Result<()> { let ups_name = server .upsname .with_context(|| "ups name must be specified: [@[:]]")?; - let mut conn = connect(server)?; + let mut conn = connect(server, debug)?; for var in conn.list_vars(ups_name)? { println!("{}", var); @@ -43,8 +43,11 @@ pub fn list_variables(server: UpsdName) -> anyhow::Result<()> { Ok(()) } -fn connect(server: UpsdName) -> anyhow::Result { +fn connect(server: UpsdName, debug: bool) -> anyhow::Result { let host = server.try_into()?; - let config = nut_client::ConfigBuilder::new().with_host(host).build(); + let config = nut_client::ConfigBuilder::new() + .with_host(host) + .with_debug(debug) + .build(); Connection::new(config).with_context(|| format!("Failed to connect to upsd: {}", server)) } diff --git a/rupsc/src/main.rs b/rupsc/src/main.rs index 38c77bc..f221fff 100644 --- a/rupsc/src/main.rs +++ b/rupsc/src/main.rs @@ -37,6 +37,12 @@ fn main() -> anyhow::Result<()> { .takes_value(false) .help("Lists each client connected on , one per line."), ) + .arg( + Arg::with_name("debug") + .short("D") + .takes_value(false) + .help("Enables debug mode (logs network commands to stderr)."), + ) .arg( Arg::with_name("upsd-server") .required(false) @@ -56,12 +62,14 @@ fn main() -> anyhow::Result<()> { |s| s.try_into().with_context(|| "Invalid upsd server name"), )?; + let debug = args.is_present("debug"); + if args.is_present("list") { - return cmd::list_devices(server, false); + return cmd::list_devices(server, false, debug); } if args.is_present("list-full") { - return cmd::list_devices(server, true); + return cmd::list_devices(server, true, debug); } if args.is_present("clients") { @@ -70,8 +78,8 @@ fn main() -> anyhow::Result<()> { // Fallback: prints one variable (or all of them) if let Some(variable) = args.value_of("variable") { - cmd::print_variable(server, variable) + cmd::print_variable(server, variable, debug) } else { - cmd::list_variables(server) + cmd::list_variables(server, debug) } } diff --git a/rupsc/src/parser.rs b/rupsc/src/parser.rs index 2c65ef4..bf5779d 100644 --- a/rupsc/src/parser.rs +++ b/rupsc/src/parser.rs @@ -3,7 +3,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::net::ToSocketAddrs; -pub const DEFAULT_HOSTNAME: &str = "localhost"; +pub const DEFAULT_HOSTNAME: &str = "127.0.0.1"; pub const DEFAULT_PORT: u16 = 3493; /// Connection information for a upsd server. @@ -131,7 +131,7 @@ mod tests { port: DEFAULT_PORT } ); - assert_eq!(format!("{}", name), "ups0@localhost:3493"); + assert_eq!(format!("{}", name), "ups0@127.0.0.1:3493"); } #[test]