diff --git a/rups/src/lib.rs b/rups/src/lib.rs index 0952659..2964e53 100644 --- a/rups/src/lib.rs +++ b/rups/src/lib.rs @@ -12,6 +12,12 @@ pub use var::*; /// Blocking client implementation for NUT. pub mod blocking; +/// NUT protocol implementation (v1.2). +/// +/// Reference: +#[allow(dead_code)] +#[macro_use] +pub mod proto; /// Async client implementation for NUT, using Tokio. #[cfg(feature = "async")] pub mod tokio; diff --git a/rups/src/proto/client.rs b/rups/src/proto/client.rs new file mode 100644 index 0000000..87161d6 --- /dev/null +++ b/rups/src/proto/client.rs @@ -0,0 +1,666 @@ +use crate::proto::impl_sentences; + +impl_sentences! { + /// A generic successful response with no additional data. + GenericOk ( + { + 0: Ok, + 1: EOL, + }, + {} + ), + /// Forced shut down (FSD) successful. + FsdOk ( + { + 0: Ok, + 1: FsdSet, + 2: EOL, + }, + {} + ), + /// Server acknowledges TLS upgrade. + StartTLSOk ( + { + 0: Ok, + 1: StartTLS, + 2: EOL, + }, + {} + ), + /// Server confirms logout. + LogoutOk ( + { + 0: Ok, + 1: Goodbye, + }, + {} + ), + /// Server returns an error. + RespondErr ( + { + 0: Err, + 1: Arg, + }, + { + /// The error code. + 1: message, + }, + { + /// Extra information about the error. + 2...: extras + } + ), + /// Server responds with the number of prior logins to the given `ups_name` device. + RespondNumLogins ( + { + 0: NumLogins, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The number of logins to the UPS device. + 2: num_logins, + } + ), + /// Server responds with the description of the UPS device. + RespondUpsDesc ( + { + 0: UpsDesc, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The description of the UPS device. + 2: description, + } + ), + /// Server responds with the value of the given `var_name` variable for the UPS device. + RespondVar ( + { + 0: Var, + 1: Arg, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + /// The current value of the variable. + 3: value, + } + ), + /// Server responds with the type of the given `var_name` variable for the UPS device. + RespondType ( + { + 0: Type, + 1: Arg, + 2: Arg, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + }, + { + /// The variable definition (RW, ENUN, STRING...) + 3...: var_types + } + ), + /// Server responds with the description of the given `var_name` variable for the UPS device. + RespondDesc ( + { + 0: Desc, + 1: Arg, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + /// The description of the variable. + 3: description, + } + ), + /// Server responds with the description of the given `cmd_name` command for the UPS device. + RespondCmdDesc ( + { + 0: CmdDesc, + 1: Arg, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the command. + 2: cmd_name, + /// The description of the command. + 3: description, + } + ), + /// Server responds with the name and description of a UPS device. + RespondUps ( + { + 0: Ups, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the command. + 2: description, + } + ), + /// Server responds with the name and description of a mutable variable. + RespondRw ( + { + 0: Rw, + 1: Arg, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + /// The current value of the variable. + 3: value, + } + ), + /// Server responds with the name of a command. + RespondCmd ( + { + 0: Cmd, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the command. + 2: cmd_name, + } + ), + /// Server responds with a possible value of an enumerable variable. + RespondEnum ( + { + 0: Enum, + 1: Arg, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + /// A possible value of the variable. + 3: enum_value, + } + ), + /// Server responds with a possible range of an numeric variable. + RespondRange ( + { + 0: Range, + 1: Arg, + 2: Arg, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the variable. + 2: var_name, + /// The minimum value of the range. + 3: min_value, + /// The maximum value of the range. + 4: max_value, + } + ), + /// Server responds with a client connected to a UPS device. + RespondClient ( + { + 0: Client, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The IP address of the client. + 2: client_ip, + } + ), + /// Server begins returning a list of UPS devices. + BeginListUps ( + { + 0: Begin, + 1: List, + 2: Ups, + 3: EOL, + }, + {} + ), + /// Server ends returning a list of UPS devices. + EndListUps ( + { + 0: End, + 1: List, + 2: Ups, + 3: EOL, + }, + {} + ), + /// Server begins returning a list of variables for a UPS device. + BeginListVar ( + { + 0: Begin, + 1: List, + 2: Var, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server ends returning a list of variables for a UPS device. + EndListVar ( + { + 0: End, + 1: List, + 2: Var, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server begins returning a list of mutable variables for a UPS device. + BeginListRw ( + { + 0: Begin, + 1: List, + 2: Rw, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server ends returning a list of mutable variables for a UPS device. + EndListRw ( + { + 0: End, + 1: List, + 2: Rw, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server begins returning a list of commands for a UPS device. + BeginListCmd ( + { + 0: Begin, + 1: List, + 2: Cmd, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server ends returning a list of commands for a UPS device. + EndListCmd ( + { + 0: End, + 1: List, + 2: Cmd, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server begins returning a list of possible values for an enumerable variable. + BeginListEnum ( + { + 0: Begin, + 1: List, + 2: Enum, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + /// The name of the variable. + 4: var_name, + } + ), + /// Server ends returning a list of possible values for an enumerable variable. + EndListEnum ( + { + 0: End, + 1: List, + 2: Enum, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + /// The name of the variable. + 4: var_name, + } + ), + /// Server begins returning a list of possible ranges for an enumerable variable. + BeginListRange ( + { + 0: Begin, + 1: List, + 2: Range, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + /// The name of the variable. + 4: var_name, + } + ), + /// Server ends returning a list of possible ranges for an enumerable variable. + EndListRange ( + { + 0: End, + 1: List, + 2: Range, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + /// The name of the variable. + 4: var_name, + } + ), + /// Server begins returning a list of clients for a UPS device. + BeginListClient ( + { + 0: Begin, + 1: List, + 2: Client, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), + /// Server ends returning a list of clients for a UPS device. + EndListClient ( + { + 0: End, + 1: List, + 2: Client, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 3: ups_name, + } + ), +} + +#[cfg(test)] +mod tests { + use crate::proto::test_encode_decode; + + use super::Sentences; + + #[test] + fn test_encode_decode() { + test_encode_decode!( + ["OK"] <=> + Sentences::GenericOk {} + ); + test_encode_decode!( + ["OK", "FSD-SET"] <=> + Sentences::FsdOk {} + ); + test_encode_decode!( + ["OK", "STARTTLS"] <=> + Sentences::StartTLSOk {} + ); + test_encode_decode!( + ["OK", "Goodbye"] <=> + Sentences::LogoutOk {} + ); + test_encode_decode!( + ["ERR", "ACCESS-DENIED"] <=> + Sentences::RespondErr { + message: "ACCESS-DENIED".into(), + extras: vec![], + } + ); + test_encode_decode!( + ["ERR", "ACCESS-DENIED", "extra1", "extra2"] <=> + Sentences::RespondErr { + message: "ACCESS-DENIED".into(), + extras: vec!["extra1".into(), "extra2".into()], + } + ); + test_encode_decode!( + ["NUMLOGINS", "nutdev", "42"] <=> + Sentences::RespondNumLogins { + ups_name: "nutdev".into(), + num_logins: "42".into(), + } + ); + test_encode_decode!( + ["UPSDESC", "nutdev", "Development box"] <=> + Sentences::RespondUpsDesc { + ups_name: "nutdev".into(), + description: "Development box".into(), + } + ); + test_encode_decode!( + ["VAR", "nutdev", "ups.status", "OL"] <=> + Sentences::RespondVar { + ups_name: "nutdev".into(), + var_name: "ups.status".into(), + value: "OL".into(), + } + ); + test_encode_decode!( + ["TYPE", "nutdev", "input.transfer.low", "ENUM", "RW"] <=> + Sentences::RespondType { + ups_name: "nutdev".into(), + var_name: "input.transfer.low".into(), + var_types: vec!["ENUM".into(), "RW".into()], + } + ); + test_encode_decode!( + ["DESC", "nutdev", "ups.status", "UPS status"] <=> + Sentences::RespondDesc { + ups_name: "nutdev".into(), + var_name: "ups.status".into(), + description: "UPS status".into(), + } + ); + test_encode_decode!( + ["CMDDESC", "nutdev", "load.on", "Turn on the load immediately"] <=> + Sentences::RespondCmdDesc { + ups_name: "nutdev".into(), + cmd_name: "load.on".into(), + description: "Turn on the load immediately".into(), + } + ); + test_encode_decode!( + ["UPS", "nutdev", "Development box"] <=> + Sentences::RespondUps { + ups_name: "nutdev".into(), + description: "Development box".into(), + } + ); + test_encode_decode!( + ["RW", "nutdev", "ups.mfr", "APC"] <=> + Sentences::RespondRw { + ups_name: "nutdev".into(), + var_name: "ups.mfr".into(), + value: "APC".into(), + } + ); + test_encode_decode!( + ["CMD", "nutdev", "do.something"] <=> + Sentences::RespondCmd { + ups_name: "nutdev".into(), + cmd_name: "do.something".into(), + } + ); + test_encode_decode!( + ["ENUM", "nutdev", "input.transfer.low", "103"] <=> + Sentences::RespondEnum { + ups_name: "nutdev".into(), + var_name: "input.transfer.low".into(), + enum_value: "103".into(), + } + ); + test_encode_decode!( + ["RANGE", "nutdev", "input.transfer.low", "90", "100"] <=> + Sentences::RespondRange { + ups_name: "nutdev".into(), + var_name: "input.transfer.low".into(), + min_value: "90".into(), + max_value: "100".into(), + } + ); + test_encode_decode!( + ["CLIENT", "nutdev", "127.0.0.1"] <=> + Sentences::RespondClient { + ups_name: "nutdev".into(), + client_ip: "127.0.0.1".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "VAR", "nutdev"] <=> + Sentences::BeginListVar { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "VAR", "nutdev"] <=> + Sentences::EndListVar { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "RW", "nutdev"] <=> + Sentences::BeginListRw { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "RW", "nutdev"] <=> + Sentences::EndListRw { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "CMD", "nutdev"] <=> + Sentences::BeginListCmd { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "CMD", "nutdev"] <=> + Sentences::EndListCmd { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "ENUM", "nutdev", "test.var"] <=> + Sentences::BeginListEnum { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "ENUM", "nutdev", "test.var"] <=> + Sentences::EndListEnum { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "RANGE", "nutdev", "test.var"] <=> + Sentences::BeginListRange { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "RANGE", "nutdev", "test.var"] <=> + Sentences::EndListRange { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["BEGIN", "LIST", "CLIENT", "nutdev"] <=> + Sentences::BeginListClient { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["END", "LIST", "CLIENT", "nutdev"] <=> + Sentences::EndListClient { + ups_name: "nutdev".into(), + } + ); + } +} diff --git a/rups/src/proto/mod.rs b/rups/src/proto/mod.rs new file mode 100644 index 0000000..02f4bb3 --- /dev/null +++ b/rups/src/proto/mod.rs @@ -0,0 +1,306 @@ +/// Client-bound protocol implementation. +/// +/// "Client-bound" implies commands RECEIVED and DECODED by the client. The server implementation +/// must use the same messages to ENCODE and SEND. +pub mod client; +/// Server-bound protocol implementation. +/// +/// "Server-bound" implies commands RECEIVED and DECODED by the server. The client implementation +/// must use the same messages to ENCODE and SEND. +pub mod server; + +/// Macro that implements the list of "words" in the NUT network protocol. +macro_rules! impl_words { + ( + $( + $(#[$attr:meta])+ + $name:ident($word:tt), + )* + ) => { + #[allow(clippy::upper_case_acronyms)] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub(crate) enum Word { + /// A string argument. + Arg, + /// End-of-line. + EOL, + $( + /// Protocol word. + $(#[$attr])* + #[allow(dead_code)] + $name, + )* + } + + impl Word { + /// Matches a raw string into the corresponding word. + /// Passing `None` will always return `EOL`. Passing an unrecognized + /// string returns `None`. + pub(crate) fn decode(raw: Option<&str>) -> Option { + if let Some(raw) = raw { + match raw { + $($word => Some(Self::$name),)* + _ => None + } + } else { + Some(Self::EOL) + } + } + + /// Decodes a sequence of words. + /// Unrecognized words will be `None` + /// Returns a `Vec` of the same length as the given slice. + pub(crate) fn decode_words>(raw: &[T]) -> Vec> { + let mut words = Vec::new(); + for r in raw.iter() { + words.push(Self::decode(Some(r.as_ref()))); + } + words.push(Some(Self::EOL)); + words + } + + /// Encodes a `Word` into a string. + /// This function cannot encode `Arg` or `EOL` (either returns `None`). + pub(crate) fn encode(&self) -> Option<&str> { + match self { + Self::Arg | Self::EOL => None, + $(Self::$name => Some($word),)* + } + } + + /// Whether the `Word` matches another. + pub(crate) fn matches(&self, other: Option<&Option>) -> bool { + if let Some(other) = other { + if self == &Word::Arg { + true + } else if let Some(other) = other { + self == other + } else { + self == &Word::EOL + } + } else { + false + } + } + + /// Whether the `Word` matches all words in the vec, starting at the given index. + pub(crate) fn matches_until_end(&self, start: usize, others: &[Option]) -> bool { + for i in start..others.len() { + if i == others.len() { + return others[i] == Some(Self::EOL); + } + if !self.matches(others.get(i)) { + return false; + } + } + true + } + } + }; +} + +/// Implements the list of sentences, which are combinations +/// of words that form commands (serverbound) and responses (clientbound). +macro_rules! impl_sentences { + ( + $( + $(#[$attr:meta])+ + $name:ident( + { + $($wordidx:tt: $word:ident,)* + }, + { + $( + $(#[$argattr:meta])+ + $argidx:tt: $arg:ident, + )* + } + $( + ,{ + $(#[$varargattr:meta])+ + $varargidx:tt...: $vararg:ident + } + )? + ), + )* + ) => { + /// Protocol sentences. + #[derive(Debug, Clone, Eq, PartialEq)] + pub enum Sentences { + $( + $(#[$attr])* + $name { + $( + $(#[$argattr])* + $arg: String, + )* + $( + $(#[$varargattr])* + $vararg: Vec, + )* + }, + )* + } + + impl Sentences { + /// Decodes a sentence. Returns `None` if the pattern cannot be recognized. + pub(crate) fn decode(raw: Vec) -> Option { + use super::{Word::*, *}; + use Sentences::*; + let words = Word::decode_words(raw.as_slice()); + + $( + if true + $(&& $word.matches(words.get($wordidx)))* + $(&& Arg.matches_until_end($varargidx, &words))* + { + return Some($name { + $($arg: raw[$argidx].to_owned(),)* + $($vararg: raw[$varargidx..].to_owned(),)* + }) + } + )* + None + } + + /// Encodes the sentence. + pub(crate) fn encode(&self) -> Vec<&str> { + use super::Word::*; + match self { + $( + Self::$name { + $($arg,)* + $($vararg,)* + } => { + #[allow(unused_mut)] + let mut words = vec![ + $( + $word.encode(), + )* + ]; + + $( + words[$argidx] = Some($arg); + )* + + $( + for vararg in $vararg { + words.push(Some(vararg)); + } + )* + + words + .into_iter() + .flatten() + .collect() + } + )* + } + } + } + }; +} + +/// Macro that asserts the encoding and decoding of a valid sentence. +/// +/// The two arguments, separated by `<=>`, are: +/// 1. the encoded sentence, e.g. `["GET", "VAR", "nutdev", "test.var"]` +/// 2. the decoded sentence +/// +/// ``` +/// test_encode_decode!( +/// ["GET", "VAR", "nutdev", "test.var"] <=> +/// Sentences::QueryVar { +/// ups_name: "nutdev".into(), +/// var_name: "test.var".into(), +/// } +/// ); +/// ``` +#[allow(unused_macros)] +macro_rules! test_encode_decode { + ([$($word:expr$(,)?)+] <=> $expected:expr) => { + assert_eq!( + Sentences::decode(vec![ + $(String::from($word),)* + ]), + Some($expected) + ); + assert_eq!( + vec![ + $(String::from($word),)* + ], + $expected.encode() + ); + }; +} + +impl_words! { + /// Begins a `LIST`. + Begin("BEGIN"), + /// Describes a client connected to a UPS. + Client("CLIENT"), + /// Represents an executable command. + Cmd("CMD"), + /// Describes a command (`CMD`). + CmdDesc("CMDDESC"), + /// Describes a variable (`VAR` or `RW`). + Desc("DESC"), + /// Ends a block of sentences. + End("END"), + /// An enumerable type. + Enum("ENUM"), + /// An error response. + Err("ERR"), + /// Executes a forced shut down (FSD). + Fsd("FSD"), + /// Server confirms forced shut down (FSD). + FsdSet("FSD-SET"), + /// Serverbound query. + Get("GET"), + /// Server confirms logout (this is lower-case on purpose). + Goodbye("Goodbye"), + /// Client requesting a list of commands supported by the server. + Help("HELP"), + /// Executes an instant command. + InstCmd("INSTCMD"), + /// Queries or describes a list. + List("LIST"), + /// Client requests login to a UPS device. + Login("LOGIN"), + /// Client logs out. + Logout("LOGOUT"), + /// Client verifying it has master-level access to the UPS device. + Master("MASTER"), + /// Client requests the network version. + NetworkVersion("NETVER"), + /// Represents the amount of logins to a UPS device. + NumLogins("NUMLOGINS"), + /// Clientbound response for a good outcome. + Ok("OK"), + /// Client setting password. + Password("PASSWORD"), + /// Represents a range of numerical values. + Range("RANGE"), + /// Represents a mutable variable. + Rw("RW"), + /// Client requests to set the value of a mutable variable. + Set("SET"), + /// Client requests the connection be upgraded to TLS. + StartTLS("STARTTLS"), + /// Represents the type of a variable. + Type("TYPE"), + /// Represents a UPS device. + Ups("UPS"), + /// Represents the description of a UPS device. + UpsDesc("UPSDESC"), + /// Client setting username. + Username("USERNAME"), + /// Represents a variable. + Var("VAR"), + /// Client requests the server version. + Version("VERSION"), +} + +pub(crate) use impl_sentences; +#[cfg(test)] +pub(crate) use test_encode_decode; diff --git a/rups/src/proto/server.rs b/rups/src/proto/server.rs new file mode 100644 index 0000000..a659b8e --- /dev/null +++ b/rups/src/proto/server.rs @@ -0,0 +1,472 @@ +use crate::proto::impl_sentences; + +impl_sentences! { + /// Client requests the number of prior logins to the given `ups_name` device. + QueryNumLogins ( + { + 0: Get, + 1: NumLogins, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests the description of the given `ups_name` device. + QueryUpsDesc ( + { + 0: Get, + 1: UpsDesc, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests the value of the given `var_name` variable in the given `ups_name` device. + QueryVar ( + { + 0: Get, + 1: Var, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + } + ), + /// Client requests the type of the given `var_name` variable in the given `ups_name` device. + QueryType ( + { + 0: Get, + 1: Type, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + } + ), + /// Client requests the description of the given `var_name` variable in the given `ups_name` device. + QueryDesc ( + { + 0: Get, + 1: Desc, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + } + ), + /// Client requests the description of the given `cmd_name` command in the given `ups_name` device. + QueryCmdDesc ( + { + 0: Get, + 1: CmdDesc, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the command. + 3: cmd_name, + } + ), + /// Client requests the list of variables for the given `ups_name` device. + QueryListVar ( + { + 0: List, + 1: Var, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests the list of mutable variables for the given `ups_name` device. + QueryListRw ( + { + 0: List, + 1: Rw, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests the list of commands for the given `ups_name` device. + QueryListCmd ( + { + 0: List, + 1: Cmd, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests the list of possible values of the enumerable variable `var_name` + /// for the given `ups_name` device. + QueryListEnum ( + { + 0: List, + 1: Enum, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + } + ), + /// Client requests the list of possible ranges of the numerical variable `var_name` + /// for the given `ups_name` device. + QueryListRange ( + { + 0: List, + 1: Range, + 2: Arg, + 3: Arg, + 4: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + } + ), + /// Client requests the list of clients connected to the given `ups_name` device. + QueryListClient ( + { + 0: List, + 1: Client, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + } + ), + /// Client requests to set the value `value` of the `var_name` variable on the `ups_name` device. + ExecSetVar ( + { + 0: Set, + 1: Var, + 2: Arg, + 3: Arg, + 4: Arg, + 5: EOL, + }, + { + /// The name of the UPS device. + 2: ups_name, + /// The name of the variable. + 3: var_name, + /// The new value of the variable. + 4: value, + } + ), + /// Client requests the execution of an instant command `cmd_name` on the `ups_name` device. + ExecInstCmd ( + { + 0: InstCmd, + 1: Arg, + 2: Arg, + 3: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + /// The name of the command. + 2: cmd_name, + } + ), + /// Client logs-out of the current UPS device. + ExecLogout ( + { + 0: Logout, + 1: EOL, + }, + {} + ), + /// Client logs-into the given `ups_name` device. + ExecLogin ( + { + 0: Login, + 1: Arg, + 2: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + } + ), + /// Client asserts master-level access to the `ups_name` device. + ExecMaster ( + { + 0: Master, + 1: Arg, + 2: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + } + ), + /// Client requests the forced shut-down of the `ups_name` device. + ExecForcedShutDown ( + { + 0: Fsd, + 1: Arg, + 2: EOL, + }, + { + /// The name of the UPS device. + 1: ups_name, + } + ), + /// Client sets the password on the connection. + SetPassword ( + { + 0: Password, + 1: Arg, + 2: EOL, + }, + { + /// The password to set. + 1: password, + } + ), + /// Client sets the username on the connection. + SetUsername ( + { + 0: Username, + 1: Arg, + 2: EOL, + }, + { + /// The username to set. + 1: username, + } + ), + /// Client requests the connection be upgraded to TLS. + ExecStartTLS ( + { + 0: StartTLS, + 1: EOL, + }, + {} + ), + /// Client requests the list of commands supported by the server. + QueryHelp ( + { + 0: Help, + 1: EOL, + }, + {} + ), + /// Client requests the server version. + QueryVersion ( + { + 0: Version, + 1: EOL, + }, + {} + ), + /// Client requests the network version. + QueryNetworkVersion ( + { + 0: NetworkVersion, + 1: EOL, + }, + {} + ), +} + +#[cfg(test)] +mod tests { + use super::Sentences; + use crate::proto::test_encode_decode; + #[test] + fn test_encode_decode() { + test_encode_decode!( + ["GET", "NUMLOGINS", "nutdev"] <=> + Sentences::QueryNumLogins { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["GET", "NUMLOGINS", "nutdev"] <=> + Sentences::QueryNumLogins { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["GET", "UPSDESC", "nutdev"] <=> + Sentences::QueryUpsDesc { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["GET", "VAR", "nutdev", "test.var"] <=> + Sentences::QueryVar { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["GET", "TYPE", "nutdev", "test.var"] <=> + Sentences::QueryType { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["GET", "DESC", "nutdev", "test.var"] <=> + Sentences::QueryDesc { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["GET", "CMDDESC", "nutdev", "test.cmd"] <=> + Sentences::QueryCmdDesc { + ups_name: "nutdev".into(), + cmd_name: "test.cmd".into(), + } + ); + test_encode_decode!( + ["LIST", "VAR", "nutdev"] <=> + Sentences::QueryListVar { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["LIST", "RW", "nutdev"] <=> + Sentences::QueryListRw { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["LIST", "CMD", "nutdev"] <=> + Sentences::QueryListCmd { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["LIST", "ENUM", "nutdev", "test.var"] <=> + Sentences::QueryListEnum { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["LIST", "RANGE", "nutdev", "test.var"] <=> + Sentences::QueryListRange { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + } + ); + test_encode_decode!( + ["LIST", "CLIENT", "nutdev"] <=> + Sentences::QueryListClient { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["SET", "VAR", "nutdev", "test.var", "something"] <=> + Sentences::ExecSetVar { + ups_name: "nutdev".into(), + var_name: "test.var".into(), + value: "something".into(), + } + ); + test_encode_decode!( + ["INSTCMD", "nutdev", "test.cmd"] <=> + Sentences::ExecInstCmd { + ups_name: "nutdev".into(), + cmd_name: "test.cmd".into(), + } + ); + test_encode_decode!( + ["LOGOUT"] <=> + Sentences::ExecLogout {} + ); + test_encode_decode!( + ["LOGIN", "nutdev"] <=> + Sentences::ExecLogin { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["MASTER", "nutdev"] <=> + Sentences::ExecMaster { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["FSD", "nutdev"] <=> + Sentences::ExecForcedShutDown { + ups_name: "nutdev".into(), + } + ); + test_encode_decode!( + ["PASSWORD", "topsecret"] <=> + Sentences::SetPassword { + password: "topsecret".into(), + } + ); + test_encode_decode!( + ["USERNAME", "john"] <=> + Sentences::SetUsername { + username: "john".into(), + } + ); + test_encode_decode!( + ["STARTTLS"] <=> + Sentences::ExecStartTLS {} + ); + test_encode_decode!( + ["HELP"] <=> + Sentences::QueryHelp {} + ); + test_encode_decode!( + ["VERSION"] <=> + Sentences::QueryVersion {} + ); + test_encode_decode!( + ["NETVER"] <=> + Sentences::QueryNetworkVersion {} + ); + } +}