onvif_server/service/
device.rs1use async_trait::async_trait;
2use bytes::Bytes;
3use chrono::{Datelike, Timelike};
4use quick_xml::events::Event;
5use quick_xml::NsReader;
6use soap_server::{escape_attr, escape_text, SoapFault, SoapHandler};
7use std::sync::Arc;
8
9use crate::error::OnvifError;
10use crate::traits::DeviceService;
11
12pub struct DeviceServiceHandler {
13 pub(crate) svc: Arc<dyn DeviceService>,
14 pub(crate) xaddr: String,
15 pub(crate) media_xaddr: String,
16 pub(crate) ptz_xaddr: String,
17 pub(crate) imaging_xaddr: String,
18 pub(crate) events_xaddr: String,
19}
20
21impl DeviceServiceHandler {
22 pub fn new(
23 svc: Arc<dyn DeviceService>,
24 xaddr: impl Into<String>,
25 media_xaddr: impl Into<String>,
26 ptz_xaddr: impl Into<String>,
27 imaging_xaddr: impl Into<String>,
28 events_xaddr: impl Into<String>,
29 ) -> Self {
30 Self {
31 svc,
32 xaddr: xaddr.into(),
33 media_xaddr: media_xaddr.into(),
34 ptz_xaddr: ptz_xaddr.into(),
35 imaging_xaddr: imaging_xaddr.into(),
36 events_xaddr: events_xaddr.into(),
37 }
38 }
39}
40
41#[async_trait]
42impl SoapHandler for DeviceServiceHandler {
43 async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault> {
44 let op = extract_local_name(&body)?;
45 match op.as_str() {
46 "GetSystemDateAndTime" => self.handle_get_system_date_and_time().await,
47 "GetCapabilities" => self.handle_get_capabilities().await,
48 "GetServices" => self.handle_get_services().await,
49 "GetDeviceInformation" => self.handle_get_device_information().await,
50 "GetScopes" => self.handle_get_scopes().await,
51 "GetHostname" => self.handle_get_hostname().await,
52 "GetNetworkInterfaces" => self.handle_get_network_interfaces().await,
53 _ => Err(OnvifError::ActionNotSupported.into_soap_fault()),
54 }
55 }
56}
57
58fn extract_local_name(body: &Bytes) -> Result<String, SoapFault> {
59 let mut reader = NsReader::from_reader(body.as_ref());
60 reader.config_mut().trim_text(true);
61 loop {
62 match reader
63 .read_resolved_event()
64 .map_err(|e| SoapFault::sender(format!("{e}")))?
65 {
66 (_, Event::Start(e)) | (_, Event::Empty(e)) => {
67 let local = std::str::from_utf8(e.local_name().as_ref())
68 .map_err(|e| SoapFault::sender(format!("{e}")))?
69 .to_string();
70 return Ok(local);
71 }
72 (_, Event::Eof) => return Err(SoapFault::sender("Empty body".to_string())),
73 _ => {}
74 }
75 }
76}
77
78impl DeviceServiceHandler {
79 async fn handle_get_system_date_and_time(&self) -> Result<Bytes, SoapFault> {
80 let dt = self
81 .svc
82 .get_system_date_and_time()
83 .await
84 .map_err(|e| e.into_soap_fault())?;
85 let xml = format!(
86 r#"<tds:GetSystemDateAndTimeResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
87 <tds:SystemDateAndTime>
88 <tt:DateTimeType>Manual</tt:DateTimeType>
89 <tt:DaylightSavings>false</tt:DaylightSavings>
90 <tt:TimeZone><tt:TZ>UTC</tt:TZ></tt:TimeZone>
91 <tt:UTCDateTime>
92 <tt:Time><tt:Hour>{}</tt:Hour><tt:Minute>{}</tt:Minute><tt:Second>{}</tt:Second></tt:Time>
93 <tt:Date><tt:Year>{}</tt:Year><tt:Month>{}</tt:Month><tt:Day>{}</tt:Day></tt:Date>
94 </tt:UTCDateTime>
95 </tds:SystemDateAndTime>
96</tds:GetSystemDateAndTimeResponse>"#,
97 dt.hour(),
98 dt.minute(),
99 dt.second(),
100 dt.year(),
101 dt.month(),
102 dt.day()
103 );
104 Ok(Bytes::from(xml))
105 }
106
107 async fn handle_get_capabilities(&self) -> Result<Bytes, SoapFault> {
108 let mut caps = String::new();
110 caps.push_str(&format!(
111 " <tt:Device><tt:XAddr>{}</tt:XAddr></tt:Device>\n",
112 escape_text(&self.xaddr)
113 ));
114 if !self.events_xaddr.is_empty() {
115 caps.push_str(&format!(
116 " <tt:Events>\n <tt:XAddr>{}</tt:XAddr>\n <tt:WSSubscriptionPolicySupport>false</tt:WSSubscriptionPolicySupport>\n <tt:WSPullPointSupport>true</tt:WSPullPointSupport>\n <tt:WSPausableSubscriptionManagerInterfaceSupport>false</tt:WSPausableSubscriptionManagerInterfaceSupport>\n </tt:Events>\n",
117 escape_text(&self.events_xaddr)
118 ));
119 }
120 if !self.imaging_xaddr.is_empty() {
121 caps.push_str(&format!(
122 " <tt:Imaging><tt:XAddr>{}</tt:XAddr></tt:Imaging>\n",
123 escape_text(&self.imaging_xaddr)
124 ));
125 }
126 if !self.media_xaddr.is_empty() {
127 caps.push_str(&format!(
129 " <tt:Media><tt:XAddr>{}</tt:XAddr><tt:StreamingCapabilities/></tt:Media>\n",
130 escape_text(&self.media_xaddr)
131 ));
132 }
133 if !self.ptz_xaddr.is_empty() {
134 caps.push_str(&format!(
135 " <tt:PTZ><tt:XAddr>{}</tt:XAddr></tt:PTZ>\n",
136 escape_text(&self.ptz_xaddr)
137 ));
138 }
139 let xml = format!(
140 "<tds:GetCapabilitiesResponse xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">\n <tds:Capabilities>\n{caps} </tds:Capabilities>\n</tds:GetCapabilitiesResponse>",
141 );
142 Ok(Bytes::from(xml))
143 }
144
145 async fn handle_get_services(&self) -> Result<Bytes, SoapFault> {
146 let mut services = String::new();
147 services.push_str(&format!(
149 " <tds:Service>\n <tds:Namespace>http://www.onvif.org/ver10/device/wsdl</tds:Namespace>\n <tds:XAddr>{}</tds:XAddr>\n <tds:Version><tt:Major>2</tt:Major><tt:Minor>42</tt:Minor></tds:Version>\n </tds:Service>\n",
150 escape_text(&self.xaddr)
151 ));
152 if !self.media_xaddr.is_empty() {
153 services.push_str(&format!(
154 " <tds:Service>\n <tds:Namespace>http://www.onvif.org/ver10/media/wsdl</tds:Namespace>\n <tds:XAddr>{}</tds:XAddr>\n <tds:Version><tt:Major>2</tt:Major><tt:Minor>42</tt:Minor></tds:Version>\n </tds:Service>\n",
155 escape_text(&self.media_xaddr)
156 ));
157 }
158 if !self.ptz_xaddr.is_empty() {
159 services.push_str(&format!(
160 " <tds:Service>\n <tds:Namespace>http://www.onvif.org/ver10/ptz/wsdl</tds:Namespace>\n <tds:XAddr>{}</tds:XAddr>\n <tds:Version><tt:Major>2</tt:Major><tt:Minor>42</tt:Minor></tds:Version>\n </tds:Service>\n",
161 escape_text(&self.ptz_xaddr)
162 ));
163 }
164 if !self.imaging_xaddr.is_empty() {
165 services.push_str(&format!(
166 " <tds:Service>\n <tds:Namespace>http://www.onvif.org/ver20/imaging/wsdl</tds:Namespace>\n <tds:XAddr>{}</tds:XAddr>\n <tds:Version><tt:Major>2</tt:Major><tt:Minor>42</tt:Minor></tds:Version>\n </tds:Service>\n",
167 escape_text(&self.imaging_xaddr)
168 ));
169 }
170 if !self.events_xaddr.is_empty() {
171 services.push_str(&format!(
172 " <tds:Service>\n <tds:Namespace>http://www.onvif.org/ver10/events/wsdl</tds:Namespace>\n <tds:XAddr>{}</tds:XAddr>\n <tds:Version><tt:Major>2</tt:Major><tt:Minor>42</tt:Minor></tds:Version>\n </tds:Service>\n",
173 escape_text(&self.events_xaddr)
174 ));
175 }
176 let xml = format!(
177 "<tds:GetServicesResponse xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">\n{services}</tds:GetServicesResponse>",
178 );
179 Ok(Bytes::from(xml))
180 }
181
182 async fn handle_get_device_information(&self) -> Result<Bytes, SoapFault> {
183 let info = self
184 .svc
185 .get_device_information()
186 .await
187 .map_err(|e| e.into_soap_fault())?;
188 let xml = format!(
189 r#"<tds:GetDeviceInformationResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl">
190 <tds:Manufacturer>{}</tds:Manufacturer>
191 <tds:Model>{}</tds:Model>
192 <tds:FirmwareVersion>{}</tds:FirmwareVersion>
193 <tds:SerialNumber>{}</tds:SerialNumber>
194 <tds:HardwareId>{}</tds:HardwareId>
195</tds:GetDeviceInformationResponse>"#,
196 escape_text(&info.manufacturer),
197 escape_text(&info.model),
198 escape_text(&info.firmware_version),
199 escape_text(&info.serial_number),
200 escape_text(&info.hardware_id)
201 );
202 Ok(Bytes::from(xml))
203 }
204
205 async fn handle_get_scopes(&self) -> Result<Bytes, SoapFault> {
206 let scopes = self
207 .svc
208 .get_scopes()
209 .await
210 .map_err(|e| e.into_soap_fault())?;
211 let mut items = String::new();
212 for s in &scopes {
213 let def = match s.scope_def {
214 crate::generated::types::ScopeDefinition::Fixed => "Fixed",
215 crate::generated::types::ScopeDefinition::Configurable => "Configurable",
216 };
217 items.push_str(&format!(
218 " <tds:Scopes>\n <tt:ScopeDef>{}</tt:ScopeDef>\n <tt:ScopeItem>{}</tt:ScopeItem>\n </tds:Scopes>\n",
219 def, escape_text(&s.scope_item)
220 ));
221 }
222 let xml = format!(
223 "<tds:GetScopesResponse xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">\n{}</tds:GetScopesResponse>",
224 items
225 );
226 Ok(Bytes::from(xml))
227 }
228
229 async fn handle_get_hostname(&self) -> Result<Bytes, SoapFault> {
230 let info = self
231 .svc
232 .get_hostname()
233 .await
234 .map_err(|e| e.into_soap_fault())?;
235 let name_el = match &info.name {
236 Some(n) => format!(" <tt:Name>{}</tt:Name>\n", escape_text(n)),
237 None => String::new(),
238 };
239 let xml = format!(
240 r#"<tds:GetHostnameResponse xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema">
241 <tds:HostnameInformation>
242 <tt:FromDHCP>{}</tt:FromDHCP>
243{} </tds:HostnameInformation>
244</tds:GetHostnameResponse>"#,
245 info.from_dhcp, name_el
246 );
247 Ok(Bytes::from(xml))
248 }
249
250 async fn handle_get_network_interfaces(&self) -> Result<Bytes, SoapFault> {
251 let ifaces = self
252 .svc
253 .get_network_interfaces()
254 .await
255 .map_err(|e| e.into_soap_fault())?;
256 let mut iface_els = String::new();
257 for iface in &ifaces {
258 iface_els.push_str(&format!(
259 r#" <tds:NetworkInterfaces token="{token}">
260 <tt:Enabled>{enabled}</tt:Enabled>
261 <tt:Info>
262 <tt:Name>{name}</tt:Name>
263 <tt:HwAddress>{hw}</tt:HwAddress>
264 <tt:MTU>{mtu}</tt:MTU>
265 </tt:Info>
266 </tds:NetworkInterfaces>
267"#,
268 token = escape_attr(&iface.token),
269 enabled = iface.enabled,
270 name = escape_text(&iface.name),
271 hw = escape_text(&iface.hw_address),
272 mtu = iface.mtu,
273 ));
274 }
275 let xml = format!(
276 "<tds:GetNetworkInterfacesResponse xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tt=\"http://www.onvif.org/ver10/schema\">\n{}</tds:GetNetworkInterfacesResponse>",
277 iface_els
278 );
279 Ok(Bytes::from(xml))
280 }
281}