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)]
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())
}
}