use super::{xml_bool, xml_str};
use crate::error::OnvifError;
use crate::soap::{SoapError, XmlNode};
#[derive(Debug, Clone, Default)]
pub struct DeviceInfo {
pub manufacturer: String,
pub model: String,
pub firmware_version: String,
pub serial_number: String,
pub hardware_id: String,
}
impl DeviceInfo {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
Ok(Self {
manufacturer: xml_str(resp, "Manufacturer").unwrap_or_default(),
model: xml_str(resp, "Model").unwrap_or_default(),
firmware_version: xml_str(resp, "FirmwareVersion").unwrap_or_default(),
serial_number: xml_str(resp, "SerialNumber").unwrap_or_default(),
hardware_id: xml_str(resp, "HardwareId").unwrap_or_default(),
})
}
}
#[derive(Debug, Clone)]
pub struct SystemDateTime {
pub utc_unix: Option<i64>,
pub timezone: String,
pub daylight_savings: bool,
}
impl SystemDateTime {
pub fn utc_offset_secs(&self) -> i64 {
match self.utc_unix {
Some(device_utc) => {
let local_utc = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
device_utc - local_utc
}
None => 0,
}
}
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let sdt = resp
.child("SystemDateAndTime")
.ok_or_else(|| SoapError::missing("SystemDateAndTime"))?;
Ok(Self {
utc_unix: sdt.child("UTCDateTime").and_then(parse_datetime_node),
timezone: sdt
.path(&["TimeZone", "TZ"])
.map(|n| n.text().to_string())
.unwrap_or_default(),
daylight_savings: xml_bool(sdt, "DaylightSavings"),
})
}
}
fn parse_datetime_node(node: &XmlNode) -> Option<i64> {
let year = node
.path(&["Date", "Year"])
.and_then(|n| n.text().parse::<i32>().ok())?;
let month = node
.path(&["Date", "Month"])
.and_then(|n| n.text().parse::<i32>().ok())?;
let day = node
.path(&["Date", "Day"])
.and_then(|n| n.text().parse::<i32>().ok())?;
let hour = node
.path(&["Time", "Hour"])
.and_then(|n| n.text().parse::<i32>().ok())?;
let min = node
.path(&["Time", "Minute"])
.and_then(|n| n.text().parse::<i32>().ok())?;
let sec = node
.path(&["Time", "Second"])
.and_then(|n| n.text().parse::<i32>().ok())?;
Some(civil_to_unix(year, month, day, hour, min, sec))
}
pub(crate) fn civil_to_unix(year: i32, month: i32, day: i32, hour: i32, min: i32, sec: i32) -> i64 {
let mut y = year as i64;
let m = month as i64;
if m <= 2 {
y -= 1;
}
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let mp = if m > 2 { m - 3 } else { m + 9 };
let doy = (153 * mp + 2) / 5 + day as i64 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146_097 + doe - 719_468;
days * 86_400 + hour as i64 * 3600 + min as i64 * 60 + sec as i64
}
#[derive(Debug, Clone, Copy)]
pub struct UtcDateTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
#[derive(Debug, Clone)]
pub struct SetDateTimeRequest {
pub datetime_type: String,
pub daylight_savings: bool,
pub timezone: String,
pub utc_datetime: Option<UtcDateTime>,
}
#[derive(Debug, Clone)]
pub struct Hostname {
pub from_dhcp: bool,
pub name: Option<String>,
}
impl Hostname {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let info = resp
.child("HostnameInformation")
.ok_or_else(|| SoapError::missing("HostnameInformation"))?;
Ok(Self {
from_dhcp: xml_bool(info, "FromDHCP"),
name: xml_str(info, "Name").filter(|s| !s.is_empty()),
})
}
}
#[derive(Debug, Clone)]
pub struct NtpInfo {
pub from_dhcp: bool,
pub servers: Vec<String>,
}
impl NtpInfo {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let info = resp
.child("NTPInformation")
.ok_or_else(|| SoapError::missing("NTPInformation"))?;
Ok(Self {
from_dhcp: xml_bool(info, "FromDHCP"),
servers: info
.children_named("NTPManual")
.chain(info.children_named("NTPFromDHCP"))
.filter_map(|entry| {
xml_str(entry, "DNSname")
.filter(|s| !s.is_empty())
.or_else(|| xml_str(entry, "IPv4Address").filter(|s| !s.is_empty()))
.or_else(|| xml_str(entry, "IPv6Address").filter(|s| !s.is_empty()))
})
.collect(),
})
}
}
#[derive(Debug, Clone)]
pub struct OnvifService {
pub namespace: String,
pub url: String,
pub version_major: u32,
pub version_minor: u32,
}
impl OnvifService {
pub fn is_media2(&self) -> bool {
self.namespace == "http://www.onvif.org/ver20/media/wsdl"
}
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
Ok(resp
.children_named("Service")
.map(|s| Self {
namespace: xml_str(s, "Namespace").unwrap_or_default(),
url: xml_str(s, "XAddr").unwrap_or_default(),
version_major: s
.path(&["Version", "Major"])
.and_then(|n| n.text().parse().ok())
.unwrap_or(0),
version_minor: s
.path(&["Version", "Minor"])
.and_then(|n| n.text().parse().ok())
.unwrap_or(0),
})
.collect())
}
}
#[derive(Debug, Clone)]
pub struct User {
pub username: String,
pub user_level: String,
}
impl User {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("User")
.map(|n| {
let username = xml_str(n, "Username").unwrap_or_default();
let user_level = xml_str(n, "UserLevel").unwrap_or_default();
Ok(Self {
username,
user_level,
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct NetworkInterface {
pub token: String,
pub enabled: bool,
pub name: String,
pub hw_address: String,
pub mtu: u32,
pub ipv4_enabled: bool,
pub ipv4_address: String,
pub ipv4_prefix_length: u32,
pub ipv4_from_dhcp: bool,
pub ipv6_enabled: bool,
pub ipv6_from_dhcp: bool,
pub ipv6_address: Option<String>,
}
impl NetworkInterface {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("NetworkInterfaces")
.map(|n| {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("NetworkInterfaces/@token"))?
.to_string();
let enabled = xml_bool(n, "Enabled");
let name = n
.path(&["Info", "Name"])
.map(|x| x.text().to_string())
.unwrap_or_default();
let hw_address = n
.path(&["Info", "HwAddress"])
.map(|x| x.text().to_string())
.unwrap_or_default();
let mtu = n
.path(&["Info", "MTU"])
.and_then(|x| x.text().parse().ok())
.unwrap_or(0);
let ipv4_enabled = n
.path(&["IPv4", "Enabled"])
.map(|x| x.text() == "true" || x.text() == "1")
.unwrap_or(false);
let ipv4_from_dhcp = n
.path(&["IPv4", "Config", "DHCP"])
.map(|x| x.text() == "true" || x.text() == "1")
.unwrap_or(false);
let ipv4_address = n
.path(&["IPv4", "Config", "Manual", "Address"])
.map(|x| x.text().to_string())
.filter(|s| !s.is_empty())
.or_else(|| {
n.path(&["IPv4", "Config", "FromDHCP", "Address"])
.map(|x| x.text().to_string())
.filter(|s| !s.is_empty())
})
.unwrap_or_default();
let ipv4_prefix_length = n
.path(&["IPv4", "Config", "Manual", "PrefixLength"])
.and_then(|x| x.text().parse().ok())
.or_else(|| {
n.path(&["IPv4", "Config", "FromDHCP", "PrefixLength"])
.and_then(|x| x.text().parse().ok())
})
.unwrap_or(0);
let ipv6_enabled = n
.path(&["IPv6", "Enabled"])
.map(|x| x.text() == "true" || x.text() == "1")
.unwrap_or(false);
let ipv6_from_dhcp = n
.path(&["IPv6", "Config", "DHCP"])
.map(|x| {
let t = x.text();
t == "Stateful" || t == "Stateless" || t == "Both"
})
.unwrap_or(false);
let ipv6_address = n
.path(&["IPv6", "Config", "Manual", "Address"])
.map(|x| x.text().to_string())
.filter(|s| !s.is_empty())
.or_else(|| {
n.path(&["IPv6", "Config", "LinkLocal", "Address"])
.map(|x| x.text().to_string())
.filter(|s| !s.is_empty())
});
Ok(Self {
token,
enabled,
name,
hw_address,
mtu,
ipv4_enabled,
ipv4_address,
ipv4_prefix_length,
ipv4_from_dhcp,
ipv6_enabled,
ipv6_from_dhcp,
ipv6_address,
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct NetworkProtocol {
pub name: String,
pub enabled: bool,
pub ports: Vec<u32>,
}
impl NetworkProtocol {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
Ok(resp
.children_named("NetworkProtocols")
.map(|n| Self {
name: xml_str(n, "Name").unwrap_or_default(),
enabled: xml_bool(n, "Enabled"),
ports: n
.children_named("Port")
.filter_map(|p| p.text().parse().ok())
.collect(),
})
.collect())
}
}
#[derive(Debug, Clone)]
pub struct DnsInformation {
pub from_dhcp: bool,
pub servers: Vec<String>,
pub search_domains: Vec<String>,
}
impl DnsInformation {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let info = resp
.child("DNSInformation")
.ok_or_else(|| SoapError::missing("DNSInformation"))?;
Ok(Self {
from_dhcp: xml_bool(info, "FromDHCP"),
servers: info
.children_named("DNSManual")
.chain(info.children_named("DNSFromDHCP"))
.filter_map(|e| {
xml_str(e, "IPv4Address")
.filter(|s| !s.is_empty())
.or_else(|| xml_str(e, "IPv6Address").filter(|s| !s.is_empty()))
.or_else(|| xml_str(e, "DNSname").filter(|s| !s.is_empty()))
})
.collect(),
search_domains: info
.children_named("SearchDomain")
.map(|n| n.text().to_string())
.filter(|s| !s.is_empty())
.collect(),
})
}
}
#[derive(Debug, Clone)]
pub struct NetworkGateway {
pub ipv4_addresses: Vec<String>,
pub ipv6_addresses: Vec<String>,
}
impl NetworkGateway {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let gw = resp
.child("NetworkGateway")
.ok_or_else(|| SoapError::missing("NetworkGateway"))?;
Ok(Self {
ipv4_addresses: gw
.children_named("IPv4Address")
.filter_map(|n| {
let t = n.text().to_string();
if t.is_empty() { None } else { Some(t) }
})
.collect(),
ipv6_addresses: gw
.children_named("IPv6Address")
.filter_map(|n| {
let t = n.text().to_string();
if t.is_empty() { None } else { Some(t) }
})
.collect(),
})
}
}
#[derive(Debug, Clone)]
pub struct SystemLog {
pub string: Option<String>,
}
impl SystemLog {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
let log = resp
.child("SystemLog")
.ok_or_else(|| SoapError::missing("SystemLog"))?;
Ok(Self {
string: log
.child("String")
.map(|n| n.text().to_string())
.filter(|s| !s.is_empty()),
})
}
}
#[derive(Debug, Clone)]
pub struct RelayOutput {
pub token: String,
pub mode: String,
pub delay_time: String,
pub idle_state: String,
}
impl RelayOutput {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("RelayOutputs")
.map(|n| {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("RelayOutputs/@token"))?
.to_string();
let props = n.child("Properties");
let mode = props
.and_then(|p| p.child("Mode"))
.map(|x| x.text().to_string())
.unwrap_or_default();
let delay_time = props
.and_then(|p| p.child("DelayTime"))
.map(|x| x.text().to_string())
.unwrap_or_default();
let idle_state = props
.and_then(|p| p.child("IdleState"))
.map(|x| x.text().to_string())
.unwrap_or_default();
Ok(Self {
token,
mode,
delay_time,
idle_state,
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct StorageConfiguration {
pub token: String,
pub storage_type: String,
pub local_path: String,
pub storage_uri: String,
pub user: String,
}
impl StorageConfiguration {
pub(crate) fn vec_from_xml(resp: &XmlNode) -> Result<Vec<Self>, OnvifError> {
resp.children_named("StorageConfigurations")
.map(|n| {
let token = n
.attr("token")
.filter(|t| !t.is_empty())
.ok_or_else(|| SoapError::missing("StorageConfigurations/@token"))?
.to_string();
let data = n.child("Data");
let storage_type = data
.and_then(|d| d.attr("type"))
.unwrap_or_default()
.to_string();
let local_path = data
.and_then(|d| d.child("LocalPath"))
.map(|x| x.text().to_string())
.unwrap_or_default();
let storage_uri = data
.and_then(|d| d.child("StorageUri"))
.map(|x| x.text().to_string())
.unwrap_or_default();
let user = data
.and_then(|d| d.child("User"))
.and_then(|u| u.child("UserName"))
.map(|x| x.text().to_string())
.unwrap_or_default();
Ok(Self {
token,
storage_type,
local_path,
storage_uri,
user,
})
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct SystemUris {
pub system_log_uri: Option<String>,
pub support_info_uri: Option<String>,
pub system_backup_uri: Option<String>,
}
impl SystemUris {
pub(crate) fn from_xml(resp: &XmlNode) -> Result<Self, OnvifError> {
Ok(Self {
system_log_uri: resp
.path(&["SystemLogUris", "SystemLogUri", "Uri"])
.map(|n| n.text().to_string())
.filter(|s| !s.is_empty()),
support_info_uri: xml_str(resp, "SupportInfoUri"),
system_backup_uri: xml_str(resp, "SystemBackupUri"),
})
}
}