Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quick Start

The entry point is OnvifServer::builder(), which returns an OnvifServerBuilder. Chain builder methods to configure the server, call .build() to validate and construct it, then call .run().await to bind the port and begin serving.

A minimal usable device

An empty impl DeviceService for MyCamera {} compiles, but a real client faults immediately — GetDeviceInformation and GetStreamUri have no working default. The smallest device a client can actually use implements those few operations and lets the framework handle the rest:

use async_trait::async_trait;
use onvif_server::{DeviceInfo, DeviceService, MediaService, OnvifError, OnvifServer};

#[derive(Clone)]
struct MinimalCamera {
    media_host: String, // the camera's routable IP, used in stream/snapshot URIs
}

#[async_trait]
impl DeviceService for MinimalCamera {
    async fn get_device_information(&self) -> Result<DeviceInfo, OnvifError> {
        Ok(DeviceInfo {
            manufacturer: "Example Corp".into(),
            model: "Minimal-1".into(),
            firmware_version: "1.0.0".into(),
            serial_number: "SN-0001".into(),
            hardware_id: "minimal-hw-1".into(),
        })
    }
    // get_scopes / get_hostname / get_system_date_and_time use working defaults.
}

#[async_trait]
impl MediaService for MinimalCamera {
    // profiles() defaults to one 1920x1080 H264 "MainProfile".
    async fn get_stream_uri(&self, _profile: &str) -> Result<String, OnvifError> {
        Ok(format!("rtsp://{}:8554/stream", self.media_host))
    }
    async fn get_snapshot_uri(&self, _profile: &str) -> Result<String, OnvifError> {
        Ok(format!("http://{}:8080/snapshot.jpg", self.media_host))
    }
}

#[tokio::main]
async fn main() {
    let host = "192.168.1.10"; // the address clients route to
    let cam = MinimalCamera { media_host: host.into() };

    OnvifServer::builder()
        .port(8080)
        .advertised_host(host)
        .device_service(cam.clone())
        .media_service(cam)
        .auth("admin", "password")
        .build()
        .expect("build failed")
        .run()
        .await
        .expect("server error");
}

This is the minimal_device example — run it with cargo run --example minimal_device. For a fuller device that implements all five service traits (PTZ, imaging, events) see virtual_ptz. For what each operation does and what still faults by default, see Operation Coverage.

Verify it is serving

GetSystemDateAndTime is auth-exempt (ONVIF requires it so clients can sync clocks before authenticating), which makes it a perfect no-credentials smoke test:

curl -s http://192.168.1.10:8080/onvif/device_service \
  -H 'Content-Type: application/soap+xml' \
  -d '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
        <s:Body>
          <GetSystemDateAndTime xmlns="http://www.onvif.org/ver10/device/wsdl"/>
        </s:Body>
      </s:Envelope>'

You should get a GetSystemDateAndTimeResponse with the current UTC time. A client then authenticates (see WS-Security) and calls GetDeviceInformation, GetProfiles, and GetStreamUri to reach the stream.

Builder methods

.port(u16)

The TCP port the server listens on. Defaults to 8080.

.advertised_host(&str)

The host address embedded in XAddr URLs returned to ONVIF clients via GetCapabilities, GetServices, and WS-Discovery responses.

ONVIF clients use these URLs to make follow-up requests, so this must be a routable address from the client’s perspective — for example "192.168.1.10", not "0.0.0.0". Defaults to "0.0.0.0" for backward compatibility.

.device_service(impl DeviceService)

Registers the Device Management Service implementation. This is the only required service.build() returns Err(BuildError::MissingRequiredService) if it is omitted. The device service handles core ONVIF operations such as GetSystemDateAndTime, GetCapabilities, GetDeviceInformation, and others.

.media_service(impl MediaService)

Registers the Media Service implementation. Optional — if omitted, the /onvif/media_service route is not mounted and media capabilities are not advertised in GetCapabilities.

.ptz_service(impl PTZService)

Registers the PTZ Service implementation. Optional — if omitted, the /onvif/ptz_service route is not mounted.

.imaging_service(impl ImagingService)

Registers the Imaging Service implementation. Optional — if omitted, the /onvif/imaging_service route is not mounted.

.event_service(impl EventService)

Registers the Event Service implementation. Optional — if omitted, the /onvif/events_service route is not mounted.

.auth(&str, &str)

Enables WS-Security UsernameToken digest authentication with the given username and password. When this method is not called, the server runs unauthenticated and all operations are accessible without credentials. See WS-Security for details.

.discovery_uuid(uuid::Uuid)

Overrides the stable WS-Discovery EndpointReference UUID for this device. When not called, a random UUID-v4 is used at build time. Callers that need a deterministic identity across restarts should supply a stable UUID derived from hardware ID or stored configuration.

.build()

Validates configuration and constructs the OnvifServer. Returns Err(BuildError::MissingRequiredService("device_service")) if no device service was registered.

.run().await

Binds 0.0.0.0:<port> and starts serving SOAP requests. Does not return until the server shuts down. Requires a tokio async runtime. Returns RunError::Io on TCP bind failure.