Releases: smiti1642/oxvif
v0.9.9
Headline: digital input read API — GetDigitalInputs returns each port's token + idle electrical state, completing the read side of the Device-service IO surface that previously only exposed relay outputs. Live input transitions still arrive via PullPoint subscription on the tns1:Device/Trigger/DigitalInput topic (unchanged from 0.9.8).
Added
OnvifSession::get_digital_inputs()/OnvifClient::get_digital_inputs(). ReturnsVec<DigitalInput>where each entry carriestokenandidle_state("closed"/"open", or empty string when the firmware omits the attribute). Mirrors the existingget_relay_outputsshape; no Set-side method is exposed because the Device service spec doesn't define one.- New
DigitalInputtype re-exported from the crate root.PartialEq/Eqderived on bothDigitalInputandRelayOutput.
Fixed
- Compile failure when
quick-xml/encodingis enabled anywhere in the build graph (#1). quick-xml 0.39 cfg-gatesAttribute::unescape_valueaway whenever itsencodingfeature is active, and Cargo feature unification turns that feature on for the whole graph as soon as any sibling crate (e.g.calamine) requests it. The XML attribute parser now goes throughAttribute::decode_and_unescape_value(reader.decoder()), which is always available and decodes identically. Aquick-xmlencoding-on dev-dependency guards against regressions in CI.
Mock server
- Stateful Relay/Input.
MockStatenow carriesrelay_outputsanddigital_inputs;GetRelayOutputs/GetDigitalInputsrender from state, andSetRelayOutputState/SetRelayOutputSettingsmutate state and fault on unknown tokens. - PullPoint IO event topics.
GetEventPropertiesadvertisestns1:Device/Trigger/DigitalInputandtns1:Device/Trigger/Relay;SetRelayOutputStatequeues aRelayOutputevent. /mock/digital-input/:token/pulseand/set?state=...REST hooks (mock-serverfeature) to simulate physical input signals in tests.
Full Changelog: v0.9.7...v0.9.9
v0.9.7
Headline: a fast, scriptable device health check (oxvif::health) — point
it at a camera and get a Pass/Warn/Fail/Skip conformance report with a Profile
S/T/G assessment, a readable alternative to the official ONVIF Device Test Tool.
Also adds the firmware-upgrade / system-restore upload-URI flow and corrects
two write-path XML bugs.
Added
- Health check —
oxvif::health(opt-in, behind thehealthfeature; pure
library code overOnvifSession, no extra dependencies).HealthCheck::new(url).with_credentials(..).run().awaitreturns a
HealthReportof per-checkCheckResults (status + category + timing) plus
aProfileAssessment(S/T/G verdict).Displayrenders a readable summary.- Checks run concurrently and are read-only by default; opt into write/clock
probes via the builder. - New
examples/healthcheck.rs(--features health).
- Device firmware / restore (upload-URI flow) —
start_firmware_upgrade()
→FirmwareUpgradeStartandstart_system_restore()→SystemRestoreStart.
Each returns the upload URI + timing; the caller HTTP-POSTs the image/backup
(the SOAP transport deliberately doesn't carry the binary payload). PartialEqderived on the video-encoder configuration types
(VideoEncoderConfiguration,VideoEncoderConfigurationOptions, and related)
so downstream code can diff configs without hand-written comparisons.
Fixed
SetVideoEncoderConfigurationand PTZSetConfigurationproduced malformed
request XML that some cameras rejected — corrected the element nesting/order.
Docs
- Recorded a read-path audit under
docs/audit-2026-05.md.
v0.9.6
Headline: a built-in mock ONVIF device so downstream crates can unit-test
client code without a real camera — every vendor's ONVIF differs and depending
on a physical IP camera in tests is painful. Also rolls in the session-level
push subscribe and vendor-tolerant OSD parsing.
Added
- Built-in mock ONVIF device —
oxvif::mock(opt-in, behind features).
Stateful (Set persists, Get reflects it) and covers every operation oxvif
implements; state is in-memory (the library never writes to disk — opt into
persistence viaMockState::set_on_change).mockfeature →MockTransport, an in-processTransport(no sockets, no
axum) — the fast unit-test path:
OnvifClient::new("http://mock").with_transport(Arc::new(MockTransport::new())).mock-serverfeature →MockServer, a real axum HTTP server on an
ephemeral port (MockServer::start().await), shutting down on drop — for
cross-process / non-Rust clients.- Both default to no auth (call
.with_auth()/.enforce_auth(true)to
exercise WS-Security) and supportinject_fault(...)for error-path tests. axum/serdeare optional deps enabled only by these features — the
default build is unchanged and axum-free.- The
examples/mock_serverbinary is now a thin wrapper overMockServer
with TOML file persistence (--features mock-server); the mock engine moved
fromexamples/intosrc/mock/. Newtests/mock_workflow.rsdrives one
command from every service against a realMockServer.
OnvifSession::subscribe— delegates the WS-BaseNotification push
subscription that was previously only onOnvifClient.OsdOptions::max_per_text_type. NewHashMap<String, u32>
exposing the per-text-type quotas (Plain,Date,Time,
DateAndTime) some cameras advertise via XML attributes on
<MaximumNumberOfOSDs>(Genetec, recent Hikvision). Lets clients
pre-validateCreateOSDcalls against per-type limits instead of
parsing opaqueter:InvalidArgsfault strings after the fact.
Populated only when fetched viaOnvifSession::get_osd_options
—OnvifClient::get_osd_optionsleaves it empty (spec-strict).OnvifSession::get_osd_optionsnow layers vendor-extension
parsing on top of the spec-strictOnvifClientresult. Two
real-world shapes handled:<MaximumNumberOfOSDs Total="8" Plain="7" DateAndTime="1" .../>
— count fromTotalattribute when element body is empty, plus
per-type quotas from named attributes.<PositionOption>UpperLeft</PositionOption>flat siblings, when
the textbook nested-<Type>shape produces nothing.
v0.9.3
Changed
-
Dependencies bumped to latest stable — keeps the lib.rs / docs.rs
badges green. Zero source changes were required:socket20.5 → 0.6 — oxvif already used the_v4-suffixed multicast
methods that 0.6 makes mandatory, so the upgrade is API-compatible.tokio1.52.0 → 1.52.1 — upstream patch reverting a regression
that causedspawn_blockingto hang under load.toml0.8 → 1.1 (dev-dep only, used by themock_serverexample
for state persistence). MSRV requirement (1.85) already met.
All 420 tests (375 lib + 19 doc + 26 mock server) continue to pass.
Full changelog: https://github.com/smiti1642/oxvif/blob/master/CHANGELOG.md
v0.9.2
Added
discovery::probe_unicast(ip, timeout)— send a WS-DiscoveryProbe
directly to a single known IP via unicast. Useful for "is this device
still there" checks against a known address (e.g. user-added manual
entries) and for cross-subnet detection where multicast cannot reach.
Sends bothNetworkVideoTransmitterandDeviceprobes and
deduplicates the responses by endpoint UUID, matching the behaviour
ofprobe/probe_rounds.
Fixed
-
XML entity decoding in SOAP response text (GeoVision snapshot URIs).
XmlNode::parsenow handlesEvent::GeneralRef(quick-xml 0.39 emits
each&/</Aas a separate event) and accumulates
text runs across events rather than overwriting on eachEvent::Text.
GeoVision cameras return aGetSnapshotUriResponsewith URIs like
http://host/cgi?skey=X&action=update&Snapshot=Video1.Stream1
— valid, RFC-compliant XML escaping. The old parser dropped every
Event::GeneralRefand overwrote text on eachEvent::Text, so only
the fragment after the last&survived; the URI came out as
Snapshot=Video1.Stream1, which the camera's web server rejected
with 500. Decodes the five predefined named entities (amp,lt,
gt,quot,apos) plus numeric character references
(&#NN;/&#xHH;). Unknown entities are preserved verbatim as
&name;so no content is silently lost. Affects every ONVIF response
carrying&-escaped text —StreamUri,SnapshotUri,Scopes,
HostnameInformation, custom metadata — not just GeoVision. -
WS-Addressing namespace regression — restored ~80 missing devices.
build_probenow emits the legacy WS-Addressing 2004/08 namespace
withs:mustUnderstand="1"on theActionandToheaders and an
explicit<wsa:ReplyTo>pointing at the WS-Addressing anonymous URI.
The 0.9.0/0.9.1 probe used the modern 2005/08 namespace, which older
Chinese OEM camera firmwares (Hikvision, Uniview, Dahua-family) silently
reject — they ship with strict ONVIF 2008-era SOAP parsers that only
recognise the 2004/08 wsa namespace. On a real heterogeneous LAN this
regression cost roughly 80 of 195 devices. The new payload matches
byte-for-byte what ODM (via WCF'sUdpDiscoveryEndpoint(WSDiscoveryApril2005))
sends. WS-Discovery 1.1 / 2009 support — which would use the 2005/08
wsa namespace — is deferred until both probes can be sent in parallel. -
Reordered
Byeno longer flaps a live device offline.listen()
now parses the<wsd:AppSequence>SOAP header (InstanceId/
MessageNumber/ optionalSequenceId) and silently drops aBye
whose sequence is comparable to (sameInstanceIdandSequenceId
as) one we have already seen but with an equal-or-lower
MessageNumber. UDP multicast does not guarantee delivery order, so
on noisy LANs an old departure could arrive after a fresh presence
announcement and incorrectly remove a still-online device. Matches
ODM'sNvtDiscovery.fs::process_offlinebehaviour.Hellois never
filtered — at worst a stale Hello resurfaces a live device, which is
harmless. TheDiscoveryEventenum is unchanged: sequence handling
is fully internal. -
probe_roundscancellation. Per-NIC listener tasks are now
spawned viatokio::task::JoinSetinstead oftokio::spawn. When
the surrounding future is dropped (e.g. caller wraps the call in
tokio::select!and a timeout branch wins), every in-flight task is
aborted instead of leaking until its own timeout elapses. Public
API unchanged.
Changed
- Multicast TTL raised from 4 to 32 (
set_multicast_ttl_v4). The
previous value was tuned for a single LAN segment and silently lost
devices on enterprise networks where the camera subnet is reached
through one or two IGMP-routed hops (PIM/IGMP on a core switch). 32
is a middle ground between the original 4 and ODM's "VPN workaround"
TTL of 64 — large enough for typical campus topologies, small enough
to respect the spec's intent that WS-Discovery stays close to the
link.
Full changelog: https://github.com/smiti1642/oxvif/blob/master/CHANGELOG.md
v0.9.0
[0.9.0] - 2026-04-15
Added
- HTTP Digest Authentication — transport layer now supports HTTP Digest
Auth (RFC 7616) as required by ONVIF Profile T §7.1 - Profile T operations — Device, Events, and PTZ mandatory operations for
Profile T compliance - Media2 audio/metadata —
GetAudioSourceConfigurations,
GetAudioEncoderConfigurations,SetAudioEncoderConfiguration,
GetAudioEncoderConfigurationOptions,GetAudioOutputConfigurations,
GetAudioDecoderConfigurations,GetMetadataConfigurations,
SetMetadataConfiguration,GetMetadataConfigurationOptions,
AddConfiguration,RemoveConfiguration - Healthcheck example — new
healthchecksubcommand for the camera
example;--ipand--authCLI flags for direct device targeting - Mock server — refactored to multi-module architecture with stateful
device service, file persistence, WS-Security auth, and snapshot endpoint
Fixed
- XML escape — all user-supplied SOAP parameters are now XML-escaped
before interpolation, preventing XML injection - MetadataConfiguration — PTZFilter alignment corrected for Media2 service
- MediaProfile
video_source_token— now correctly parses<SourceToken>
child element instead of reading the wrong attribute - Transport — HTTP 400 responses are now treated as SOAP Faults with
structured error parsing instead of raw XML dump
Breaking
MediaProfile— addedvideo_source_config_token: Option<String>field;
code that constructsMediaProfilewith struct literal syntax will need to
include this new field
Dependencies
if-addrs: 0.10 -> 0.15 (major upgrade)rand: 0.10.0 -> 0.10.1 (fixes RUSTSEC-2026-0097)rustls-webpki: 0.103.10 -> 0.103.12 (fixes RUSTSEC-2026-0098)tokio: 1.51.0 -> 1.52.0
Full Changelog: v0.8.6...v0.9.0
v0.8.6
Fixed
- XML injection — all user-supplied string parameters (
consumer_url,
filter,termination_time,timeout,keep_alive_timeout,wait_time)
in the Events and Recording services are now XML-escaped before
interpolation into SOAP request bodies - XML injection in WS-Security — the
usernamefield in the
UsernameTokenheader is now XML-escaped get_osdssent wrong XML element — was sending<OSDToken>but
ONVIF Media WSDL §5.14 specifies<ConfigurationToken>for the GetOSDs
request; devices that ignored unknown elements were silently returning
unfiltered results
Changed
xml_escape()now returnsCow<str>instead ofString, avoiding
allocation when the input contains no XML-special characters (the common
case for tokens, ISO durations, and numeric values)- Removed duplicate
xml_escape_url()insoap::envelope; all code now
uses the unifiedxml_escape()fromtypes parse_soap_body()extracts the<Body>node viaswap_removeinstead
of.cloned(), eliminating a deep clone of the entire SOAP body subtree
on every ONVIF callnotification_listener()now handles connections concurrently via
tokio::spawn+mpscchannel (previously sequential)notification_listener()rejects notification bodies larger than 1 MiB- WS-Discovery
probe_innermutex access usesunwrap_or_elseto recover
from poison instead of panicking - WS-Discovery multicast address uses
const Ipv4Addrinstead of runtime
parse().unwrap()
Dependencies
tokio: addedsyncfeature (required formpscchannel in
notification_listener)
Tests
- 11 new unit tests:
xml_escapeCow behavior (5), XML escape security for
profile token / consumer URL / username (3),get_osdssends correct
ConfigurationTokenelement (2),parse_soap_bodywith header (1)
v0.8.5
What's Changed
Added
discovery::listen()— passive WS-Discovery listener; joins the ONVIF multicast group (239.255.255.250:3702) and collectsHello/Byeannouncements for a configurable durationDiscoveryEventenum (Hello(DiscoveredDevice)/Bye { endpoint }) returned bylisten()OnvifSession::subscribe()+notification_listener()— WS-BaseNotification push subscription; spawns a minimal tokio TCP server so the device can POSTNotifymessages back to the consumerPushSubscriptiontype returned bysubscribe()examples/camera— newdiscovery-listenandpush-subscribesub-commandsexamples/odm_compat— runs all ODM v2.2.250 ONVIF APIs against a real camera and reports PASS / FAIL / SKIP / NOT_IMPL coverage summary- Mock server handlers for Events service (
GetEventProperties,CreatePullPointSubscription,PullMessages,Subscribe,Renew,Unsubscribe)
Fixed
- WS-Discovery multicast NIC selection on Windows — without
IP_MULTICAST_IFthe OS routes the probe through its default multicast interface (often a Hyper-V or WSL virtual adapter) rather than the LAN NIC connected to the cameras.probe_innernow creates onesocket2socket per interface, setsIP_MULTICAST_IFon each, and collects responses in parallel.
Dependencies
- Added
socket2 = "0.5"
Full Changelog: https://github.com/smiti1642/oxvif/blob/master/CHANGELOG.md
v0.8.4
Fixed
- ONVIF spec compliance — 11 parsing bugs corrected against official WSDL/XSD
NetworkInterface: IPv4 address now readsConfig/DHCPfor DHCP flag and
Manual/Address/FromDHCP/Addressper spec (was misreadingFromDHCPas
boolean text → producedip=/0against real devices)Capabilities:max_profilesnow reads from
Extension/ProfileCapabilities/MaximumNumberOfProfilesStorageConfiguration: removed non-specuse_anonymous/storage_status
fields; now readsData type=attribute,LocalPath,StorageUri,
Data/User/UserNameper specSystemUris: removed non-specfirmware_upgrade_uri; addedsystem_backup_uri;
system_log_urinow readsSystemLogUris/SystemLogUri/Uri;
support_info_urireadsSupportInfoUriper specRecordingConfiguration: addedmaximum_retention_timefieldRecordingItem: removed non-specearliest_recording,latest_recording,
recording_statusfields; token now reads child elementRecordingToken;
source/content read fromConfiguration/SourceandConfiguration/ContentRecordingJobState: renamedtoken→recording_token;active_state
now readsState/State(wasState/ActiveState)FocusOptions20:focus_af_modesreadsAutoFocusModes(wasAFModes);
focus_speed_rangereadsDefaultSpeed(wasAutoFocusSpeed)renew_subscription/unsubscribeSOAP actions: corrected to OASIS-WSN
namespace (docs.oasis-open.org/wsn/bw-2/SubscriptionManager/…)set_storage_configuration: removeduse_anonymousparam; XML body now
uses spec-compliant<tt:Data type="…">wrapper
Tests
- Updated all affected fixtures and assertions in
client_tests.rs,
session_tests.rs,types_tests.rsto match spec-compliant XML - Added
test_renew_subscription_uses_oasis_action_uriand
test_unsubscribe_uses_oasis_action_uri
v0.8.3
Added
set_scopes(device_url, scopes)— replace the device's scope listset_system_date_and_time(device_url, req)— set device clock;
takesSetDateTimeRequest(manual or NTP, UTC offset, datetime fields)- Both methods covered by handlers in
examples/mock_server.rs - Both methods demonstrated in
examples/write_workflow.rs
Fixed
- Broken intra-doc links in
events.rs,imaging.rs,types/device.rs,
types/recording.rs,client/mod.rs— resolves red version badge on lib.rs