feat: Added get_status for client and the Status data structure for deserializing API response

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2022-06-28 00:22:37 +02:00
parent 8576f252ba
commit 65f1b0bc5e
No known key found for this signature in database
GPG Key ID: 0C87282F76E61283
4 changed files with 121 additions and 7 deletions

View File

@ -11,10 +11,13 @@ license = "GPL-3.0"
maintenance = { status = "experimental" } maintenance = { status = "experimental" }
[dependencies] [dependencies]
serde_json = "1.0.81" serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hyper = { version = "0.14", features = ["full"] } hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-test = "0.4.2"
log = "0.4.17" log = "0.4.17"
env_logger = "0.9.0" env_logger = "0.9.0"
cookie = "0.16" cookie = "0.16"
[dev-dependencies]
tokio-test = "0.4.2"

View File

@ -4,10 +4,12 @@
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use cookie::Cookie; use cookie::Cookie;
use serde_json::json; use serde_json::{json, Value};
use hyper::{Method, Request, body::Body, header::{CONTENT_TYPE, SET_COOKIE, AUTHORIZATION, COOKIE}, client::HttpConnector}; use hyper::{Method, Request, body::Body, header::{CONTENT_TYPE, SET_COOKIE, AUTHORIZATION, COOKIE}, client::HttpConnector};
use log::{trace, debug}; use log::{trace, debug};
use crate::router_info::Status;
/// A structure to hold all the necessary data to connect to the router. /// A structure to hold all the necessary data to connect to the router.
pub struct Client { pub struct Client {
ip: String, ip: String,
@ -21,9 +23,9 @@ pub struct Client {
impl Client { impl Client {
/// Create a new Client using only the password, and using `192.168.1.1` as /// Create a new Client using only the password, and using `192.168.1.1` as
/// target IP, and `admin` as the default username. /// target IP, and `admin` as the default username.
pub fn new(password: &str) -> Client { pub fn new(password: &str) -> Self {
trace!("Creating a new client."); trace!("Creating a new client.");
return Client { return Self {
ip: Ipv4Addr::new(192, 168, 1, 1).to_string(), ip: Ipv4Addr::new(192, 168, 1, 1).to_string(),
username: "admin".to_string(), username: "admin".to_string(),
password: password.to_string(), password: password.to_string(),
@ -57,8 +59,10 @@ impl Client {
.expect("There was an issue contacting the router.").into_parts(); .expect("There was an issue contacting the router.").into_parts();
debug!("Status is {}.", parts.status.as_str()); debug!("Status is {}.", parts.status.as_str());
debug!("Body was: '{}'.", std::str::from_utf8( let body_bytes = hyper::body::to_bytes(body).await.unwrap();
&hyper::body::to_bytes(body).await.unwrap()).unwrap()); let json: Value = serde_json::from_slice(&body_bytes)
.expect("Could not parse JSON.");
debug!("Body was: '{}'.", std::str::from_utf8(&body_bytes).unwrap());
assert!(parts.status.is_success(), "Router answered with something else assert!(parts.status.is_success(), "Router answered with something else
than a success code."); than a success code.");
@ -68,6 +72,44 @@ impl Client {
} }
assert!(!self.cookies.is_empty(), assert!(!self.cookies.is_empty(),
"No cookie detected on login, there should be an error."); "No cookie detected on login, there should be an error.");
assert!(json["status"].as_u64().unwrap() == 0, "Status wasn't 0.");
self.context_id =
Some(json["data"]["contextID"].as_str().unwrap().to_string());
}
/// Get various status data from the router.
pub async fn get_status(&self) -> Status {
assert!(!self.cookies.is_empty() && self.context_id.is_some(),
"Cannot get status without logging in beforehand.");
trace!("Getting router status.");
let post_data = json!({
"service": "DeviceInfo",
"method": "get",
"parameters": {}
});
let req = Request::builder()
.method(Method::POST)
.uri(format!("http://{}/ws", self.ip))
.header(CONTENT_TYPE, "application/x-sah-ws-4-call+json")
.header("X-Context", self.context_id.clone().unwrap())
.header(COOKIE, self.cookies.join("; "))
.body(Body::from(post_data.to_string()))
.expect("Could not build request.");
let (parts, body) = self.client.request(req).await
.expect("There was an issue contacting the router.").into_parts();
debug!("Status is {}.", parts.status.as_str());
let body_bytes = hyper::body::to_bytes(body).await.unwrap();
let json: Value = serde_json::from_slice(&body_bytes)
.expect("Could not parse JSON.");
debug!("Body was: '{}'.", std::str::from_utf8(&body_bytes).unwrap());
assert!(parts.status.is_success(), "Router answered with something else
than a success code.");
let status: Status = serde_json::from_value(json["status"].clone())
.expect("Looks like the deserialized data is incomplete.");
debug!("Deserialized status is: {:?}", status);
return status;
} }
/// Logout from the router. /// Logout from the router.

View File

@ -2,3 +2,4 @@
//! A crate aimed to gather (read only) data from a Livebox 4 or more recent. //! A crate aimed to gather (read only) data from a Livebox 4 or more recent.
pub mod client; pub mod client;
pub mod router_info;

68
src/router_info.rs Normal file
View File

@ -0,0 +1,68 @@
///! All the necessary struct elements to store the router status.
use serde::Deserialize;
/// The Status structure is used to store status data from the router.
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct Status {
#[serde(rename(deserialize = "Manufacturer"))]
pub manufacturer: String,
#[serde(rename(deserialize = "ManufacturerOUI"))]
pub manufacturer_oui: String,
#[serde(rename(deserialize = "ModelName"))]
pub model_name: String,
#[serde(rename(deserialize = "Description"))]
pub description: String,
#[serde(rename(deserialize = "ProductClass"))]
pub product_class: String,
#[serde(rename(deserialize = "SerialNumber"))]
pub serial_number: String,
#[serde(rename(deserialize = "HardwareVersion"))]
pub hardware_version: String,
#[serde(rename(deserialize = "SoftwareVersion"))]
pub software_version: String,
#[serde(rename(deserialize = "RescueVersion"))]
pub rescue_version: String,
#[serde(rename(deserialize = "ModemFirmwareVersion"))]
pub modem_firmware_version: String,
#[serde(rename(deserialize = "EnabledOptions"))]
pub enabled_options: String,
#[serde(rename(deserialize = "AdditionalHardwareVersion"))]
pub additional_hardware_version: String,
#[serde(rename(deserialize = "AdditionalSoftwareVersion"))]
pub additional_software_version: String,
#[serde(rename(deserialize = "SpecVersion"))]
pub spec_version: String,
#[serde(rename(deserialize = "ProvisioningCode"))]
pub provisioning_code: String,
#[serde(rename(deserialize = "UpTime"))]
pub uptime: u32,
#[serde(rename(deserialize = "FirstUseDate"))]
pub first_use_date: String,
#[serde(rename(deserialize = "DeviceLog"))]
pub device_log: String,
#[serde(rename(deserialize = "VendorConfigFileNumberOfEntries"))]
pub vendor_configfile_number_of_entries: u32,
#[serde(rename(deserialize = "ManufacturerURL"))]
pub manufacturer_url: String,
#[serde(rename(deserialize = "Country"))]
pub country: String,
#[serde(rename(deserialize = "ExternalIPAddress"))]
pub external_ip_address: String,
#[serde(rename(deserialize = "DeviceStatus"))]
pub device_status: String,
#[serde(rename(deserialize = "NumberOfReboots"))]
pub number_of_reboots: u32,
#[serde(rename(deserialize = "UpgradeOccurred"))]
pub upgrade_occured: bool,
#[serde(rename(deserialize = "ResetOccurred"))]
pub reset_occurred: bool,
#[serde(rename(deserialize = "RestoreOccurred"))]
pub restore_occurred: bool,
#[serde(rename(deserialize = "X_SOFTATHOME-COM_AdditionalSoftwareVersions"))]
pub softathome_additional_software_versions: String,
#[serde(rename(deserialize = "BaseMAC"))]
pub base_mac: String,
}