onvif-server
A spec-compliant ONVIF Profile S device server library for Rust. Implement the service traits for your camera hardware and get a fully functional ONVIF-compatible device accessible by any standard ONVIF client.
ONVIF Profile S coverage
| Service | Status |
|---|---|
| Device | Supported |
| Media | Supported |
| PTZ | Supported |
| Imaging | Supported |
| Events | Supported |
Features
| Feature | Default | Description |
|---|---|---|
discovery |
no | WS-Discovery multicast listener on 239.255.255.250:3702 via socket2 |
Installation
[]
= "0.1"
Or via cargo-add:
cargo add onvif-server
The discovery feature
To enable automatic device discovery on the local network:
[]
= { = "0.1", = ["discovery"] }
MSRV
Minimum supported Rust version: 1.85.1 (pinned in rust-toolchain.toml).
Quick start
use ;
;
async
DeviceService is the only required service — .build() returns
Err(BuildError::MissingRequiredService) if it is omitted. All other services
(Media, PTZ, Imaging, Events) are optional; unregistered services are simply not
advertised and their routes are not mounted.
Implementing service traits
All five traits (DeviceService, MediaService, PTZService, ImagingService,
EventService) provide default implementations for every method. Unoverridden
methods return Err(OnvifError::NotImplemented), which the SOAP layer converts to a
well-formed SOAP fault with the ONVIF ter:ActionNotSupported subcode. Clients see a
standards-compliant fault rather than a connection error.
You can implement services incrementally: start with the methods your ONVIF client actually calls and add more as needed.
WS-Security
Call .auth(username, password) on the builder to enable WS-Security UsernameToken
digest authentication. When enabled, every SOAP request must carry a valid
UsernameToken header; requests without one receive a SOAP authentication fault.
GetSystemDateAndTime is automatically exempt from authentication, as required by
the ONVIF specification (clients must retrieve device time before they can compute a
valid digest).
When .auth() is not called the server runs unauthenticated and all operations
are accessible without credentials.
WS-Discovery
Enable the discovery feature and the server spawns a background UDP listener when
.run() is called:
- Joins IPv4 multicast group
239.255.255.250on port3702. - Parses incoming datagrams; ignores anything that is not a well-formed WS-Discovery
Probemessage. - Responds with a
ProbeMatchesmessage embedding the device XAddr (http://<advertised_host>:<port>/onvif/device_service) and a stable EndpointReference UUID.
Use .discovery_uuid(uuid::Uuid) on the builder to supply a fixed UUID so the
device identity is stable across restarts. When not set, a random UUID-v4 is
generated at build time.
The probe-parsing and probe-response helpers (discovery_is_probe,
discovery_build_probe_match) are always compiled and available without the feature
flag, which makes them usable in tests.
Example: virtual PTZ camera
The virtual_ptz example is a fully functional in-memory PTZ camera implementing all
five service traits. It demonstrates sharing state across multiple service
registrations using Arc<Mutex<_>>.
cargo run --example virtual_ptz
The server starts on port 8080 with credentials admin/admin. Connect any ONVIF
client (ONVIF Device Manager, VLC, Frigate, Home Assistant, python-onvif-zeep) to
http://<host>:8080/onvif/device_service.
Documentation
- API reference: https://docs.rs/onvif-server
- User guide (mdBook): published to GitHub Pages at https://NavistAu.github.io/onvif-server once the repository is public
Contributing
See CONTRIBUTING.md.
License
The Rust source code in this repository is dual-licensed under either:
- MIT License (LICENSE-MIT), or
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.
The bundled WSDL and XSD files under wsdl/ are verbatim official ONVIF
specification documents and are not covered by the MIT/Apache-2.0 licenses above.
They are distributed under the ONVIF license; see LICENSE-ONVIF for
the full terms.
Copyright Joshua Hogendorn / NavistAu.