Skip to content

Releases: smiti1642/oxvif

v0.9.9

17 Jun 01:38

Choose a tag to compare

Headline: digital input read APIGetDigitalInputs 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(). Returns Vec<DigitalInput> where each entry carries token and idle_state ("closed" / "open", or empty string when the firmware omits the attribute). Mirrors the existing get_relay_outputs shape; no Set-side method is exposed because the Device service spec doesn't define one.
  • New DigitalInput type re-exported from the crate root. PartialEq/Eq derived on both DigitalInput and RelayOutput.

Fixed

  • Compile failure when quick-xml/encoding is enabled anywhere in the build graph (#1). quick-xml 0.39 cfg-gates Attribute::unescape_value away whenever its encoding feature 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 through Attribute::decode_and_unescape_value(reader.decoder()), which is always available and decodes identically. A quick-xml encoding-on dev-dependency guards against regressions in CI.

Mock server

  • Stateful Relay/Input. MockState now carries relay_outputs and digital_inputs; GetRelayOutputs / GetDigitalInputs render from state, and SetRelayOutputState / SetRelayOutputSettings mutate state and fault on unknown tokens.
  • PullPoint IO event topics. GetEventProperties advertises tns1:Device/Trigger/DigitalInput and tns1:Device/Trigger/Relay; SetRelayOutputState queues a RelayOutput event.
  • /mock/digital-input/:token/pulse and /set?state=... REST hooks (mock-server feature) to simulate physical input signals in tests.

Full Changelog: v0.9.7...v0.9.9

v0.9.7

31 May 15:15

Choose a tag to compare

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 the health feature; pure
    library code over OnvifSession, no extra dependencies).
    • HealthCheck::new(url).with_credentials(..).run().await returns a
      HealthReport of per-check CheckResults (status + category + timing) plus
      a ProfileAssessment (S/T/G verdict). Display renders 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()
    FirmwareUpgradeStart and start_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).
  • PartialEq derived on the video-encoder configuration types
    (VideoEncoderConfiguration, VideoEncoderConfigurationOptions, and related)
    so downstream code can diff configs without hand-written comparisons.

Fixed

  • SetVideoEncoderConfiguration and PTZ SetConfiguration produced 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

26 May 07:16

Choose a tag to compare

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 via MockState::set_on_change).
    • mock feature → MockTransport, an in-process Transport (no sockets, no
      axum) — the fast unit-test path:
      OnvifClient::new("http://mock").with_transport(Arc::new(MockTransport::new())).
    • mock-server feature → 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 support inject_fault(...) for error-path tests.
    • axum / serde are optional deps enabled only by these features — the
      default build is unchanged and axum-free.
    • The examples/mock_server binary is now a thin wrapper over MockServer
      with TOML file persistence (--features mock-server); the mock engine moved
      from examples/ into src/mock/. New tests/mock_workflow.rs drives one
      command from every service against a real MockServer.
  • OnvifSession::subscribe — delegates the WS-BaseNotification push
    subscription that was previously only on OnvifClient.
  • OsdOptions::max_per_text_type. New HashMap<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-validate CreateOSD calls against per-type limits instead of
    parsing opaque ter:InvalidArgs fault strings after the fact.
    Populated only when fetched via OnvifSession::get_osd_options
    OnvifClient::get_osd_options leaves it empty (spec-strict).
  • OnvifSession::get_osd_options now layers vendor-extension
    parsing
    on top of the spec-strict OnvifClient result. Two
    real-world shapes handled:
    • <MaximumNumberOfOSDs Total="8" Plain="7" DateAndTime="1" .../>
      — count from Total attribute 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

17 Apr 10:34

Choose a tag to compare

Changed

  • Dependencies bumped to latest stable — keeps the lib.rs / docs.rs
    badges green. Zero source changes were required:

    • socket2 0.5 → 0.6 — oxvif already used the _v4-suffixed multicast
      methods that 0.6 makes mandatory, so the upgrade is API-compatible.
    • tokio 1.52.0 → 1.52.1 — upstream patch reverting a regression
      that caused spawn_blocking to hang under load.
    • toml 0.8 → 1.1 (dev-dep only, used by the mock_server example
      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

17 Apr 10:22

Choose a tag to compare

Added

  • discovery::probe_unicast(ip, timeout) — send a WS-Discovery Probe
    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 both NetworkVideoTransmitter and Device probes and
    deduplicates the responses by endpoint UUID, matching the behaviour
    of probe / probe_rounds.

Fixed

  • XML entity decoding in SOAP response text (GeoVision snapshot URIs).
    XmlNode::parse now handles Event::GeneralRef (quick-xml 0.39 emits
    each &amp; / &lt; / &#65; as a separate event) and accumulates
    text runs across events rather than overwriting on each Event::Text.
    GeoVision cameras return a GetSnapshotUriResponse with URIs like
    http://host/cgi?skey=X&amp;action=update&amp;Snapshot=Video1.Stream1
    — valid, RFC-compliant XML escaping. The old parser dropped every
    Event::GeneralRef and overwrote text on each Event::Text, so only
    the fragment after the last &amp; 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_probe now emits the legacy WS-Addressing 2004/08 namespace
    with s:mustUnderstand="1" on the Action and To headers 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's UdpDiscoveryEndpoint(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 Bye no longer flaps a live device offline. listen()
    now parses the <wsd:AppSequence> SOAP header (InstanceId /
    MessageNumber / optional SequenceId) and silently drops a Bye
    whose sequence is comparable to (same InstanceId and SequenceId
    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's NvtDiscovery.fs::process_offline behaviour. Hello is never
    filtered — at worst a stale Hello resurfaces a live device, which is
    harmless. The DiscoveryEvent enum is unchanged: sequence handling
    is fully internal.

  • probe_rounds cancellation. Per-NIC listener tasks are now
    spawned via tokio::task::JoinSet instead of tokio::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

15 Apr 08:20

Choose a tag to compare

[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/metadataGetAudioSourceConfigurations,
    GetAudioEncoderConfigurations, SetAudioEncoderConfiguration,
    GetAudioEncoderConfigurationOptions, GetAudioOutputConfigurations,
    GetAudioDecoderConfigurations, GetMetadataConfigurations,
    SetMetadataConfiguration, GetMetadataConfigurationOptions,
    AddConfiguration, RemoveConfiguration
  • Healthcheck example — new healthcheck subcommand for the camera
    example; --ip and --auth CLI 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 — added video_source_config_token: Option<String> field;
    code that constructs MediaProfile with 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

08 Apr 04:06

Choose a tag to compare

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 username field in the
    UsernameToken header is now XML-escaped
  • get_osds sent 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 returns Cow<str> instead of String, 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() in soap::envelope; all code now
    uses the unified xml_escape() from types
  • parse_soap_body() extracts the <Body> node via swap_remove instead
    of .cloned(), eliminating a deep clone of the entire SOAP body subtree
    on every ONVIF call
  • notification_listener() now handles connections concurrently via
    tokio::spawn + mpsc channel (previously sequential)
  • notification_listener() rejects notification bodies larger than 1 MiB
  • WS-Discovery probe_inner mutex access uses unwrap_or_else to recover
    from poison instead of panicking
  • WS-Discovery multicast address uses const Ipv4Addr instead of runtime
    parse().unwrap()

Dependencies

  • tokio: added sync feature (required for mpsc channel in
    notification_listener)

Tests

  • 11 new unit tests: xml_escape Cow behavior (5), XML escape security for
    profile token / consumer URL / username (3), get_osds sends correct
    ConfigurationToken element (2), parse_soap_body with header (1)

v0.8.5

06 Apr 09:39

Choose a tag to compare

What's Changed

Added

  • discovery::listen() — passive WS-Discovery listener; joins the ONVIF multicast group (239.255.255.250:3702) and collects Hello / Bye announcements for a configurable duration
  • DiscoveryEvent enum (Hello(DiscoveredDevice) / Bye { endpoint }) returned by listen()
  • OnvifSession::subscribe() + notification_listener() — WS-BaseNotification push subscription; spawns a minimal tokio TCP server so the device can POST Notify messages back to the consumer
  • PushSubscription type returned by subscribe()
  • examples/camera — new discovery-listen and push-subscribe sub-commands
  • examples/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_IF the 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_inner now creates one socket2 socket per interface, sets IP_MULTICAST_IF on 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

05 Apr 03:45

Choose a tag to compare

Fixed

  • ONVIF spec compliance — 11 parsing bugs corrected against official WSDL/XSD
    • NetworkInterface: IPv4 address now reads Config/DHCP for DHCP flag and
      Manual/Address / FromDHCP/Address per spec (was misreading FromDHCP as
      boolean text → produced ip=/0 against real devices)
    • Capabilities: max_profiles now reads from
      Extension/ProfileCapabilities/MaximumNumberOfProfiles
    • StorageConfiguration: removed non-spec use_anonymous / storage_status
      fields; now reads Data type= attribute, LocalPath, StorageUri,
      Data/User/UserName per spec
    • SystemUris: removed non-spec firmware_upgrade_uri; added system_backup_uri;
      system_log_uri now reads SystemLogUris/SystemLogUri/Uri;
      support_info_uri reads SupportInfoUri per spec
    • RecordingConfiguration: added maximum_retention_time field
    • RecordingItem: removed non-spec earliest_recording, latest_recording,
      recording_status fields; token now reads child element RecordingToken;
      source/content read from Configuration/Source and Configuration/Content
    • RecordingJobState: renamed tokenrecording_token; active_state
      now reads State/State (was State/ActiveState)
    • FocusOptions20: focus_af_modes reads AutoFocusModes (was AFModes);
      focus_speed_range reads DefaultSpeed (was AutoFocusSpeed)
    • renew_subscription / unsubscribe SOAP actions: corrected to OASIS-WSN
      namespace (docs.oasis-open.org/wsn/bw-2/SubscriptionManager/…)
    • set_storage_configuration: removed use_anonymous param; 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.rs to match spec-compliant XML
  • Added test_renew_subscription_uses_oasis_action_uri and
    test_unsubscribe_uses_oasis_action_uri

v0.8.3

05 Apr 03:44

Choose a tag to compare

Added

  • set_scopes(device_url, scopes) — replace the device's scope list
  • set_system_date_and_time(device_url, req) — set device clock;
    takes SetDateTimeRequest (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