mirror of
https://github.com/arampoire/nut-rs.git
synced 2025-11-30 16:20:25 -05:00
Compare commits
31 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b79a6af150 | |||
| 292cdecd87 | |||
|
|
8712a6087b | ||
| 8ef85f3b28 | |||
| 0b69b3fc47 | |||
| 3e70c36b85 | |||
|
|
821414d9cd | ||
| 35d40d3111 | |||
| 1842ebef15 | |||
|
|
1e3481e18d | ||
| ff77c1bdbd | |||
| 6b839dc631 | |||
| 7336676914 | |||
| 96fbfeaeab | |||
| c935d88496 | |||
| feef67255f | |||
| 539d11848e | |||
| 6c7434623a | |||
| efa5e71443 | |||
| 2468c5c193 | |||
| f1700739f6 | |||
| cf0f058c7a | |||
| 2360bd4a3f | |||
| fd1f72e1d0 | |||
| ea8ecdbf0c | |||
| 0e18b57624 | |||
| b6e3a96aa1 | |||
| f3814c831d | |||
| 11f70642dd | |||
| c56be76906 | |||
| 77d3020df5 |
32 changed files with 4108 additions and 1016 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
/.idea
|
||||
/.vscode
|
||||
|
|
|
|||
350
Cargo.lock
generated
350
Cargo.lock
generated
|
|
@ -3,19 +3,34 @@
|
|||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.42"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
|
|
@ -23,46 +38,58 @@ version = "0.2.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.7.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.0.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.69"
|
||||
version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
|
@ -72,9 +99,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
version = "2.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
|
|
@ -85,6 +112,12 @@ dependencies = [
|
|||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
|
@ -95,120 +128,103 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.51"
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.13"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
|
||||
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.3",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nut-client"
|
||||
version = "0.3.1"
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"shell-words",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.28"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -228,15 +244,33 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rups"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"shell-words",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rupsc"
|
||||
version = "0.3.1"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"nut-client",
|
||||
"rups",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
|
|
@ -262,9 +296,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fa3938c99da4914afedd13bf3d79bcb6c277d1b2c398d23257a304d9e1b074"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
|
|
@ -280,13 +324,13 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.74"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -300,26 +344,26 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.9.0"
|
||||
version = "1.35.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c"
|
||||
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"winapi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.3.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110"
|
||||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -338,16 +382,16 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
|
|
@ -362,10 +406,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.74"
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
|
|
@ -373,13 +423,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.74"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
|
||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
@ -388,9 +438,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.74"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
|
||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
|
@ -398,9 +448,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.74"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
|
||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -411,15 +461,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.74"
|
||||
version = "0.2.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.51"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
|
||||
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -465,3 +515,69 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"nut-client",
|
||||
"rups",
|
||||
"rupsc"
|
||||
]
|
||||
|
|
|
|||
40
README.md
40
README.md
|
|
@ -1,9 +1,9 @@
|
|||
# nut-client
|
||||
# rups
|
||||
|
||||
[](https://crates.io/crates/nut-client)
|
||||
[](https://docs.rs/nut-client)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/aramperes/nut-client-rs/actions?query=workflow%3ACI)
|
||||
[](https://crates.io/crates/rups)
|
||||
[](https://docs.rs/rups)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/aramperes/nut-rs/actions?query=workflow%3ACI)
|
||||
|
||||
A [Network UPS Tools](https://github.com/networkupstools/nut) (NUT) client library for Rust.
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ A [Network UPS Tools](https://github.com/networkupstools/nut) (NUT) client libra
|
|||
|
||||
## Getting Started
|
||||
|
||||
You'll need a running instance of the NUT daemon (`upsd`) and
|
||||
You'll need a running instance of the NUT daemon (`upsd`, version >= 2.6.4) and
|
||||
a [compatible UPS device](https://networkupstools.org/stable-hcl.html)
|
||||
to use this library:
|
||||
|
||||
|
|
@ -31,30 +31,29 @@ upsc myupsname@localhost ups.status
|
|||
|
||||
## Example
|
||||
|
||||
The [rupsc](https://github.com/aramperes/nut-client-rs/tree/master/rupsc)
|
||||
The [rupsc](https://github.com/aramperes/nut-rs/tree/master/rupsc)
|
||||
CLI is written using this library, and is a clone of NUT's
|
||||
built-in [upsc](https://networkupstools.org/docs/man/upsc.html) tool.
|
||||
|
||||
Below is a sample program using this library (`cargo run --example blocking`).
|
||||
|
||||
You can also run the async version of this code using
|
||||
`cargo run --example async --features async-rt` (source: `nut-client/examples/async.rs`).
|
||||
`cargo run --example async --features async-rt` (source: `rups/examples/async.rs`).
|
||||
|
||||
```rust
|
||||
// nut-client/examples/blocking.rs
|
||||
// rups/examples/blocking.rs
|
||||
|
||||
use std::env;
|
||||
|
||||
use nut_client::blocking::Connection;
|
||||
use nut_client::{Auth, ConfigBuilder};
|
||||
use rups::blocking::Connection;
|
||||
use rups::{Auth, ConfigBuilder};
|
||||
use std::convert::TryInto;
|
||||
|
||||
fn main() -> nut_client::Result<()> {
|
||||
let host = env::var("NUT_HOST").unwrap_or_else(|_| "localhost".into());
|
||||
let port = env::var("NUT_PORT")
|
||||
.ok()
|
||||
.map(|s| s.parse::<u16>().ok())
|
||||
.flatten()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(3493);
|
||||
|
||||
let username = env::var("NUT_USER").ok();
|
||||
|
|
@ -100,7 +99,18 @@ If the server is using a self-signed certificate, and you'd like to ignore the s
|
|||
|
||||
## Async (Tokio)
|
||||
|
||||
The `nut-client` library supports async network requests. This requires the `async` feature, which uses Tokio v1 under
|
||||
the hood.
|
||||
The `rups` library supports async network requests. This requires the `async` feature, which uses Tokio v1 under the
|
||||
hood.
|
||||
|
||||
For SSL support, you must use the `async-ssl` feature as well.
|
||||
|
||||
## Pronunciation
|
||||
|
||||
> r-oops
|
||||
|
||||
## License
|
||||
|
||||
The crates in this repository are licensed as such:
|
||||
|
||||
- `rups`: MIT License, see `./LICENSE`
|
||||
- `rupsc`: GPL-3.0 or later, see `./rupsc/LICENSE`
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
use nut_client::tokio::Connection;
|
||||
use nut_client::{Auth, ConfigBuilder};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> nut_client::Result<()> {
|
||||
let host = env::var("NUT_HOST").unwrap_or_else(|_| "localhost".into());
|
||||
let port = env::var("NUT_PORT")
|
||||
.ok()
|
||||
.map(|s| s.parse::<u16>().ok())
|
||||
.flatten()
|
||||
.unwrap_or(3493);
|
||||
|
||||
let username = env::var("NUT_USER").ok();
|
||||
let password = env::var("NUT_PASSWORD").ok();
|
||||
let auth = username.map(|username| Auth::new(username, password));
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.with_host((host, port).try_into().unwrap_or_default())
|
||||
.with_auth(auth)
|
||||
.with_debug(false) // Turn this on for debugging network chatter
|
||||
.build();
|
||||
|
||||
let mut conn = Connection::new(&config).await?;
|
||||
|
||||
// Print a list of all UPS devices
|
||||
println!("Connected UPS devices:");
|
||||
for (name, description) in conn.list_ups().await? {
|
||||
println!("\t- Name: {}", name);
|
||||
println!("\t Description: {}", description);
|
||||
|
||||
// List UPS variables (key = val)
|
||||
println!("\t Variables:");
|
||||
for var in conn.list_vars(&name).await? {
|
||||
println!("\t\t- {}", var);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
use nut_client::blocking::Connection;
|
||||
use nut_client::{Auth, ConfigBuilder};
|
||||
use std::convert::TryInto;
|
||||
|
||||
fn main() -> nut_client::Result<()> {
|
||||
let host = env::var("NUT_HOST").unwrap_or_else(|_| "localhost".into());
|
||||
let port = env::var("NUT_PORT")
|
||||
.ok()
|
||||
.map(|s| s.parse::<u16>().ok())
|
||||
.flatten()
|
||||
.unwrap_or(3493);
|
||||
|
||||
let username = env::var("NUT_USER").ok();
|
||||
let password = env::var("NUT_PASSWORD").ok();
|
||||
let auth = username.map(|username| Auth::new(username, password));
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.with_host((host, port).try_into().unwrap_or_default())
|
||||
.with_auth(auth)
|
||||
.with_debug(false) // Turn this on for debugging network chatter
|
||||
.build();
|
||||
|
||||
let mut conn = Connection::new(&config)?;
|
||||
|
||||
// Print a list of all UPS devices
|
||||
println!("Connected UPS devices:");
|
||||
for (name, description) in conn.list_ups()? {
|
||||
println!("\t- Name: {}", name);
|
||||
println!("\t Description: {}", description);
|
||||
|
||||
// List UPS variables (key = val)
|
||||
println!("\t Variables:");
|
||||
for var in conn.list_vars(&name)? {
|
||||
println!("\t\t- {}", var);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,432 +0,0 @@
|
|||
use core::fmt;
|
||||
|
||||
use crate::{ClientError, NutError, Variable};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Command<'a> {
|
||||
Get(&'a [&'a str]),
|
||||
/// Passes the login username.
|
||||
SetUsername(&'a str),
|
||||
/// Passes the login password.
|
||||
SetPassword(&'a str),
|
||||
/// Queries for a list. Allows for any number of arguments, which forms a single query.
|
||||
List(&'a [&'a str]),
|
||||
/// Tells upsd to switch to TLS, so all future communications will be encrypted.
|
||||
StartTLS,
|
||||
/// Queries the network version.
|
||||
NetworkVersion,
|
||||
}
|
||||
|
||||
impl<'a> Command<'a> {
|
||||
/// The network identifier of the command.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Get(_) => "GET",
|
||||
Self::SetUsername(_) => "USERNAME",
|
||||
Self::SetPassword(_) => "PASSWORD",
|
||||
Self::List(_) => "LIST",
|
||||
Self::StartTLS => "STARTTLS",
|
||||
Self::NetworkVersion => "NETVER",
|
||||
}
|
||||
}
|
||||
|
||||
/// The arguments of the command to serialize.
|
||||
pub fn args(&self) -> Vec<&str> {
|
||||
match self {
|
||||
Self::Get(cmd) => cmd.to_vec(),
|
||||
Self::SetUsername(username) => vec![username],
|
||||
Self::SetPassword(password) => vec![password],
|
||||
Self::List(query) => query.to_vec(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Command<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut args = self.args();
|
||||
args.insert(0, self.name());
|
||||
write!(f, "{}", shell_words::join(args))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Response {
|
||||
/// A successful response.
|
||||
Ok,
|
||||
/// Marks the beginning of a list response.
|
||||
BeginList(String),
|
||||
/// Marks the end of a list response.
|
||||
EndList(String),
|
||||
/// A variable (VAR) response.
|
||||
///
|
||||
/// Params: (var name, var value)
|
||||
Var(String, String),
|
||||
/// A UPS (UPS) response.
|
||||
///
|
||||
/// Params: (device name, device description)
|
||||
Ups(String, String),
|
||||
/// A client (CLIENT) response.
|
||||
///
|
||||
/// Params: (client IP)
|
||||
Client(String),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub fn from_args(mut args: Vec<String>) -> crate::Result<Response> {
|
||||
if args.is_empty() {
|
||||
return Err(
|
||||
NutError::Generic("Parsing server response failed: empty line".into()).into(),
|
||||
);
|
||||
}
|
||||
let cmd_name = args.remove(0);
|
||||
match cmd_name.as_str() {
|
||||
"OK" => Ok(Self::Ok),
|
||||
"ERR" => {
|
||||
if args.is_empty() {
|
||||
Err(NutError::Generic("Unspecified server error".into()).into())
|
||||
} else {
|
||||
let err_type = args.remove(0);
|
||||
match err_type.as_str() {
|
||||
"ACCESS-DENIED" => Err(NutError::AccessDenied.into()),
|
||||
"UNKNOWN-UPS" => Err(NutError::UnknownUps.into()),
|
||||
"FEATURE-NOT-CONFIGURED" => Err(NutError::FeatureNotConfigured.into()),
|
||||
_ => Err(NutError::Generic(format!(
|
||||
"Server error: {} {}",
|
||||
err_type,
|
||||
args.join(" ")
|
||||
))
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
"BEGIN" => {
|
||||
if args.is_empty() {
|
||||
Err(NutError::Generic("Unspecified BEGIN type".into()).into())
|
||||
} else {
|
||||
let begin_type = args.remove(0);
|
||||
if &begin_type != "LIST" {
|
||||
Err(
|
||||
NutError::Generic(format!("Unexpected BEGIN type: {}", begin_type))
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
let args = shell_words::join(args);
|
||||
Ok(Response::BeginList(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
"END" => {
|
||||
if args.is_empty() {
|
||||
Err(NutError::Generic("Unspecified END type".into()).into())
|
||||
} else {
|
||||
let begin_type = args.remove(0);
|
||||
if &begin_type != "LIST" {
|
||||
Err(
|
||||
NutError::Generic(format!("Unexpected END type: {}", begin_type))
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
let args = shell_words::join(args);
|
||||
Ok(Response::EndList(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
"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(),
|
||||
)))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let var_value = if args.is_empty() {
|
||||
Err(ClientError::from(NutError::Generic(
|
||||
"Unspecified VAR value in response".into(),
|
||||
)))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
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))
|
||||
}
|
||||
"CLIENT" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::from(NutError::Generic(
|
||||
"Unspecified CLIENT device in response".into(),
|
||||
)))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let ip_address = if args.is_empty() {
|
||||
Err(ClientError::from(NutError::Generic(
|
||||
"Unspecified CLIENT IP in response".into(),
|
||||
)))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Client(ip_address))
|
||||
}
|
||||
_ => Err(NutError::UnknownResponseType(cmd_name).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_ok(&self) -> crate::Result<&Response> {
|
||||
match self {
|
||||
Self::Ok => Ok(self),
|
||||
_ => Err(NutError::UnexpectedResponse.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_begin_list(self, expected_args: &[&str]) -> crate::Result<Response> {
|
||||
let expected_args = shell_words::join(expected_args);
|
||||
if let Self::BeginList(args) = &self {
|
||||
if &expected_args == args {
|
||||
Ok(self)
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_var(&self) -> crate::Result<Variable> {
|
||||
if let Self::Var(name, value) = &self {
|
||||
Ok(Variable::parse(name, value.to_owned()))
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_client(&self) -> crate::Result<String> {
|
||||
if let Self::Client(client_ip) = &self {
|
||||
Ok(client_ip.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro for implementing `LIST` commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the query to pass to `LIST`
|
||||
/// (2) a closure for mapping each `Response` row to the return type
|
||||
macro_rules! implement_list_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$query:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::List($query))?;
|
||||
let list = conn.read_list($query)?;
|
||||
list.into_iter().map($mapper).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::List($query)).await?;
|
||||
let list = conn.read_list($query).await?;
|
||||
list.into_iter().map($mapper).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro for implementing `GET` commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the query to pass to `GET`
|
||||
/// (2) a closure for mapping the `Response` row to the return type
|
||||
macro_rules! implement_get_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$query:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Get($query))?;
|
||||
($mapper)(conn.read_response()?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Get($query)).await?;
|
||||
($mapper)(conn.read_response().await?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro for implementing simple/direct commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the command to pass
|
||||
/// (2) a closure for mapping the `String` row to the return type
|
||||
macro_rules! implement_simple_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$cmd:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd)?;
|
||||
($mapper)(conn.read_plain_response()?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd).await?;
|
||||
($mapper)(conn.read_plain_response().await?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
implement_list_commands! {
|
||||
/// Queries a list of UPS devices.
|
||||
fn list_ups() -> Vec<(String, String)> {
|
||||
(
|
||||
{ &["UPS"] },
|
||||
{ |row: Response| row.expect_ups() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries a list of client IP addresses connected to the given device.
|
||||
fn list_clients(ups_name: &str) -> Vec<String> {
|
||||
(
|
||||
{ &["CLIENT", ups_name] },
|
||||
{ |row: Response| row.expect_client() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the list of variables for a UPS device.
|
||||
fn list_vars(ups_name: &str) -> Vec<Variable> {
|
||||
(
|
||||
{ &["VAR", ups_name] },
|
||||
{ |row: Response| row.expect_var() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implement_get_commands! {
|
||||
/// Queries one variable for a UPS device.
|
||||
fn get_var(ups_name: &str, variable: &str) -> Variable {
|
||||
(
|
||||
{ &["VAR", ups_name, variable] },
|
||||
{ |row: Response| row.expect_var() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implement_simple_commands! {
|
||||
/// Queries the network protocol version.
|
||||
fn get_network_version() -> String {
|
||||
(
|
||||
{ Command::NetworkVersion },
|
||||
{ |row: String| Ok(row) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
use core::fmt;
|
||||
use std::io;
|
||||
|
||||
/// A NUT-native error.
|
||||
#[derive(Debug)]
|
||||
pub enum NutError {
|
||||
/// Occurs when the username/password combination is rejected.
|
||||
AccessDenied,
|
||||
/// Occurs when the specified UPS device does not exist.
|
||||
UnknownUps,
|
||||
/// Occurs when the response type or content wasn't expected at the current stage.
|
||||
UnexpectedResponse,
|
||||
/// Occurs when the response type is not recognized by the client.
|
||||
UnknownResponseType(String),
|
||||
/// Occurs when attempting to use SSL in a transport that doesn't support it, or
|
||||
/// if the server is not configured for it.
|
||||
SslNotSupported,
|
||||
/// Occurs when trying to initialize a strict SSL connection with an invalid hostname.
|
||||
SslInvalidHostname,
|
||||
/// Occurs when the client used a feature that is disabled by the server.
|
||||
FeatureNotConfigured,
|
||||
/// Generic (usually internal) client error.
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for NutError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::AccessDenied => write!(f, "Authentication failed"),
|
||||
Self::UnknownUps => write!(f, "Unknown UPS device name"),
|
||||
Self::UnexpectedResponse => write!(f, "Unexpected server response content"),
|
||||
Self::UnknownResponseType(ty) => write!(f, "Unknown response type: {}", ty),
|
||||
Self::SslNotSupported => write!(f, "SSL not supported by server or transport"),
|
||||
Self::SslInvalidHostname => write!(
|
||||
f,
|
||||
"Given hostname cannot be used for a strict SSL connection"
|
||||
),
|
||||
Self::FeatureNotConfigured => write!(f, "Feature not configured by server"),
|
||||
Self::Generic(msg) => write!(f, "Internal client error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NutError {}
|
||||
|
||||
/// Encapsulation for errors emitted by the client library.
|
||||
#[derive(Debug)]
|
||||
pub enum ClientError {
|
||||
/// Encapsulates IO errors.
|
||||
Io(io::Error),
|
||||
/// Encapsulates NUT and client-specific errors.
|
||||
Nut(NutError),
|
||||
}
|
||||
|
||||
impl fmt::Display for ClientError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(err) => err.fmt(f),
|
||||
Self::Nut(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ClientError {}
|
||||
|
||||
impl From<io::Error> for ClientError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
ClientError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NutError> for ClientError {
|
||||
fn from(err: NutError) -> Self {
|
||||
ClientError::Nut(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type for [`ClientError`]
|
||||
pub type Result<T> = std::result::Result<T, ClientError>;
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
use core::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Well-known variable keys for NUT UPS devices.
|
||||
///
|
||||
/// List retrieved from: https://networkupstools.org/docs/user-manual.chunked/apcs01.html
|
||||
pub mod key {
|
||||
/// Device model.
|
||||
pub const DEVICE_MODEL: &str = "device.model";
|
||||
/// Device manufacturer.
|
||||
pub const DEVICE_MANUFACTURER: &str = "device.mfr";
|
||||
/// Device serial number.
|
||||
pub const DEVICE_SERIAL: &str = "device.serial";
|
||||
/// Device type.
|
||||
pub const DEVICE_TYPE: &str = "device.type";
|
||||
/// Device description.
|
||||
pub const DEVICE_DESCRIPTION: &str = "device.description";
|
||||
/// Device administrator name.
|
||||
pub const DEVICE_CONTACT: &str = "device.contact";
|
||||
/// Device physical location.
|
||||
pub const DEVICE_LOCATION: &str = "device.location";
|
||||
/// Device part number.
|
||||
pub const DEVICE_PART: &str = "device.part";
|
||||
/// Device MAC address.
|
||||
pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr";
|
||||
/// Device uptime.
|
||||
pub const DEVICE_UPTIME: &str = "device.uptime";
|
||||
}
|
||||
|
||||
/// Well-known variables for NUT UPS devices.
|
||||
///
|
||||
/// List retrieved from: https://networkupstools.org/docs/user-manual.chunked/apcs01.html
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Variable {
|
||||
/// Device model.
|
||||
DeviceModel(String),
|
||||
/// Device manufacturer.
|
||||
DeviceManufacturer(String),
|
||||
/// Device serial number.
|
||||
DeviceSerial(String),
|
||||
/// Device type.
|
||||
DeviceType(DeviceType),
|
||||
/// Device description.
|
||||
DeviceDescription(String),
|
||||
/// Device administrator name.
|
||||
DeviceContact(String),
|
||||
/// Device physical location.
|
||||
DeviceLocation(String),
|
||||
/// Device part number.
|
||||
DevicePart(String),
|
||||
/// Device MAC address.
|
||||
DeviceMacAddress(String),
|
||||
/// Device uptime.
|
||||
DeviceUptime(Duration),
|
||||
|
||||
/// Any other variable. Value is a tuple of (key, value).
|
||||
Other((String, String)),
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
/// Parses a variable from its key and value.
|
||||
pub fn parse(name: &str, value: String) -> Variable {
|
||||
use self::key::*;
|
||||
|
||||
match name {
|
||||
DEVICE_MODEL => Self::DeviceModel(value),
|
||||
DEVICE_MANUFACTURER => Self::DeviceManufacturer(value),
|
||||
DEVICE_SERIAL => Self::DeviceSerial(value),
|
||||
DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)),
|
||||
DEVICE_DESCRIPTION => Self::DeviceDescription(value),
|
||||
DEVICE_CONTACT => Self::DeviceContact(value),
|
||||
DEVICE_LOCATION => Self::DeviceLocation(value),
|
||||
DEVICE_PART => Self::DevicePart(value),
|
||||
DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value),
|
||||
DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs(
|
||||
value.parse().expect("invalid uptime value"),
|
||||
)),
|
||||
|
||||
_ => Self::Other((name.into(), value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the NUT name of the variable.
|
||||
pub fn name(&self) -> &str {
|
||||
use self::key::*;
|
||||
match self {
|
||||
Self::DeviceModel(_) => DEVICE_MODEL,
|
||||
Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER,
|
||||
Self::DeviceSerial(_) => DEVICE_SERIAL,
|
||||
Self::DeviceType(_) => DEVICE_TYPE,
|
||||
Self::DeviceDescription(_) => DEVICE_DESCRIPTION,
|
||||
Self::DeviceContact(_) => DEVICE_CONTACT,
|
||||
Self::DeviceLocation(_) => DEVICE_LOCATION,
|
||||
Self::DevicePart(_) => DEVICE_PART,
|
||||
Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS,
|
||||
Self::DeviceUptime(_) => DEVICE_UPTIME,
|
||||
Self::Other((name, _)) => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the NUT variable.
|
||||
pub fn value(&self) -> String {
|
||||
match self {
|
||||
Self::DeviceModel(value) => value.clone(),
|
||||
Self::DeviceManufacturer(value) => value.clone(),
|
||||
Self::DeviceSerial(value) => value.clone(),
|
||||
Self::DeviceType(value) => value.to_string(),
|
||||
Self::DeviceDescription(value) => value.clone(),
|
||||
Self::DeviceContact(value) => value.clone(),
|
||||
Self::DeviceLocation(value) => value.clone(),
|
||||
Self::DevicePart(value) => value.clone(),
|
||||
Self::DeviceMacAddress(value) => value.clone(),
|
||||
Self::DeviceUptime(value) => value.as_secs().to_string(),
|
||||
Self::Other((_, value)) => value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Variable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.name(), self.value())
|
||||
}
|
||||
}
|
||||
|
||||
/// NUT device type.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceType {
|
||||
/// UPS (Uninterruptible Power Supply)
|
||||
Ups,
|
||||
/// PDU (Power Distribution Unit)
|
||||
Pdu,
|
||||
/// SCD (Solar Controller Device)
|
||||
Scd,
|
||||
/// PSU (Power Supply Unit)
|
||||
Psu,
|
||||
/// ATS (Automatic Transfer Switch)
|
||||
Ats,
|
||||
/// Other device type.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
/// Convert from string.
|
||||
pub fn from(v: String) -> DeviceType {
|
||||
match v.as_str() {
|
||||
"ups" => Self::Ups,
|
||||
"pdu" => Self::Pdu,
|
||||
"scd" => Self::Scd,
|
||||
"psu" => Self::Psu,
|
||||
"ats" => Self::Ats,
|
||||
_ => Self::Other(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Ups => write!(f, "ups"),
|
||||
Self::Pdu => write!(f, "pdu"),
|
||||
Self::Scd => write!(f, "scd"),
|
||||
Self::Psu => write!(f, "psu"),
|
||||
Self::Ats => write!(f, "ats"),
|
||||
Self::Other(val) => write!(f, "other({})", val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "nut-client"
|
||||
version = "0.3.1"
|
||||
name = "rups"
|
||||
version = "0.6.1"
|
||||
authors = ["Aram Peres <aram.peres@wavy.fm>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Network UPS Tools (NUT) client library"
|
||||
categories = ["network-programming"]
|
||||
keywords = ["ups", "nut", "tokio", "async"]
|
||||
repository = "https://github.com/aramperes/nut-client-rs"
|
||||
documentation = "https://docs.rs/nut-client"
|
||||
repository = "https://github.com/aramperes/nut-rs"
|
||||
documentation = "https://docs.rs/rups"
|
||||
readme = "../README.md"
|
||||
license = "MIT"
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ default = []
|
|||
ssl = ["rustls", "rustls/dangerous_configuration", "webpki", "webpki-roots"]
|
||||
async = ["tokio"]
|
||||
async-ssl = ["async", "tokio-rustls", "ssl"]
|
||||
write = []
|
||||
# a feature gate for examples
|
||||
async-rt = ["async", "tokio/rt-multi-thread", "tokio/macros"]
|
||||
|
||||
72
rups/examples/async.rs
Normal file
72
rups/examples/async.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use std::env;
|
||||
|
||||
use rups::tokio::Connection;
|
||||
use rups::{Auth, ConfigBuilder};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> rups::Result<()> {
|
||||
let host = env::var("NUT_HOST").unwrap_or_else(|_| "localhost".into());
|
||||
let port = env::var("NUT_PORT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(3493);
|
||||
|
||||
let username = env::var("NUT_USER").ok();
|
||||
let password = env::var("NUT_PASSWORD").ok();
|
||||
let auth = username.map(|username| Auth::new(username, password));
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.with_host((host, port).try_into().unwrap_or_default())
|
||||
.with_auth(auth)
|
||||
.with_debug(false) // Turn this on for debugging network chatter
|
||||
.build();
|
||||
|
||||
let mut conn = Connection::new(&config).await?;
|
||||
|
||||
// Get server information
|
||||
println!("NUT server:");
|
||||
println!("\tVersion: {}", conn.get_server_version().await?);
|
||||
println!("\tNetwork Version: {}", conn.get_network_version().await?);
|
||||
|
||||
// Print a list of all UPS devices
|
||||
println!("Connected UPS devices:");
|
||||
for (name, description) in conn.list_ups().await? {
|
||||
println!("\t- Name: {}", name);
|
||||
println!("\t Description: {}", description);
|
||||
println!(
|
||||
"\t Number of logins: {}",
|
||||
conn.get_num_logins(&name).await?
|
||||
);
|
||||
|
||||
// Get list of mutable variables
|
||||
let mutable_vars = conn.list_mutable_vars(&name).await?;
|
||||
|
||||
// List UPS variables (key = val)
|
||||
println!("\t Mutable Variables:");
|
||||
for var in mutable_vars.iter() {
|
||||
println!("\t\t- {}", var);
|
||||
println!("\t\t {:?}", conn.get_var_type(&name, var.name()).await?);
|
||||
}
|
||||
|
||||
// List UPS immutable properties (key = val)
|
||||
println!("\t Immutable Properties:");
|
||||
for var in conn.list_vars(&name).await? {
|
||||
if mutable_vars.iter().any(|v| v.name() == var.name()) {
|
||||
continue;
|
||||
}
|
||||
println!("\t\t- {}", var);
|
||||
println!("\t\t {:?}", conn.get_var_type(&name, var.name()).await?);
|
||||
}
|
||||
|
||||
// List UPS commands
|
||||
println!("\t Commands:");
|
||||
for cmd in conn.list_commands(&name).await? {
|
||||
let description = conn.get_command_description(&name, &cmd).await?;
|
||||
println!("\t\t- {} ({})", cmd, description);
|
||||
}
|
||||
}
|
||||
|
||||
// Gracefully shut down the connection using the `LOGOUT` command
|
||||
conn.close().await
|
||||
}
|
||||
68
rups/examples/blocking.rs
Normal file
68
rups/examples/blocking.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
|
||||
use rups::blocking::Connection;
|
||||
use rups::{Auth, ConfigBuilder};
|
||||
|
||||
fn main() -> rups::Result<()> {
|
||||
let host = env::var("NUT_HOST").unwrap_or_else(|_| "localhost".into());
|
||||
let port = env::var("NUT_PORT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(3493);
|
||||
|
||||
let username = env::var("NUT_USER").ok();
|
||||
let password = env::var("NUT_PASSWORD").ok();
|
||||
let auth = username.map(|username| Auth::new(username, password));
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.with_host((host, port).try_into().unwrap_or_default())
|
||||
.with_auth(auth)
|
||||
.with_debug(false) // Turn this on for debugging network chatter
|
||||
.build();
|
||||
|
||||
let mut conn = Connection::new(&config)?;
|
||||
|
||||
// Get server information
|
||||
println!("NUT server:");
|
||||
println!("\tVersion: {}", conn.get_server_version()?);
|
||||
println!("\tNetwork Version: {}", conn.get_network_version()?);
|
||||
|
||||
// Print a list of all UPS devices
|
||||
println!("Connected UPS devices:");
|
||||
for (name, description) in conn.list_ups()? {
|
||||
println!("\t- Name: {}", name);
|
||||
println!("\t Description: {}", description);
|
||||
println!("\t Number of logins: {}", conn.get_num_logins(&name)?);
|
||||
|
||||
// Get list of mutable variables
|
||||
let mutable_vars = conn.list_mutable_vars(&name)?;
|
||||
|
||||
// List UPS variables (key = val)
|
||||
println!("\t Mutable Variables:");
|
||||
for var in mutable_vars.iter() {
|
||||
println!("\t\t- {}", var);
|
||||
println!("\t\t {:?}", conn.get_var_type(&name, var.name())?);
|
||||
}
|
||||
|
||||
// List UPS immutable properties (key = val)
|
||||
println!("\t Immutable Properties:");
|
||||
for var in conn.list_vars(&name)? {
|
||||
if mutable_vars.iter().any(|v| v.name() == var.name()) {
|
||||
continue;
|
||||
}
|
||||
println!("\t\t- {}", var);
|
||||
println!("\t\t {:?}", conn.get_var_type(&name, var.name())?);
|
||||
}
|
||||
|
||||
// List UPS commands
|
||||
println!("\t Commands:");
|
||||
for cmd in conn.list_commands(&name)? {
|
||||
let description = conn.get_command_description(&name, &cmd)?;
|
||||
println!("\t\t- {} ({})", cmd, description);
|
||||
}
|
||||
}
|
||||
|
||||
// Gracefully shut down the connection using the `LOGOUT` command
|
||||
conn.close()
|
||||
}
|
||||
|
|
@ -16,9 +16,34 @@ pub enum Connection {
|
|||
impl Connection {
|
||||
/// Initializes a connection to a NUT server (upsd).
|
||||
pub fn new(config: &Config) -> crate::Result<Self> {
|
||||
match &config.host {
|
||||
Host::Tcp(host) => Ok(Self::Tcp(TcpConnection::new(config.clone(), &host.addr)?)),
|
||||
let mut conn = match &config.host {
|
||||
Host::Tcp(host) => Self::Tcp(TcpConnection::new(config.clone(), &host.addr)?),
|
||||
};
|
||||
|
||||
conn.get_network_version()?;
|
||||
conn.login(config)?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
/// Gracefully closes the connection.
|
||||
pub fn close(mut self) -> crate::Result<()> {
|
||||
self.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends username and password, as applicable.
|
||||
fn login(&mut self, config: &Config) -> crate::Result<()> {
|
||||
if let Some(auth) = config.auth.clone() {
|
||||
// Pass username and check for 'OK'
|
||||
self.set_username(&auth.username)?;
|
||||
|
||||
// Pass password and check for 'OK'
|
||||
if let Some(password) = &auth.password {
|
||||
self.set_password(password)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,25 +61,18 @@ impl TcpConnection {
|
|||
config,
|
||||
stream: ConnectionStream::Plain(tcp_stream),
|
||||
};
|
||||
|
||||
// Initialize SSL connection
|
||||
connection = connection.enable_ssl()?;
|
||||
|
||||
// Attempt login using `config.auth`
|
||||
connection.login()?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
fn enable_ssl(mut self) -> crate::Result<Self> {
|
||||
if self.config.ssl {
|
||||
// Send TLS request and check for 'OK'
|
||||
self.write_cmd(Command::StartTLS)?;
|
||||
self.read_response()
|
||||
.map_err(|e| {
|
||||
if let ClientError::Nut(NutError::FeatureNotConfigured) = e {
|
||||
ClientError::Nut(NutError::SslNotSupported)
|
||||
if let crate::ClientError::Nut(NutError::FeatureNotConfigured) = e {
|
||||
crate::ClientError::Nut(NutError::SslNotSupported)
|
||||
} else {
|
||||
e
|
||||
}
|
||||
|
|
@ -92,10 +110,6 @@ impl TcpConnection {
|
|||
|
||||
// Wrap and override the TCP stream
|
||||
self.stream = self.stream.upgrade_ssl(sess)?;
|
||||
|
||||
// Send a test command
|
||||
self.write_cmd(Command::NetworkVersion)?;
|
||||
self.read_plain_response()?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
|
@ -105,21 +119,6 @@ impl TcpConnection {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
fn login(&mut self) -> crate::Result<()> {
|
||||
if let Some(auth) = self.config.auth.clone() {
|
||||
// Pass username and check for 'OK'
|
||||
self.write_cmd(Command::SetUsername(&auth.username))?;
|
||||
self.read_response()?.expect_ok()?;
|
||||
|
||||
// Pass password and check for 'OK'
|
||||
if let Some(password) = &auth.password {
|
||||
self.write_cmd(Command::SetPassword(password))?;
|
||||
self.read_response()?.expect_ok()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn write_cmd(&mut self, line: Command) -> crate::Result<()> {
|
||||
let line = format!("{}\n", line);
|
||||
if self.config.debug {
|
||||
|
|
@ -139,11 +138,11 @@ impl TcpConnection {
|
|||
if debug {
|
||||
eprint!("DEBUG <- {}", raw);
|
||||
}
|
||||
raw = raw[..raw.len() - 1].to_string(); // Strip off \n
|
||||
raw = raw.trim_end_matches('\n').to_string(); // Strip off \n
|
||||
|
||||
// Parse args by splitting whitespace, minding quotes for args with multiple words
|
||||
let args = shell_words::split(&raw)
|
||||
.map_err(|e| NutError::Generic(format!("Parsing server response failed: {}", e)))?;
|
||||
.map_err(|e| NutError::generic(format!("Parsing server response failed: {}", e)))?;
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
894
rups/src/cmd.rs
Normal file
894
rups/src/cmd.rs
Normal file
|
|
@ -0,0 +1,894 @@
|
|||
use core::fmt;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::{ClientError, NutError, Variable, VariableDefinition, VariableRange};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Command<'a> {
|
||||
Get(&'a [&'a str]),
|
||||
/// Passes the login username.
|
||||
SetUsername(&'a str),
|
||||
/// Passes the login password.
|
||||
SetPassword(&'a str),
|
||||
/// Queries for a list. Allows for any number of arguments, which forms a single query.
|
||||
List(&'a [&'a str]),
|
||||
/// Tells upsd to switch to TLS, so all future communications will be encrypted.
|
||||
StartTLS,
|
||||
/// Queries the network version.
|
||||
NetworkVersion,
|
||||
/// Queries the server version.
|
||||
Version,
|
||||
#[cfg(feature = "write")]
|
||||
/// Run a command. Allow for on additional optional param.
|
||||
Run(&'a str, Option<&'a str>),
|
||||
/// Gracefully shuts down the connection.
|
||||
Logout,
|
||||
}
|
||||
|
||||
impl<'a> Command<'a> {
|
||||
/// The network identifier of the command.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Get(_) => "GET",
|
||||
Self::SetUsername(_) => "USERNAME",
|
||||
Self::SetPassword(_) => "PASSWORD",
|
||||
Self::List(_) => "LIST",
|
||||
Self::StartTLS => "STARTTLS",
|
||||
Self::NetworkVersion => "NETVER",
|
||||
Self::Version => "VER",
|
||||
#[cfg(feature = "write")]
|
||||
Self::Run(_, _) => "INSTCMD",
|
||||
Self::Logout => "LOGOUT",
|
||||
}
|
||||
}
|
||||
|
||||
/// The arguments of the command to serialize.
|
||||
pub fn args(&self) -> Vec<&str> {
|
||||
match self {
|
||||
Self::Get(cmd) => cmd.to_vec(),
|
||||
Self::SetUsername(username) => vec![username],
|
||||
Self::SetPassword(password) => vec![password],
|
||||
Self::List(query) => query.to_vec(),
|
||||
#[cfg(feature = "write")]
|
||||
Self::Run(cmd, param) => param
|
||||
.map(|param| vec![*cmd, param])
|
||||
.unwrap_or_else(|| vec![cmd]),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Command<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut args = self.args();
|
||||
args.insert(0, self.name());
|
||||
write!(f, "{}", shell_words::join(args))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Response {
|
||||
/// A successful response.
|
||||
Ok,
|
||||
/// Marks the beginning of a list response.
|
||||
BeginList(String),
|
||||
/// Marks the end of a list response.
|
||||
EndList(String),
|
||||
/// A variable (VAR) response.
|
||||
///
|
||||
/// Params: (var name, var value)
|
||||
Var(String, String),
|
||||
/// A UPS (UPS) response.
|
||||
///
|
||||
/// Params: (device name, device description)
|
||||
Ups(String, String),
|
||||
/// A client (CLIENT) response.
|
||||
///
|
||||
/// Params: (client IP)
|
||||
Client(String),
|
||||
/// A command (CMD) response.
|
||||
///
|
||||
/// Params: (command name)
|
||||
Cmd(String),
|
||||
/// A command description (CMDDESC) response.
|
||||
///
|
||||
/// Params: (command description)
|
||||
CmdDesc(String),
|
||||
/// A UPS description (UPSDESC) response.
|
||||
///
|
||||
/// Params: (UPS description)
|
||||
UpsDesc(String),
|
||||
/// A mutable variable (RW) response.
|
||||
///
|
||||
/// Params: (var name, var value)
|
||||
Rw(String, String),
|
||||
/// A variable description (DESC) response.
|
||||
///
|
||||
/// Params: (variable description)
|
||||
Desc(String),
|
||||
/// A NUMLOGINS response.
|
||||
///
|
||||
/// Params: (number of logins)
|
||||
NumLogins(i32),
|
||||
/// A variable type (TYPE) response.
|
||||
///
|
||||
/// Params: (variable name, variable types)
|
||||
Type(String, Vec<String>),
|
||||
/// A variable range (RANGE) response.
|
||||
///
|
||||
/// Params: (variable range)
|
||||
Range(VariableRange),
|
||||
/// A variable enum (ENUM) response.
|
||||
///
|
||||
/// Params: (enum value)
|
||||
Enum(String),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(crate) fn from_args(mut args: Vec<String>) -> crate::Result<Response> {
|
||||
if args.is_empty() {
|
||||
return Err(ClientError::generic(
|
||||
"Parsing server response failed: empty line",
|
||||
));
|
||||
}
|
||||
let cmd_name = args.remove(0);
|
||||
match cmd_name.as_str() {
|
||||
"OK" => Ok(Self::Ok),
|
||||
"ERR" => {
|
||||
if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified server error"))
|
||||
} else {
|
||||
let err_type = args.remove(0);
|
||||
match err_type.as_str() {
|
||||
"ACCESS-DENIED" => Err(NutError::AccessDenied.into()),
|
||||
"UNKNOWN-UPS" => Err(NutError::UnknownUps.into()),
|
||||
"FEATURE-NOT-CONFIGURED" => Err(NutError::FeatureNotConfigured.into()),
|
||||
_ => Err(NutError::generic(format!(
|
||||
"Server error: {} {}",
|
||||
err_type,
|
||||
args.join(" ")
|
||||
))
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
"BEGIN" => {
|
||||
if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified BEGIN type"))
|
||||
} else {
|
||||
let begin_type = args.remove(0);
|
||||
if &begin_type != "LIST" {
|
||||
Err(ClientError::generic(format!(
|
||||
"Unexpected BEGIN type: {}",
|
||||
begin_type
|
||||
)))
|
||||
} else {
|
||||
let args = shell_words::join(args);
|
||||
Ok(Response::BeginList(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
"END" => {
|
||||
if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified END type"))
|
||||
} else {
|
||||
let begin_type = args.remove(0);
|
||||
if &begin_type != "LIST" {
|
||||
Err(ClientError::generic(format!(
|
||||
"Unexpected END type: {}",
|
||||
begin_type
|
||||
)))
|
||||
} else {
|
||||
let args = shell_words::join(args);
|
||||
Ok(Response::EndList(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
"VAR" => {
|
||||
let _var_device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified VAR device name in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let var_name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified VAR name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let var_value = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified VAR value in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Var(var_name, var_value))
|
||||
}
|
||||
"RW" => {
|
||||
let _var_device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified RW device name in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let var_name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RW name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let var_value = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RW value in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Rw(var_name, var_value))
|
||||
}
|
||||
"UPS" => {
|
||||
let name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified UPS name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let description = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified UPS description in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Ups(name, description))
|
||||
}
|
||||
"CLIENT" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified CLIENT device in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let ip_address = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified CLIENT IP in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Client(ip_address))
|
||||
}
|
||||
"CMD" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified CMD device in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified CMD name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Cmd(name))
|
||||
}
|
||||
"CMDDESC" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified CMDDESC device in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let _name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified CMDDESC name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let desc = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified CMDDESC description in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::CmdDesc(desc))
|
||||
}
|
||||
"UPSDESC" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified UPSDESC device in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let desc = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified UPSDESC description in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::UpsDesc(desc))
|
||||
}
|
||||
"DESC" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified DESC device in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let _name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified DESC name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let desc = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified DESC description in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Desc(desc))
|
||||
}
|
||||
"NUMLOGINS" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified NUMLOGINS device in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let num = if args.is_empty() {
|
||||
Err(ClientError::generic(
|
||||
"Unspecified NUMLOGINS number in response",
|
||||
))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let num = num
|
||||
.parse::<i32>()
|
||||
.map_err(|_| ClientError::generic("Invalid NUMLOGINS number in response"))?;
|
||||
Ok(Response::NumLogins(num))
|
||||
}
|
||||
"TYPE" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified TYPE device in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified TYPE name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let types = args;
|
||||
Ok(Response::Type(name, types))
|
||||
}
|
||||
"RANGE" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RANGE device in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let _name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RANGE name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let min = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RANGE min in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let max = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified RANGE max in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Range(VariableRange(min, max)))
|
||||
}
|
||||
"ENUM" => {
|
||||
let _device = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified ENUM device in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let _name = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified ENUM name in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
let val = if args.is_empty() {
|
||||
Err(ClientError::generic("Unspecified ENUM value in response"))
|
||||
} else {
|
||||
Ok(args.remove(0))
|
||||
}?;
|
||||
Ok(Response::Enum(val))
|
||||
}
|
||||
_ => Err(NutError::UnknownResponseType(cmd_name).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_ok(&self) -> crate::Result<&Response> {
|
||||
match self {
|
||||
Self::Ok => Ok(self),
|
||||
_ => Err(NutError::UnexpectedResponse.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_begin_list(self, expected_args: &[&str]) -> crate::Result<Response> {
|
||||
let expected_args = shell_words::join(expected_args);
|
||||
if let Self::BeginList(args) = &self {
|
||||
if &expected_args == args {
|
||||
Ok(self)
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_var(&self) -> crate::Result<Variable> {
|
||||
if let Self::Var(name, value) = &self {
|
||||
Ok(Variable::parse(name, value.to_owned()))
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_rw(&self) -> crate::Result<Variable> {
|
||||
if let Self::Rw(name, value) = &self {
|
||||
Ok(Variable::parse(name, value.to_owned()))
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) 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())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_client(&self) -> crate::Result<String> {
|
||||
if let Self::Client(client_ip) = &self {
|
||||
Ok(client_ip.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_cmd(&self) -> crate::Result<String> {
|
||||
if let Self::Cmd(name) = &self {
|
||||
Ok(name.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_cmddesc(&self) -> crate::Result<String> {
|
||||
if let Self::CmdDesc(description) = &self {
|
||||
Ok(description.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_upsdesc(&self) -> crate::Result<String> {
|
||||
if let Self::UpsDesc(description) = &self {
|
||||
Ok(description.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_desc(&self) -> crate::Result<String> {
|
||||
if let Self::Desc(description) = &self {
|
||||
Ok(description.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_numlogins(&self) -> crate::Result<i32> {
|
||||
if let Self::NumLogins(num) = &self {
|
||||
Ok(*num)
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_type(&self) -> crate::Result<VariableDefinition> {
|
||||
if let Self::Type(name, types) = &self {
|
||||
VariableDefinition::try_from((
|
||||
name.to_owned(),
|
||||
types.iter().map(String::as_str).collect(),
|
||||
))
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_range(&self) -> crate::Result<VariableRange> {
|
||||
if let Self::Range(range) = &self {
|
||||
Ok(range.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_enum(&self) -> crate::Result<String> {
|
||||
if let Self::Enum(value) = &self {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(NutError::UnexpectedResponse.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro for implementing `LIST` commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the query to pass to `LIST`
|
||||
/// (2) a closure for mapping each `Response` row to the return type
|
||||
macro_rules! implement_list_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
$vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$query:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::List($query))?;
|
||||
let list = conn.read_list($query)?;
|
||||
list.into_iter().map($mapper).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::List($query)).await?;
|
||||
let list = conn.read_list($query).await?;
|
||||
list.into_iter().map($mapper).collect()
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro for implementing `GET` commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the query to pass to `GET`
|
||||
/// (2) a closure for mapping the `Response` row to the return type
|
||||
macro_rules! implement_get_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
$vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$query:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Get($query))?;
|
||||
($mapper)(conn.read_response()?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Get($query)).await?;
|
||||
($mapper)(conn.read_response().await?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro for implementing simple/direct commands.
|
||||
///
|
||||
/// Each function should return a 2-tuple with
|
||||
/// (1) the command to pass
|
||||
/// (2) a closure for mapping the `String` row to the return type
|
||||
macro_rules! implement_simple_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
$vis:vis fn $name:ident($($argname:ident: $argty:ty),*) -> $retty:ty {
|
||||
(
|
||||
$cmd:block,
|
||||
$mapper:block,
|
||||
)
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd)?;
|
||||
($mapper)(conn.read_plain_response()?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<$retty> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd).await?;
|
||||
($mapper)(conn.read_plain_response().await?)
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro for implementing action commands that return `OK`.
|
||||
///
|
||||
/// Each function should return the command to pass.
|
||||
macro_rules! implement_action_commands {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])+
|
||||
$vis:vis fn $name:ident($($argname:ident: $argty:ty),*) $cmd:block
|
||||
)*
|
||||
) => {
|
||||
impl crate::blocking::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<()> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd)?;
|
||||
conn.read_response()?.expect_ok()?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl crate::tokio::Connection {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[allow(dead_code)]
|
||||
$vis async fn $name(&mut self$(, $argname: $argty)*) -> crate::Result<()> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd($cmd).await?;
|
||||
conn.read_response().await?.expect_ok()?;
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
implement_list_commands! {
|
||||
/// Queries a list of UPS devices.
|
||||
pub fn list_ups() -> Vec<(String, String)> {
|
||||
(
|
||||
{ &["UPS"] },
|
||||
{ |row: Response| row.expect_ups() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the list of client IP addresses connected to the given device.
|
||||
pub fn list_clients(ups_name: &str) -> Vec<String> {
|
||||
(
|
||||
{ &["CLIENT", ups_name] },
|
||||
{ |row: Response| row.expect_client() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the list of variables for a UPS device.
|
||||
pub fn list_vars(ups_name: &str) -> Vec<Variable> {
|
||||
(
|
||||
{ &["VAR", ups_name] },
|
||||
{ |row: Response| row.expect_var() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the list of mutable variables for a UPS device.
|
||||
pub fn list_mutable_vars(ups_name: &str) -> Vec<Variable> {
|
||||
(
|
||||
{ &["RW", ups_name] },
|
||||
{ |row: Response| row.expect_rw() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the list of commands available for the given device.
|
||||
pub fn list_commands(ups_name: &str) -> Vec<String> {
|
||||
(
|
||||
{ &["CMD", ups_name] },
|
||||
{ |row: Response| row.expect_cmd() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the possible ranges of a UPS variable.
|
||||
pub fn list_var_range(ups_name: &str, variable: &str) -> Vec<VariableRange> {
|
||||
(
|
||||
{ &["RANGE", ups_name, variable] },
|
||||
{ |row: Response| row.expect_range() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the possible enum values of a UPS variable.
|
||||
pub fn list_var_enum(ups_name: &str, variable: &str) -> Vec<String> {
|
||||
(
|
||||
{ &["ENUM", ups_name, variable] },
|
||||
{ |row: Response| row.expect_enum() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implement_get_commands! {
|
||||
/// Queries one variable for a UPS device.
|
||||
pub fn get_var(ups_name: &str, variable: &str) -> Variable {
|
||||
(
|
||||
{ &["VAR", ups_name, variable] },
|
||||
{ |row: Response| row.expect_var() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the description of a UPS variable.
|
||||
pub fn get_var_description(ups_name: &str, variable: &str) -> String {
|
||||
(
|
||||
{ &["DESC", ups_name, variable] },
|
||||
{ |row: Response| row.expect_desc() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the type of a UPS variable.
|
||||
pub fn get_var_type(ups_name: &str, variable: &str) -> VariableDefinition {
|
||||
(
|
||||
{ &["TYPE", ups_name, variable] },
|
||||
{ |row: Response| row.expect_type() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the description of a UPS command.
|
||||
pub fn get_command_description(ups_name: &str, variable: &str) -> String {
|
||||
(
|
||||
{ &["CMDDESC", ups_name, variable] },
|
||||
{ |row: Response| row.expect_cmddesc() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the description of a UPS device.
|
||||
pub fn get_ups_description(ups_name: &str) -> String {
|
||||
(
|
||||
{ &["UPSDESC", ups_name] },
|
||||
{ |row: Response| row.expect_upsdesc() },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the number of logins to the specified UPS.
|
||||
pub fn get_num_logins(ups_name: &str) -> i32 {
|
||||
(
|
||||
{ &["NUMLOGINS", ups_name] },
|
||||
{ |row: Response| row.expect_numlogins() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implement_simple_commands! {
|
||||
/// Queries the network protocol version.
|
||||
pub fn get_network_version() -> String {
|
||||
(
|
||||
{ Command::NetworkVersion },
|
||||
{ Ok },
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the server NUT version.
|
||||
pub fn get_server_version() -> String {
|
||||
(
|
||||
{ Command::Version },
|
||||
{ Ok },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
implement_action_commands! {
|
||||
/// Sends the login username.
|
||||
pub(crate) fn set_username(username: &str) {
|
||||
Command::SetUsername(username)
|
||||
}
|
||||
|
||||
/// Sends the login password.
|
||||
pub(crate) fn set_password(password: &str) {
|
||||
Command::SetPassword(password)
|
||||
}
|
||||
|
||||
/// Gracefully shuts down the connection.
|
||||
pub(crate) fn logout() {
|
||||
Command::Logout
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "write")]
|
||||
impl crate::blocking::Connection {
|
||||
/// Runs a command on the UPS.
|
||||
pub fn run_command(&mut self, cmd: &str, param: Option<&str>) -> crate::Result<()> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Run(cmd, param))?;
|
||||
conn.read_response()?.expect_ok()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "write", feature = "async"))]
|
||||
impl crate::tokio::Connection {
|
||||
/// Runs a command on the UPS.
|
||||
pub async fn run_command(&mut self, cmd: &str, param: Option<&str>) -> crate::Result<()> {
|
||||
match self {
|
||||
Self::Tcp(conn) => {
|
||||
conn.write_cmd(Command::Run(cmd, param)).await?;
|
||||
conn.read_response().await?.expect_ok()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
rups/src/error.rs
Normal file
192
rups/src/error.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
use crate::proto::ClientSentences;
|
||||
use core::fmt;
|
||||
use std::io;
|
||||
|
||||
/// A NUT-native error.
|
||||
#[derive(Debug)]
|
||||
pub enum NutError {
|
||||
/// Occurs when the username/password combination is rejected.
|
||||
AccessDenied,
|
||||
/// Occurs when the specified UPS device does not exist.
|
||||
UnknownUps,
|
||||
/// The specified UPS doesn't support the variable in the request.
|
||||
VarNotSupported,
|
||||
/// The specified UPS doesn't support the instant command in the request.
|
||||
CmdNotSupported,
|
||||
/// The client sent an argument to a command which is not recognized or is otherwise invalid in this context.
|
||||
InvalidArgument,
|
||||
/// Server failed to deliver the instant command request to the driver. No further information is available to the client.
|
||||
InstCmdFailed,
|
||||
/// Server failed to deliver the set request to the driver.
|
||||
SetFailed,
|
||||
/// The requested variable in a SET command is not writable.
|
||||
ReadOnly,
|
||||
/// The requested value in a SET command is too long.
|
||||
TooLong,
|
||||
/// The server does not support the requested feature.
|
||||
FeatureNotSupported,
|
||||
/// TLS/SSL mode is already enabled on this connection, so the server can't start it again.
|
||||
AlreadySslMode,
|
||||
/// The server can't perform the requested command, since the driver for that UPS is not connected.
|
||||
DriverNotConnected,
|
||||
/// Server is connected to the driver for the UPS, but that driver isn't providing regular updates
|
||||
/// or has specifically marked the data as stale.
|
||||
DataStale,
|
||||
/// The client already sent LOGIN for a UPS and can't do it again.
|
||||
/// There is presently a limit of one LOGIN record per connection.
|
||||
AlreadyLoggedIn,
|
||||
/// The client sent an invalid PASSWORD - perhaps an empty one.
|
||||
InvalidPassword,
|
||||
/// The client already set a PASSWORD and can't set another.
|
||||
AlreadySetPassword,
|
||||
/// The client sent an invalid USERNAME.
|
||||
InvalidUsername,
|
||||
/// The client has already set a USERNAME, and can't set another.
|
||||
AlreadySetUsername,
|
||||
/// The requested command requires a username for authentication, but the client hasn't set one.
|
||||
UsernameRequired,
|
||||
/// The requested command requires a password for authentication, but the client hasn't set one.
|
||||
PasswordRequired,
|
||||
/// The server doesn't recognize the requested command.
|
||||
UnknownCommand,
|
||||
/// The value specified in the request is not valid.
|
||||
InvalidValue,
|
||||
/// Occurs when the response type or content wasn't expected at the current stage.
|
||||
UnexpectedResponse,
|
||||
/// Occurs when the response type is not recognized by the client.
|
||||
UnknownResponseType(String),
|
||||
/// Occurs when attempting to use SSL in a transport that doesn't support it, or
|
||||
/// if the server is not configured for it.
|
||||
SslNotSupported,
|
||||
/// Occurs when trying to initialize a strict SSL connection with an invalid hostname.
|
||||
SslInvalidHostname,
|
||||
/// Occurs when the client used a feature that is disabled by the server.
|
||||
FeatureNotConfigured,
|
||||
/// Generic (usually internal) client error.
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for NutError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::AccessDenied => write!(f, "Access denied"),
|
||||
Self::UnknownUps => write!(f, "Unknown UPS device"),
|
||||
Self::VarNotSupported => write!(f, "Variable not supported"),
|
||||
Self::CmdNotSupported => write!(f, "Command not supported"),
|
||||
Self::InvalidArgument => write!(f, "Invalid argument"),
|
||||
Self::InstCmdFailed => write!(f, "Instant command failed"),
|
||||
Self::SetFailed => write!(f, "Failed to set variable"),
|
||||
Self::ReadOnly => write!(f, "Cannot set read-only variable"),
|
||||
Self::TooLong => write!(f, "Value is too long"),
|
||||
Self::FeatureNotSupported => write!(f, "Feature is not supported by server"),
|
||||
Self::AlreadySslMode => write!(f, "Connection is already in TLS/SSL"),
|
||||
Self::DriverNotConnected => write!(f, "Driver is not connected"),
|
||||
Self::DataStale => write!(f, "Data is stale"),
|
||||
Self::AlreadyLoggedIn => write!(f, "Connection is already authenticated"),
|
||||
Self::InvalidPassword => write!(f, "Invalid password"),
|
||||
Self::AlreadySetPassword => write!(f, "Password can only be set once"),
|
||||
Self::InvalidUsername => write!(f, "Invalid username"),
|
||||
Self::AlreadySetUsername => write!(f, "Username can only be set once"),
|
||||
Self::UsernameRequired => write!(f, "Username required"),
|
||||
Self::PasswordRequired => write!(f, "Password required"),
|
||||
Self::UnknownCommand => write!(f, "Unknown command"),
|
||||
Self::InvalidValue => write!(f, "Invalid value"),
|
||||
Self::UnexpectedResponse => write!(f, "Unexpected server response content"),
|
||||
Self::UnknownResponseType(ty) => write!(f, "Unknown response type: {}", ty),
|
||||
Self::SslNotSupported => write!(f, "SSL not supported by server or transport"),
|
||||
Self::SslInvalidHostname => write!(
|
||||
f,
|
||||
"Given hostname cannot be used for a strict SSL connection"
|
||||
),
|
||||
Self::FeatureNotConfigured => write!(f, "Feature not configured by server"),
|
||||
Self::Generic(msg) => write!(f, "Client error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<ClientSentences>> From<T> for NutError {
|
||||
fn from(sentence: T) -> Self {
|
||||
if let ClientSentences::RespondErr { message, .. } = sentence.as_ref() {
|
||||
match message.as_str() {
|
||||
"ACCESS-DENIED" => Self::AccessDenied,
|
||||
"UNKNOWN-UPS" => Self::UnknownUps,
|
||||
"VAR-NOT-SUPPORTED" => Self::VarNotSupported,
|
||||
"CMD-NOT-SUPPORTED" => Self::CmdNotSupported,
|
||||
"INVALID-ARGUMENT" => Self::InvalidArgument,
|
||||
"INSTCMD-FAILED" => Self::InstCmdFailed,
|
||||
"SET-FAILED" => Self::SetFailed,
|
||||
"READONLY" => Self::ReadOnly,
|
||||
"TOO-LONG" => Self::TooLong,
|
||||
"FEATURE-NOT-SUPPORTED" => Self::FeatureNotSupported,
|
||||
"FEATURE-NOT-CONFIGURED" => Self::FeatureNotConfigured,
|
||||
"ALREADY-SSL-MODE" => Self::AlreadySslMode,
|
||||
"DRIVER-NOT-CONNECTED" => Self::DriverNotConnected,
|
||||
"DATA-STALE" => Self::DataStale,
|
||||
"ALREADY-LOGGED-IN" => Self::AlreadyLoggedIn,
|
||||
"INVALID-PASSWORD" => Self::InvalidPassword,
|
||||
"ALREADY-SET-PASSWORD" => Self::AlreadySetPassword,
|
||||
"INVALID-USERNAME" => Self::InvalidUsername,
|
||||
"ALREADY-SET-USERNAME" => Self::AlreadySetUsername,
|
||||
"USERNAME-REQUIRED" => Self::UsernameRequired,
|
||||
"PASSWORD-REQUIRED" => Self::PasswordRequired,
|
||||
"UNKNOWN-COMMAND" => Self::UnknownCommand,
|
||||
"INVALID-VALUE" => Self::InvalidValue,
|
||||
_ => Self::Generic(message.to_string()),
|
||||
}
|
||||
} else {
|
||||
// This is not supposed to happen...
|
||||
panic!("Cannot convert {:?} into NutError", sentence.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NutError {
|
||||
/// Constructs a generic rups error.
|
||||
pub fn generic<T: ToString>(message: T) -> Self {
|
||||
Self::Generic(message.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NutError {}
|
||||
|
||||
/// Encapsulation for errors emitted by the client library.
|
||||
#[derive(Debug)]
|
||||
pub enum ClientError {
|
||||
/// Encapsulates IO errors.
|
||||
Io(io::Error),
|
||||
/// Encapsulates NUT and client-specific errors.
|
||||
Nut(NutError),
|
||||
}
|
||||
|
||||
impl ClientError {
|
||||
/// Constructs a generic rups error.
|
||||
pub fn generic<T: ToString>(message: T) -> Self {
|
||||
NutError::generic(message.to_string()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ClientError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io(err) => err.fmt(f),
|
||||
Self::Nut(err) => err.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ClientError {}
|
||||
|
||||
impl From<io::Error> for ClientError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
ClientError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NutError> for ClientError {
|
||||
fn from(err: NutError) -> Self {
|
||||
ClientError::Nut(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type for [`ClientError`]
|
||||
pub type Result<T> = std::result::Result<T, ClientError>;
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
//! # nut-client
|
||||
//! # rups
|
||||
//!
|
||||
//! The `nut-client` crate provides a network client implementation
|
||||
//! The `rups` crate provides a network client implementation
|
||||
//! for Network UPS Tools (NUT) servers.
|
||||
|
||||
pub use config::*;
|
||||
pub use error::*;
|
||||
pub use util::*;
|
||||
pub use var::*;
|
||||
|
||||
/// Blocking client implementation for NUT.
|
||||
pub mod blocking;
|
||||
/// NUT protocol implementation (v1.2).
|
||||
///
|
||||
/// Reference: <https://networkupstools.org/docs/developer-guide.chunked/ar01s09.html>
|
||||
#[allow(dead_code)]
|
||||
#[macro_use]
|
||||
pub(crate) mod proto;
|
||||
/// Async client implementation for NUT, using Tokio.
|
||||
#[cfg(feature = "async")]
|
||||
pub mod tokio;
|
||||
|
|
@ -20,4 +27,5 @@ mod config;
|
|||
mod error;
|
||||
#[cfg(feature = "ssl")]
|
||||
mod ssl;
|
||||
mod util;
|
||||
mod var;
|
||||
666
rups/src/proto/client.rs
Normal file
666
rups/src/proto/client.rs
Normal file
|
|
@ -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(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
315
rups/src/proto/mod.rs
Normal file
315
rups/src/proto/mod.rs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
/// 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;
|
||||
/// Utilities for encoding and decoding packets.
|
||||
pub mod util;
|
||||
|
||||
pub use client::Sentences as ClientSentences;
|
||||
pub use server::Sentences as ServerSentences;
|
||||
|
||||
/// 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<Self> {
|
||||
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<T: AsRef<str>>(raw: &[T]) -> Vec<Option<Self>> {
|
||||
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<Self>>) -> 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<Self>]) -> 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<String>,
|
||||
)*
|
||||
},
|
||||
)*
|
||||
}
|
||||
|
||||
impl Sentences {
|
||||
/// Decodes a sentence. Returns `None` if the pattern cannot be recognized.
|
||||
pub(crate) fn decode(raw: Vec<String>) -> Option<Sentences> {
|
||||
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
|
||||
///
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate rups;
|
||||
/// # fn main() {
|
||||
/// # #[cfg(test)]
|
||||
/// 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;
|
||||
472
rups/src/proto/server.rs
Normal file
472
rups/src/proto/server.rs
Normal file
|
|
@ -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 {}
|
||||
);
|
||||
}
|
||||
}
|
||||
41
rups/src/proto/util.rs
Normal file
41
rups/src/proto/util.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/// Splits a sentence (line) into a `Vec<String>`, minding quotation marks
|
||||
/// for words with spaces in them.
|
||||
///
|
||||
/// Returns `None` if the sentence cannot be split safely (usually unbalanced quotation marks).
|
||||
pub fn split_sentence<T: AsRef<str>>(sentence: T) -> Option<Vec<String>> {
|
||||
shell_words::split(sentence.as_ref()).ok()
|
||||
}
|
||||
|
||||
/// Joins a collection of words (`&str`) into one sentence string,
|
||||
/// adding quotation marks for words with spaces in them.
|
||||
pub fn join_sentence<I, S>(words: I) -> String
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
shell_words::join(words)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_split() {
|
||||
assert_eq!(
|
||||
split_sentence("AbC dEf GHi"),
|
||||
Some(vec!["AbC".into(), "dEf".into(), "GHi".into()])
|
||||
);
|
||||
assert_eq!(
|
||||
split_sentence("\"AbC dEf\" GHi"),
|
||||
Some(vec!["AbC dEf".into(), "GHi".into()])
|
||||
);
|
||||
assert_eq!(split_sentence("\"AbC dEf GHi"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_join() {
|
||||
assert_eq!(join_sentence(vec!["AbC", "dEf", "GHi"]), "AbC dEf GHi",);
|
||||
assert_eq!(join_sentence(vec!["AbC dEf", "GHi"]), "'AbC dEf' GHi",);
|
||||
assert_eq!(join_sentence(vec!["\"AbC dEf", "GHi"]), "'\"AbC dEf' GHi",);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,34 @@ pub enum Connection {
|
|||
impl Connection {
|
||||
/// Initializes a connection to a NUT server (upsd).
|
||||
pub async fn new(config: &Config) -> crate::Result<Self> {
|
||||
match &config.host {
|
||||
Host::Tcp(host) => Ok(Self::Tcp(
|
||||
TcpConnection::new(config.clone(), &host.addr).await?,
|
||||
)),
|
||||
let mut conn = match &config.host {
|
||||
Host::Tcp(host) => Self::Tcp(TcpConnection::new(config.clone(), &host.addr).await?),
|
||||
};
|
||||
|
||||
conn.get_network_version().await?;
|
||||
conn.login(config).await?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
/// Gracefully closes the connection.
|
||||
pub async fn close(mut self) -> crate::Result<()> {
|
||||
self.logout().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends username and password, as applicable.
|
||||
async fn login(&mut self, config: &Config) -> crate::Result<()> {
|
||||
if let Some(auth) = config.auth.clone() {
|
||||
// Pass username and check for 'OK'
|
||||
self.set_username(&auth.username).await?;
|
||||
|
||||
// Pass password and check for 'OK'
|
||||
if let Some(password) = &auth.password {
|
||||
self.set_password(password).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,13 +62,7 @@ impl TcpConnection {
|
|||
config,
|
||||
stream: ConnectionStream::Plain(tcp_stream),
|
||||
};
|
||||
|
||||
// Initialize SSL connection
|
||||
connection = connection.enable_ssl().await?;
|
||||
|
||||
// Attempt login using `config.auth`
|
||||
connection.login().await?;
|
||||
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
|
|
@ -99,10 +116,6 @@ impl TcpConnection {
|
|||
|
||||
// Wrap and override the TCP stream
|
||||
self.stream = self.stream.upgrade_ssl(config, dns_name.as_ref()).await?;
|
||||
|
||||
// Send a test command
|
||||
self.write_cmd(Command::NetworkVersion).await?;
|
||||
self.read_plain_response().await?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
|
@ -112,21 +125,6 @@ impl TcpConnection {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
async fn login(&mut self) -> crate::Result<()> {
|
||||
if let Some(auth) = self.config.auth.clone() {
|
||||
// Pass username and check for 'OK'
|
||||
self.write_cmd(Command::SetUsername(&auth.username)).await?;
|
||||
self.read_response().await?.expect_ok()?;
|
||||
|
||||
// Pass password and check for 'OK'
|
||||
if let Some(password) = &auth.password {
|
||||
self.write_cmd(Command::SetPassword(password)).await?;
|
||||
self.read_response().await?.expect_ok()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn write_cmd(&mut self, line: Command<'_>) -> crate::Result<()> {
|
||||
let line = format!("{}\n", line);
|
||||
if self.config.debug {
|
||||
|
|
@ -146,11 +144,11 @@ impl TcpConnection {
|
|||
if debug {
|
||||
eprint!("DEBUG <- {}", raw);
|
||||
}
|
||||
raw = raw[..raw.len() - 1].to_string(); // Strip off \n
|
||||
raw = raw.trim_end_matches('\n').to_string(); // Strip off \n
|
||||
|
||||
// Parse args by splitting whitespace, minding quotes for args with multiple words
|
||||
let args = shell_words::split(&raw)
|
||||
.map_err(|e| NutError::Generic(format!("Parsing server response failed: {}", e)))?;
|
||||
.map_err(|e| NutError::generic(format!("Parsing server response failed: {}", e)))?;
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
use anyhow::Context;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt;
|
||||
|
||||
/// The default upsd hostname.
|
||||
pub const DEFAULT_HOSTNAME: &str = "localhost";
|
||||
/// The default upsd port.
|
||||
pub const DEFAULT_PORT: u16 = 3493;
|
||||
|
||||
/// Connection information for a upsd server.
|
||||
/// TCP connection information for a upsd server.
|
||||
///
|
||||
/// The upsname is optional depending on context.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct UpsdName<'a> {
|
||||
/// The name of the ups device, if specified.
|
||||
pub upsname: Option<&'a str>,
|
||||
/// The hostname of the upsd server.
|
||||
pub hostname: &'a str,
|
||||
/// The port of the upsd server.
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
|
|
@ -26,9 +30,9 @@ impl<'a> Default for UpsdName<'a> {
|
|||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for UpsdName<'a> {
|
||||
type Error = anyhow::Error;
|
||||
type Error = crate::ClientError;
|
||||
|
||||
fn try_from(value: &'a str) -> anyhow::Result<UpsdName<'a>> {
|
||||
fn try_from(value: &'a str) -> crate::Result<UpsdName<'a>> {
|
||||
let mut upsname: Option<&str> = None;
|
||||
let mut hostname = DEFAULT_HOSTNAME;
|
||||
let mut port = DEFAULT_PORT;
|
||||
|
|
@ -40,7 +44,7 @@ impl<'a> TryFrom<&'a str> for UpsdName<'a> {
|
|||
.next()
|
||||
.unwrap()
|
||||
.parse::<u16>()
|
||||
.with_context(|| "invalid port number")?;
|
||||
.map_err(|_| crate::ClientError::generic("Invalid port number"))?;
|
||||
if prefix.contains('@') {
|
||||
let mut split = prefix.splitn(2, '@');
|
||||
upsname = Some(split.next().unwrap());
|
||||
|
|
@ -64,13 +68,13 @@ impl<'a> TryFrom<&'a str> for UpsdName<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> TryInto<nut_client::Host> for UpsdName<'a> {
|
||||
type Error = anyhow::Error;
|
||||
impl<'a> TryInto<crate::Host> for UpsdName<'a> {
|
||||
type Error = crate::ClientError;
|
||||
|
||||
fn try_into(self) -> anyhow::Result<nut_client::Host> {
|
||||
fn try_into(self) -> crate::Result<crate::Host> {
|
||||
(self.hostname.to_owned(), self.port)
|
||||
.try_into()
|
||||
.with_context(|| "Invalid hostname/port")
|
||||
.map_err(|_| crate::ClientError::generic("Invalid hostname/port"))
|
||||
}
|
||||
}
|
||||
|
||||
339
rups/src/var.rs
Normal file
339
rups/src/var.rs
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
use core::fmt;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Well-known variable keys for NUT UPS devices.
|
||||
///
|
||||
/// List retrieved from: <https://networkupstools.org/docs/user-manual.chunked/apcs01.html>
|
||||
pub mod key {
|
||||
/// Device model.
|
||||
pub const DEVICE_MODEL: &str = "device.model";
|
||||
/// Device manufacturer.
|
||||
pub const DEVICE_MANUFACTURER: &str = "device.mfr";
|
||||
/// Device serial number.
|
||||
pub const DEVICE_SERIAL: &str = "device.serial";
|
||||
/// Device type.
|
||||
pub const DEVICE_TYPE: &str = "device.type";
|
||||
/// Device description.
|
||||
pub const DEVICE_DESCRIPTION: &str = "device.description";
|
||||
/// Device administrator name.
|
||||
pub const DEVICE_CONTACT: &str = "device.contact";
|
||||
/// Device physical location.
|
||||
pub const DEVICE_LOCATION: &str = "device.location";
|
||||
/// Device part number.
|
||||
pub const DEVICE_PART: &str = "device.part";
|
||||
/// Device MAC address.
|
||||
pub const DEVICE_MAC_ADDRESS: &str = "device.macaddr";
|
||||
/// Device uptime.
|
||||
pub const DEVICE_UPTIME: &str = "device.uptime";
|
||||
}
|
||||
|
||||
/// Well-known variables for NUT UPS devices.
|
||||
///
|
||||
/// List retrieved from: <https://networkupstools.org/docs/user-manual.chunked/apcs01.html>
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Variable {
|
||||
/// Device model.
|
||||
DeviceModel(String),
|
||||
/// Device manufacturer.
|
||||
DeviceManufacturer(String),
|
||||
/// Device serial number.
|
||||
DeviceSerial(String),
|
||||
/// Device type.
|
||||
DeviceType(DeviceType),
|
||||
/// Device description.
|
||||
DeviceDescription(String),
|
||||
/// Device administrator name.
|
||||
DeviceContact(String),
|
||||
/// Device physical location.
|
||||
DeviceLocation(String),
|
||||
/// Device part number.
|
||||
DevicePart(String),
|
||||
/// Device MAC address.
|
||||
DeviceMacAddress(String),
|
||||
/// Device uptime.
|
||||
DeviceUptime(Duration),
|
||||
|
||||
/// Any other variable. Value is a tuple of (key, value).
|
||||
Other((String, String)),
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
/// Parses a variable from its key and value.
|
||||
pub fn parse(name: &str, value: String) -> Variable {
|
||||
use self::key::*;
|
||||
|
||||
match name {
|
||||
DEVICE_MODEL => Self::DeviceModel(value),
|
||||
DEVICE_MANUFACTURER => Self::DeviceManufacturer(value),
|
||||
DEVICE_SERIAL => Self::DeviceSerial(value),
|
||||
DEVICE_TYPE => Self::DeviceType(DeviceType::from(value)),
|
||||
DEVICE_DESCRIPTION => Self::DeviceDescription(value),
|
||||
DEVICE_CONTACT => Self::DeviceContact(value),
|
||||
DEVICE_LOCATION => Self::DeviceLocation(value),
|
||||
DEVICE_PART => Self::DevicePart(value),
|
||||
DEVICE_MAC_ADDRESS => Self::DeviceMacAddress(value),
|
||||
DEVICE_UPTIME => Self::DeviceUptime(Duration::from_secs(
|
||||
value.parse().expect("invalid uptime value"),
|
||||
)),
|
||||
|
||||
_ => Self::Other((name.into(), value)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the NUT name of the variable.
|
||||
pub fn name(&self) -> &str {
|
||||
use self::key::*;
|
||||
match self {
|
||||
Self::DeviceModel(_) => DEVICE_MODEL,
|
||||
Self::DeviceManufacturer(_) => DEVICE_MANUFACTURER,
|
||||
Self::DeviceSerial(_) => DEVICE_SERIAL,
|
||||
Self::DeviceType(_) => DEVICE_TYPE,
|
||||
Self::DeviceDescription(_) => DEVICE_DESCRIPTION,
|
||||
Self::DeviceContact(_) => DEVICE_CONTACT,
|
||||
Self::DeviceLocation(_) => DEVICE_LOCATION,
|
||||
Self::DevicePart(_) => DEVICE_PART,
|
||||
Self::DeviceMacAddress(_) => DEVICE_MAC_ADDRESS,
|
||||
Self::DeviceUptime(_) => DEVICE_UPTIME,
|
||||
Self::Other((name, _)) => name.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the NUT variable.
|
||||
pub fn value(&self) -> String {
|
||||
match self {
|
||||
Self::DeviceModel(value) => value.clone(),
|
||||
Self::DeviceManufacturer(value) => value.clone(),
|
||||
Self::DeviceSerial(value) => value.clone(),
|
||||
Self::DeviceType(value) => value.to_string(),
|
||||
Self::DeviceDescription(value) => value.clone(),
|
||||
Self::DeviceContact(value) => value.clone(),
|
||||
Self::DeviceLocation(value) => value.clone(),
|
||||
Self::DevicePart(value) => value.clone(),
|
||||
Self::DeviceMacAddress(value) => value.clone(),
|
||||
Self::DeviceUptime(value) => value.as_secs().to_string(),
|
||||
Self::Other((_, value)) => value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Variable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}: {}", self.name(), self.value())
|
||||
}
|
||||
}
|
||||
|
||||
/// NUT device type.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum DeviceType {
|
||||
/// UPS (Uninterruptible Power Supply)
|
||||
Ups,
|
||||
/// PDU (Power Distribution Unit)
|
||||
Pdu,
|
||||
/// SCD (Solar Controller Device)
|
||||
Scd,
|
||||
/// PSU (Power Supply Unit)
|
||||
Psu,
|
||||
/// ATS (Automatic Transfer Switch)
|
||||
Ats,
|
||||
/// Other device type.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
/// Convert from string.
|
||||
pub fn from(v: String) -> DeviceType {
|
||||
match v.as_str() {
|
||||
"ups" => Self::Ups,
|
||||
"pdu" => Self::Pdu,
|
||||
"scd" => Self::Scd,
|
||||
"psu" => Self::Psu,
|
||||
"ats" => Self::Ats,
|
||||
_ => Self::Other(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Ups => write!(f, "ups"),
|
||||
Self::Pdu => write!(f, "pdu"),
|
||||
Self::Scd => write!(f, "scd"),
|
||||
Self::Psu => write!(f, "psu"),
|
||||
Self::Ats => write!(f, "ats"),
|
||||
Self::Other(val) => write!(f, "other({})", val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NUT Variable type
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum VariableType {
|
||||
/// A mutable variable (`RW`).
|
||||
Rw,
|
||||
/// An enumerated type, which supports a few specific values (`ENUM`).
|
||||
Enum,
|
||||
/// A string with a maximum size (`STRING:n`).
|
||||
String(usize),
|
||||
/// A numeric type, either integer or float, comprised in the range defined by `LIST RANGE`.
|
||||
Range,
|
||||
/// A simple numeric value, either integer or float.
|
||||
Number,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for VariableType {
|
||||
type Error = crate::ClientError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"RW" => Ok(Self::Rw),
|
||||
"ENUM" => Ok(Self::Enum),
|
||||
"RANGE" => Ok(Self::Range),
|
||||
"NUMBER" => Ok(Self::Number),
|
||||
other => {
|
||||
if other.starts_with("STRING:") {
|
||||
let size = other
|
||||
.split_once(':')
|
||||
.and_then(|(_, s)| s.parse().ok())
|
||||
.ok_or_else(|| crate::ClientError::generic("Invalid STRING definition"))?;
|
||||
Ok(Self::String(size))
|
||||
} else {
|
||||
Err(crate::ClientError::generic(format!(
|
||||
"Unrecognized variable type: {}",
|
||||
value
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NUT Variable definition.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct VariableDefinition(String, HashSet<VariableType>);
|
||||
|
||||
impl VariableDefinition {
|
||||
/// The name of this variable.
|
||||
pub fn name(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
/// Whether this variable is mutable.
|
||||
pub fn is_mutable(&self) -> bool {
|
||||
self.1.contains(&VariableType::Rw)
|
||||
}
|
||||
|
||||
/// Whether this variable is an enumerated type.
|
||||
pub fn is_enum(&self) -> bool {
|
||||
self.1.contains(&VariableType::Enum)
|
||||
}
|
||||
|
||||
/// Whether this variable is a String type
|
||||
pub fn is_string(&self) -> bool {
|
||||
self.1.iter().any(|t| matches!(t, VariableType::String(_)))
|
||||
}
|
||||
|
||||
/// Whether this variable is a numeric type,
|
||||
/// either integer or float, comprised in a range
|
||||
pub fn is_range(&self) -> bool {
|
||||
self.1.contains(&VariableType::Range)
|
||||
}
|
||||
|
||||
/// Whether this variable is a numeric type, either integer or float.
|
||||
pub fn is_number(&self) -> bool {
|
||||
self.1.contains(&VariableType::Number)
|
||||
}
|
||||
|
||||
/// Returns the max string length, if applicable.
|
||||
pub fn get_string_length(&self) -> Option<usize> {
|
||||
self.1.iter().find_map(|t| match t {
|
||||
VariableType::String(n) => Some(*n),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: ToString> TryFrom<(A, Vec<&str>)> for VariableDefinition {
|
||||
type Error = crate::ClientError;
|
||||
|
||||
fn try_from(value: (A, Vec<&str>)) -> Result<Self, Self::Error> {
|
||||
Ok(VariableDefinition(
|
||||
value.0.to_string(),
|
||||
value
|
||||
.1
|
||||
.iter()
|
||||
.map(|s| VariableType::try_from(*s))
|
||||
.collect::<crate::Result<HashSet<VariableType>>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A range of values for a variable.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct VariableRange(pub String, pub String);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_variable_definition() {
|
||||
assert_eq!(
|
||||
VariableDefinition::try_from(("var0", vec![])).unwrap(),
|
||||
VariableDefinition("var0".into(), HashSet::new())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
VariableDefinition::try_from(("var1", vec!["RW"])).unwrap(),
|
||||
VariableDefinition(
|
||||
"var1".into(),
|
||||
HashSet::from_iter(vec![VariableType::Rw].into_iter())
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"])).unwrap(),
|
||||
VariableDefinition(
|
||||
"var1".into(),
|
||||
HashSet::from_iter(vec![VariableType::Rw, VariableType::String(123)].into_iter())
|
||||
)
|
||||
);
|
||||
|
||||
assert!(
|
||||
VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.is_mutable()
|
||||
);
|
||||
assert!(
|
||||
VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.is_string()
|
||||
);
|
||||
assert!(
|
||||
!VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.is_enum()
|
||||
);
|
||||
assert!(
|
||||
!VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.is_number()
|
||||
);
|
||||
assert!(
|
||||
!VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.is_range()
|
||||
);
|
||||
assert_eq!(
|
||||
VariableDefinition::try_from(("var1", vec!["RW", "STRING:123"]))
|
||||
.unwrap()
|
||||
.get_string_length(),
|
||||
Some(123)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
[package]
|
||||
name = "rupsc"
|
||||
version = "0.3.1"
|
||||
version = "0.6.1"
|
||||
authors = ["Aram Peres <aram.peres@wavy.fm>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "A demo program to display UPS variables"
|
||||
categories = ["network-programming"]
|
||||
keywords = ["ups", "nut"]
|
||||
repository = "https://github.com/aramperes/nut-client-rs"
|
||||
repository = "https://github.com/aramperes/nut-rs"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ license = "MIT"
|
|||
clap = "2.33.3"
|
||||
anyhow = "1"
|
||||
|
||||
[dependencies.nut-client]
|
||||
version = "0.3.1"
|
||||
path = "../nut-client"
|
||||
[dependencies.rups]
|
||||
version = "0.6.1"
|
||||
path = "../rups"
|
||||
features = ["ssl"]
|
||||
|
|
|
|||
687
rupsc/LICENSE
687
rupsc/LICENSE
|
|
@ -1,21 +1,674 @@
|
|||
MIT License
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (c) 2020-2021 Aram Peres
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Preamble
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# rupsc
|
||||
|
||||
[](https://crates.io/crates/rupsc)
|
||||
[](https://docs.rs/nut-client)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/aramperes/nut-client-rs/actions?query=workflow%3ACI)
|
||||
[](https://docs.rs/rups)
|
||||
[](./LICENSE)
|
||||
[](https://github.com/aramperes/nut-rs/actions?query=workflow%3ACI)
|
||||
|
||||
A Rust clone of [upsc](https://networkupstools.org/docs/man/upsc.html),
|
||||
the [Network UPS Tools](https://github.com/networkupstools/nut) (NUT) demo program to display UPS variables.
|
||||
|
||||
Written using the [nut-client](https://github.com/aramperes/nut-client-rs) crate.
|
||||
Written using the [rups](https://github.com/aramperes/nut-rs) crate.
|
||||
|
||||
- Connect to `upsd`/`nut-server` using TCP
|
||||
- List UPS devices
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Context;
|
||||
|
||||
use nut_client::blocking::Connection;
|
||||
use nut_client::Config;
|
||||
use rups::blocking::Connection;
|
||||
use rups::Config;
|
||||
|
||||
/// Lists each UPS on the upsd server, one per line.
|
||||
pub fn list_devices(config: Config, with_description: bool) -> anyhow::Result<()> {
|
||||
|
|
@ -15,7 +15,7 @@ pub fn list_devices(config: Config, with_description: bool) -> anyhow::Result<()
|
|||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
logout(conn)
|
||||
}
|
||||
|
||||
pub fn print_variable(config: Config, ups_name: &str, variable: &str) -> anyhow::Result<()> {
|
||||
|
|
@ -24,7 +24,7 @@ pub fn print_variable(config: Config, ups_name: &str, variable: &str) -> anyhow:
|
|||
let variable = conn.get_var(ups_name, variable)?;
|
||||
println!("{}", variable.value());
|
||||
|
||||
Ok(())
|
||||
logout(conn)
|
||||
}
|
||||
|
||||
pub fn list_variables(config: Config, ups_name: &str) -> anyhow::Result<()> {
|
||||
|
|
@ -34,7 +34,7 @@ pub fn list_variables(config: Config, ups_name: &str) -> anyhow::Result<()> {
|
|||
println!("{}", var);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
logout(conn)
|
||||
}
|
||||
|
||||
pub fn list_clients(config: Config, ups_name: &str) -> anyhow::Result<()> {
|
||||
|
|
@ -44,9 +44,13 @@ pub fn list_clients(config: Config, ups_name: &str) -> anyhow::Result<()> {
|
|||
println!("{}", client_ip);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
logout(conn)
|
||||
}
|
||||
|
||||
fn connect(config: Config) -> anyhow::Result<Connection> {
|
||||
Connection::new(&config).with_context(|| format!("Failed to connect to upsd: {:?}", &config))
|
||||
}
|
||||
|
||||
fn logout(conn: Connection) -> anyhow::Result<()> {
|
||||
conn.close().with_context(|| "Failed to close gracefully")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
///! # rupsc
|
||||
///! A demo program to display UPS variables.
|
||||
///! This a Rust clone of [upsc](https://github.com/networkupstools/nut/blob/master/clients/upsc.c).
|
||||
///!
|
||||
///! P.S.: pronounced "r-oopsie".
|
||||
//! # rupsc
|
||||
//! A demo program to display UPS variables.
|
||||
//! This a Rust clone of [upsc](https://github.com/networkupstools/nut/blob/master/clients/upsc.c).
|
||||
//!
|
||||
//! P.S.: pronounced "r-oopsie".
|
||||
use core::convert::TryInto;
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::{App, Arg};
|
||||
|
||||
use crate::parser::UpsdName;
|
||||
use rups::UpsdName;
|
||||
|
||||
mod cmd;
|
||||
mod parser;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let args = App::new(clap::crate_name!())
|
||||
|
|
@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
let server: parser::UpsdName = args.value_of("upsd-server").map_or_else(
|
||||
let server: UpsdName = args.value_of("upsd-server").map_or_else(
|
||||
|| Ok(UpsdName::default()),
|
||||
|s| s.try_into().with_context(|| "Invalid upsd server name"),
|
||||
)?;
|
||||
|
|
@ -82,7 +82,7 @@ fn main() -> anyhow::Result<()> {
|
|||
let ssl = insecure_ssl || args.is_present("ssl");
|
||||
|
||||
let host = server.try_into()?;
|
||||
let config = nut_client::ConfigBuilder::new()
|
||||
let config = rups::ConfigBuilder::new()
|
||||
.with_host(host)
|
||||
.with_debug(debug)
|
||||
.with_ssl(ssl)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue