add ubus api for 'send', 'call', 'list' and 'wait_for'

This commit is contained in:
Rokas Puzonas 2023-03-26 14:05:49 +03:00
parent 044d774ef0
commit a652a7ac07
4 changed files with 551 additions and 11 deletions

294
Cargo.lock generated
View File

@ -31,7 +31,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cee8cf1202a4f94d31837f1902ab0a75c77b65bf59719e093703abe83efd74ec"
dependencies = [
"accesskit",
"parking_lot",
"parking_lot 0.12.1",
]
[[package]]
@ -44,7 +44,7 @@ dependencies = [
"accesskit_consumer",
"objc2",
"once_cell",
"parking_lot",
"parking_lot 0.12.1",
]
[[package]]
@ -58,7 +58,7 @@ dependencies = [
"async-channel",
"atspi",
"futures-lite",
"parking_lot",
"parking_lot 0.12.1",
"serde",
"zbus",
]
@ -73,7 +73,7 @@ dependencies = [
"accesskit_consumer",
"arrayvec",
"once_cell",
"parking_lot",
"parking_lot 0.12.1",
"paste",
"windows",
]
@ -88,7 +88,7 @@ dependencies = [
"accesskit_macos",
"accesskit_unix",
"accesskit_windows",
"parking_lot",
"parking_lot 0.12.1",
"winit",
]
@ -160,7 +160,7 @@ dependencies = [
"objc-foundation",
"objc_id",
"once_cell",
"parking_lot",
"parking_lot 0.12.1",
"thiserror",
"winapi",
"x11rb",
@ -254,6 +254,36 @@ dependencies = [
"event-listener",
]
[[package]]
name = "async-net"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f"
dependencies = [
"async-io",
"autocfg",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4"
dependencies = [
"async-io",
"async-lock",
"autocfg",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"libc",
"signal-hook",
"windows-sys 0.42.0",
]
[[package]]
name = "async-recursion"
version = "1.0.4"
@ -265,6 +295,19 @@ dependencies = [
"syn 2.0.10",
]
[[package]]
name = "async-ssh2-lite"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068cabec74fe85e7a689c96fe3eecd5f3bce99d7358beeecd7d690372bc4de0e"
dependencies = [
"async-io",
"async-trait",
"futures-util",
"libssh2-sys",
"ssh2",
]
[[package]]
name = "async-task"
version = "4.4.0"
@ -785,7 +828,7 @@ dependencies = [
"ecolor",
"emath",
"nohash-hasher",
"parking_lot",
"parking_lot 0.12.1",
]
[[package]]
@ -906,6 +949,17 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "futures-macro"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "futures-sink"
version = "0.3.27"
@ -926,6 +980,7 @@ checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
dependencies = [
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
@ -1058,6 +1113,15 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
@ -1108,11 +1172,17 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.45.0",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "jni"
version = "0.21.1"
@ -1159,6 +1229,29 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "lazy-regex"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4"
dependencies = [
"lazy-regex-proc_macros",
"once_cell",
"regex",
]
[[package]]
name = "lazy-regex-proc_macros"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 1.0.109",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1181,6 +1274,32 @@ dependencies = [
"winapi",
]
[[package]]
name = "libssh2-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
dependencies = [
"cc",
"libc",
"libz-sys",
"openssl-sys",
"pkg-config",
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
@ -1365,6 +1484,16 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.11"
@ -1447,6 +1576,19 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "openssl-sys"
version = "0.9.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "orbclient"
version = "0.3.43"
@ -1484,6 +1626,17 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1491,7 +1644,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.9.7",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
@ -1709,6 +1876,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "same-file"
version = "1.0.6"
@ -1775,6 +1948,17 @@ dependencies = [
"syn 2.0.10",
]
[[package]]
name = "serde_json"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.12"
@ -1806,6 +1990,31 @@ dependencies = [
"digest",
]
[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.8"
@ -1859,6 +2068,23 @@ dependencies = [
"wayland-client",
]
[[package]]
name = "smol"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
dependencies = [
"async-channel",
"async-executor",
"async-fs",
"async-io",
"async-lock",
"async-net",
"async-process",
"blocking",
"futures-lite",
]
[[package]]
name = "socket2"
version = "0.4.9"
@ -1869,6 +2095,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "ssh2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7fe461910559f6d5604c3731d00d2aafc4a83d1665922e280f42f9a168d5455"
dependencies = [
"bitflags",
"libc",
"libssh2-sys",
"parking_lot 0.11.2",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
@ -1982,6 +2220,30 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"num_cpus",
"pin-project-lite",
"tokio-macros",
"windows-sys 0.45.0",
]
[[package]]
name = "tokio-macros"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "toml"
version = "0.7.3"
@ -2065,11 +2327,19 @@ name = "ubusman"
version = "0.1.0"
dependencies = [
"anyhow",
"async-ssh2-lite",
"directories-next",
"eframe",
"egui",
"hex",
"lazy-regex",
"serde",
"serde_json",
"shell-escape",
"smol",
"ssh2",
"thiserror",
"tokio",
"toml",
]
@ -2115,6 +2385,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"

View File

@ -7,9 +7,17 @@ edition = "2021"
[dependencies]
anyhow = "1.0.70"
async-ssh2-lite = { version = "0.4.5", features = ["async-io"] }
directories-next = "2.0.0"
eframe = "0.21.3"
egui = "0.21.0"
hex = "0.4.3"
lazy-regex = "2.5.0"
serde = { version = "1.0.158", features = ["derive"] }
serde_json = "1.0.94"
shell-escape = "0.1.5"
smol = "1.3.0"
ssh2 = "0.9.4"
thiserror = "1.0.40"
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }
toml = "0.7.3"

View File

@ -1,9 +1,42 @@
use std::net::ToSocketAddrs;
#[cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use anyhow::Result;
use smol::Async;
use async_ssh2_lite::{AsyncSession, AsyncSessionStream};
use app::App;
mod app;
use crate::ubus::Ubus;
fn main() -> eframe::Result<()> {
mod app;
mod ubus;
#[tokio::main]
async fn main() -> Result<()> {
let address = "172.24.224.1:22";
let username = "root";
let password = "admin01";
let mut session = AsyncSession::<async_ssh2_lite::AsyncIoTcpStream>::connect(
address.to_socket_addrs()?.next().unwrap(),
None,
).await?;
session.handshake().await?;
session.userauth_password(username, password).await?;
let ubus = Ubus::new(session);
// dbg!(ubus.call(
// "container",
// "get_features",
// None
// ).await?
// );
dbg!(ubus.wait_for(&vec!["network"]).await?);
/*
let mut native_options = eframe::NativeOptions::default();
native_options.decorated = true;
native_options.resizable = true;
@ -17,5 +50,8 @@ fn main() -> eframe::Result<()> {
Box::new(app)
})
)
*/
Ok(())
}

220
src/ubus.rs Normal file
View File

@ -0,0 +1,220 @@
use std::{io::Read, borrow::Cow, net::TcpStream};
use async_ssh2_lite::AsyncSession;
use lazy_regex::regex_captures;
use serde_json::Value;
use shell_escape::unix::escape;
use hex::FromHex;
use anyhow::{Result, bail, anyhow};
use smol::{Async, io::AsyncReadExt};
use thiserror::Error;
pub struct Ubus {
session: AsyncSession<Async<TcpStream>>
}
#[derive(Debug)]
pub enum UbusParamType {
Unknown,
Integer,
Boolean,
Table,
String,
Array,
Double
}
#[derive(Error, Debug)]
pub enum UbusError {
#[error("Invalid command")]
InvalidCommand,
#[error("Invalid argument")]
InvalidArgument,
#[error("Method not found")]
MethodNotFound,
#[error("Not found")]
NotFound,
#[error("No response")]
NoData,
#[error("Permission denied")]
PermissionDenied,
#[error("Request timed out")]
Timeout,
#[error("Operation not supported")]
NotSupported,
#[error("Unknown error")]
UnknownError,
#[error("Connection failed")]
ConnectionFailed,
#[error("Out of memory")]
NoMemory,
#[error("Parsing message data failed")]
ParseError,
#[error("System error")]
SystemError,
}
#[derive(Debug)]
pub struct UbusObject {
pub name: String,
pub id: u32,
pub methods: Vec<(String, Vec<(String, UbusParamType)>)>
}
fn parse_parameter_type(param: &str) -> Option<UbusParamType> {
use UbusParamType::*;
match param {
"String" => Some(String),
"Boolean" => Some(Boolean),
"Integer" => Some(Integer),
"Double" => Some(Double),
"Table" => Some(Table),
"Array" => Some(Array),
"(unknown)" => Some(Unknown),
_ => None
}
}
fn parse_error_code(code: i32) -> Option<UbusError> {
use UbusError::*;
match code {
0 => None,
1 => Some(InvalidCommand),
2 => Some(InvalidArgument),
3 => Some(MethodNotFound),
4 => Some(NotFound),
5 => Some(NoData),
6 => Some(PermissionDenied),
7 => Some(Timeout),
8 => Some(NotSupported),
9 => Some(UnknownError),
10 => Some(ConnectionFailed),
11 => Some(NoMemory),
12 => Some(ParseError),
13 => Some(SystemError),
_ => Some(UnknownError)
}
}
fn parse_hex_id(id: &str) -> Result<u32> {
let [byte1, byte2, byte3, byte4] = <[u8; 4]>::from_hex(id)?;
let byte1 = (byte1 as u32) << 24;
let byte2 = (byte2 as u32) << 16;
let byte3 = (byte3 as u32) << 8;
let byte4 = (byte4 as u32) << 0;
return Ok(byte1 + byte2 + byte3 + byte4);
}
fn escape_json(json: &Value) -> String {
escape(Cow::from(json.to_string())).into()
}
impl Ubus {
pub fn new(session: AsyncSession<Async<TcpStream>>) -> Ubus {
Ubus {
session
}
}
async fn exec_cmd(&self, cmd: &str) -> Result<String> {
let mut channel = self.session.channel_session().await?;
channel.exec(cmd).await?;
channel.close().await?;
if let Some(err) = parse_error_code(channel.exit_status()?) {
return Err(err.into())
}
let mut output = String::new();
channel.read_to_string(&mut output).await?;
Ok(output)
}
pub async fn list(self, path: Option<&str>) -> Result<Vec<String>> {
let output = match path {
Some(path) => self.exec_cmd(&format!("ubus list {}", path)).await?,
None => self.exec_cmd("ubus list").await?,
};
Ok(output.lines().map(ToOwned::to_owned).collect::<Vec<_>>())
}
pub async fn list_verbose(self, path: Option<&str>) -> Result<Vec<UbusObject>> {
let output = match path {
Some(path) => self.exec_cmd(&format!("ubus -v list {}", path)).await?,
None => self.exec_cmd("ubus -v list").await?,
};
let mut cur_name = None;
let mut cur_id = None;
let mut cur_methods = vec![];
let mut objects = vec![];
for line in output.lines() {
if let Some((_, name, id)) = regex_captures!(r"^'([\w.-]+)' @([0-9a-zA-Z]+)$", line) {
if cur_name.is_some() && cur_id.is_some() {
objects.push(UbusObject {
id: cur_id.unwrap(),
name: cur_name.unwrap(),
methods: cur_methods
});
cur_methods = vec![];
}
cur_name = Some(name.into());
cur_id = Some(parse_hex_id(id)?);
} else if let Some((_, name, params_body)) = regex_captures!(r#"^\s+"([\w-]+)":\{(.*)}$"#, line) {
let mut params = vec![];
if !params_body.is_empty() {
for param in params_body.split(",") {
let (_, name, param_type_name) = regex_captures!(r#"^"([\w-]+)":"(\w+)"$"#, param)
.ok_or(anyhow!("Failed to parse parameter '{}' in line '{}'", param, line))?;
let param_type = parse_parameter_type(param_type_name)
.ok_or(anyhow!("Unknown parameter type '{}'", param_type_name))?;
params.push((name.into(), param_type));
}
}
cur_methods.push((name.into(), params));
} else {
bail!("Failed to parse line '{}'", line);
}
}
Ok(objects)
}
pub async fn call(self, path: &str, method: &str, message: Option<&Value>) -> Result<Value> {
let cmd = match message {
Some(msg) => format!("ubus call {} {} {}", path, method, escape_json(msg)),
None => format!("ubus call {} {}", path, method),
};
let output = self.exec_cmd(&cmd).await?;
let value = serde_json::from_str::<Value>(&output)?;
Ok(value)
}
pub async fn send(self, event_type: &str, message: Option<&Value>) -> Result<()> {
let cmd = match message {
Some(msg) => format!("ubus send {} {}", event_type, escape_json(msg)),
None => format!("ubus send {}", event_type),
};
self.exec_cmd(&cmd).await?;
Ok(())
}
pub async fn wait_for(self, objects: &[&str]) -> Result<()> {
if objects.len() < 1 {
bail!("At least 1 object is required")
}
let cmd = format!("ubus wait_for {}", objects.join(" "));
let mut channel = self.session.channel_session().await?;
channel.exec(&cmd).await?;
channel.close().await?;
Ok(())
}
}