Skip to main content

onvif_server/service/
imaging.rs

1use async_trait::async_trait;
2use bytes::Bytes;
3use quick_xml::events::Event;
4use quick_xml::NsReader;
5use soap_server::{SoapFault, SoapHandler};
6use std::sync::Arc;
7
8use crate::error::OnvifError;
9use crate::service::xml_util::extract_text_ns;
10use crate::traits::ImagingService;
11
12/// ONVIF namespaces accepted for imaging request elements.
13const ONVIF_IMAGING_NS: &[u8] = b"http://www.onvif.org/ver20/imaging/wsdl";
14const ONVIF_SCHEMA_NS: &[u8] = b"http://www.onvif.org/ver10/schema";
15
16pub struct ImagingServiceHandler {
17    pub(crate) svc: Arc<dyn ImagingService>,
18}
19
20impl ImagingServiceHandler {
21    pub fn new(svc: Arc<dyn ImagingService>) -> Self {
22        Self { svc }
23    }
24}
25
26#[async_trait]
27impl SoapHandler for ImagingServiceHandler {
28    async fn handle(&self, body: Bytes) -> Result<Bytes, SoapFault> {
29        let op = extract_local_name(&body)?;
30        match op.as_str() {
31            "GetImagingSettings" => self.handle_get_imaging_settings(&body).await,
32            _ => Err(OnvifError::ActionNotSupported.into_soap_fault()),
33        }
34    }
35}
36
37fn extract_local_name(body: &Bytes) -> Result<String, SoapFault> {
38    let mut reader = NsReader::from_reader(body.as_ref());
39    reader.config_mut().trim_text(true);
40    loop {
41        match reader
42            .read_resolved_event()
43            .map_err(|e| SoapFault::sender(format!("{e}")))?
44        {
45            (_, Event::Start(e)) | (_, Event::Empty(e)) => {
46                let local = std::str::from_utf8(e.local_name().as_ref())
47                    .map_err(|e| SoapFault::sender(format!("{e}")))?
48                    .to_string();
49                return Ok(local);
50            }
51            (_, Event::Eof) => return Err(SoapFault::sender("Empty body".to_string())),
52            _ => {}
53        }
54    }
55}
56
57fn extract_text_element(body: &Bytes, element_name: &str) -> Result<String, SoapFault> {
58    extract_text_ns(body, element_name, &[ONVIF_IMAGING_NS, ONVIF_SCHEMA_NS])
59}
60
61impl ImagingServiceHandler {
62    async fn handle_get_imaging_settings(&self, body: &Bytes) -> Result<Bytes, SoapFault> {
63        let token = extract_text_element(body, "VideoSourceToken")?;
64        let settings = self
65            .svc
66            .get_imaging_settings(token)
67            .await
68            .map_err(|e| e.into_soap_fault())?;
69
70        let mut inner = String::new();
71        if let Some(v) = settings.brightness {
72            inner.push_str(&format!("<tt:Brightness>{}</tt:Brightness>", v as i32));
73        }
74        if let Some(v) = settings.contrast {
75            inner.push_str(&format!("<tt:Contrast>{}</tt:Contrast>", v as i32));
76        }
77        if let Some(v) = settings.sharpness {
78            inner.push_str(&format!("<tt:Sharpness>{}</tt:Sharpness>", v as i32));
79        }
80        if let Some(v) = settings.color_saturation {
81            inner.push_str(&format!(
82                "<tt:ColorSaturation>{}</tt:ColorSaturation>",
83                v as i32
84            ));
85        }
86        // Emit a single <tt:WhiteBalance> with both gain children when either is present.
87        // ONVIF schema requires one element with CrGain and CbGain as children,
88        // not two separate <tt:WhiteBalance> elements (BLOCK-OS-C06 fix).
89        if settings.white_balance_cr_gain.is_some() || settings.white_balance_cb_gain.is_some() {
90            let cr = settings
91                .white_balance_cr_gain
92                .map(|v| format!("<tt:CrGain>{v}</tt:CrGain>"))
93                .unwrap_or_default();
94            let cb = settings
95                .white_balance_cb_gain
96                .map(|v| format!("<tt:CbGain>{v}</tt:CbGain>"))
97                .unwrap_or_default();
98            inner.push_str(&format!(
99                "<tt:WhiteBalance><tt:Mode>MANUAL</tt:Mode>{cr}{cb}</tt:WhiteBalance>"
100            ));
101        }
102
103        let xml = format!(
104            r#"<timg:GetImagingSettingsResponse xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" xmlns:tt="http://www.onvif.org/ver10/schema"><timg:ImagingSettings>{inner}</timg:ImagingSettings></timg:GetImagingSettingsResponse>"#,
105            inner = inner,
106        );
107        Ok(Bytes::from(xml))
108    }
109}