Skip to main content

onvif_server/
lib.rs

1//! # onvif-server
2//!
3//! An ONVIF **Profile S streaming-core** device server library for Rust.
4//!
5//! Implement the service traits for your camera hardware to expose a device that
6//! standard ONVIF clients (VMS, NVR, Home Assistant, Frigate, python-onvif-zeep,
7//! ONVIF Device Manager) can discover and stream from. It targets the Profile S
8//! streaming core, not every ONVIF operation — see the Operation Coverage matrix
9//! in the user guide for the exact support claims.
10//!
11//! ## ONVIF Profile S coverage
12//!
13//! | Service        | Status        |
14//! |----------------|---------------|
15//! | Device         | Supported     |
16//! | Media          | Supported     |
17//! | PTZ            | Supported     |
18//! | Imaging        | Supported     |
19//! | Events         | Supported     |
20//!
21//! ## Quick start
22//!
23//! An empty `impl DeviceService for MyCamera {}` compiles but faults on the first
24//! real request — `GetDeviceInformation` and `GetStreamUri` have no working
25//! default. This is the smallest *usable* device (see the `minimal_device`
26//! example, and the Operation Coverage matrix in the user guide for what each
27//! operation does by default):
28//!
29//! ```rust,no_run
30//! use async_trait::async_trait;
31//! use onvif_server::{DeviceInfo, DeviceService, MediaService, OnvifError, OnvifServer};
32//!
33//! #[derive(Clone)]
34//! struct MinimalCamera {
35//!     media_host: String,
36//! }
37//!
38//! #[async_trait]
39//! impl DeviceService for MinimalCamera {
40//!     async fn get_device_information(&self) -> Result<DeviceInfo, OnvifError> {
41//!         Ok(DeviceInfo {
42//!             manufacturer: "Example Corp".into(),
43//!             model: "Minimal-1".into(),
44//!             firmware_version: "1.0.0".into(),
45//!             serial_number: "SN-0001".into(),
46//!             hardware_id: "minimal-hw-1".into(),
47//!         })
48//!     }
49//! }
50//!
51//! #[async_trait]
52//! impl MediaService for MinimalCamera {
53//!     async fn get_stream_uri(&self, _profile: &str) -> Result<String, OnvifError> {
54//!         Ok(format!("rtsp://{}:8554/stream", self.media_host))
55//!     }
56//! }
57//!
58//! #[tokio::main]
59//! async fn main() {
60//!     let host = "192.168.1.10";
61//!     let cam = MinimalCamera { media_host: host.into() };
62//!     OnvifServer::builder()
63//!         .port(8080)
64//!         .advertised_host(host)
65//!         .device_service(cam.clone())
66//!         .media_service(cam)
67//!         .auth("admin", "password")
68//!         .build()
69//!         .expect("build failed")
70//!         .run()
71//!         .await
72//!         .expect("server error");
73//! }
74//! ```
75//!
76//! ## WS-Security
77//!
78//! Call `.auth(username, password)` on the builder to enable WS-Security
79//! UsernameToken digest authentication. `GetSystemDateAndTime` is automatically
80//! exempt from auth (required by ONVIF spec for clock synchronisation before
81//! the client has valid credentials).
82//!
83//! ## WS-Discovery
84//!
85//! Enable the optional `discovery` feature to have the server respond to
86//! WS-Discovery multicast probes on `239.255.255.250:3702`, making the device
87//! auto-discoverable on the local network.
88//!
89//! ```toml
90//! [dependencies]
91//! onvif-server = { version = "0.1", features = ["discovery"] }
92//! ```
93
94mod constants;
95pub mod discovery;
96mod error;
97pub mod generated;
98mod server;
99pub mod service;
100pub mod traits;
101mod wsdl_loader;
102
103pub use constants::*;
104pub use error::OnvifError;
105pub use generated::{
106    DeviceInfo, HostnameInformation, ImagingSettings, MediaProfile, NetworkInterface, PTZPreset,
107    PTZStatusResult, Scope, ScopeDefinition, VideoEncoderConfiguration, VideoSource,
108    VideoSourceConfiguration,
109};
110pub use server::{BuildError, OnvifServer, OnvifServerBuilder, RunError};
111pub use service::device::DeviceServiceHandler;
112pub use service::events::EventServiceHandler;
113pub use service::imaging::ImagingServiceHandler;
114pub use service::media::MediaServiceHandler;
115pub use service::ptz::PTZServiceHandler;
116pub use soap_server::WsdlError;
117pub use soap_server::WsdlLoader;
118pub use traits::{DeviceService, EventService, ImagingService, MediaService, PTZService};
119pub use wsdl_loader::EmbeddedWsdlLoader;
120
121// ─── Discovery helpers ────────────────────────────────────────────────────────
122//
123// Thin wrappers exposing the WS-Discovery probe-parsing and probe-response
124// building functions. These are always available (the underlying logic is pure
125// XML); only the UDP listener requires the `discovery` feature.
126
127/// Returns `true` when `msg` is a well-formed WS-Discovery `Probe` message
128/// (SOAP body first child = `Probe` in namespace
129/// `http://schemas.xmlsoap.org/ws/2005/04/discovery`).
130pub fn discovery_is_probe(msg: &[u8]) -> bool {
131    discovery::is_probe_message(msg)
132}
133
134/// Build a WS-Discovery `ProbeMatches` response XML with a stable
135/// `device_uuid` embedded in `EndpointReference/Address`.
136pub fn discovery_build_probe_match(
137    relates_to: &str,
138    xaddr: &str,
139    device_uuid: uuid::Uuid,
140) -> String {
141    discovery::build_probe_match(relates_to, xaddr, device_uuid)
142}