6 Commits

Author SHA1 Message Date
Sebastian Norling
16312b236d docs: fix WS-Addressing spec citations
The reference-parameter-to-SOAP-header mapping rule (with the
wsa:IsReferenceParameter='true' attribute) lives in WS-Addressing 1.0
SOAP Binding §3.4 (Binding Message Addressing Properties), not Core
§3.1 (Abstract Property Definitions). The earlier citations were
wrong on both the section number and the document. Corrected across
doc comments, test descriptions, and the PR description.

The "ReferenceParameters can appear in any endpoint reference" claim
in the anchored-extraction test now cites Core §2.1 (Information
Model for Endpoint References), which is where the [reference
parameters] property is defined on the abstract EPR.

Verified against the W3C Recommendations:
 - https://www.w3.org/TR/ws-addr-core/   §2.1
 - https://www.w3.org/TR/ws-addr-soap/   §3.4

Also adds PR_event_stream_axis_compat.md — the branch's PR
description, framed independently of the prior event/stream PR.
2026-05-27 18:11:28 +02:00
Sebastian Norling
d7cfee56a1 fix(event/stream): address round-2 review findings
Critical
  - Renew busy-loop on persistent failure: after a failed
    renewPullPoint, the loop re-read the (now in-past)
    GrantedTermination and nextRenewInterval floored to 1s, hammering
    the camera at 1 Hz until reconnect. nextRenewIntervalAfterError
    decouples the failure path from the stale grant and backs off
    at opts.RetryBackoff. renewLoop also routes through s.now() so
    test clocks can drive it deterministically.
  - Lost-update race on GrantedTermination: a renew result for an
    old subscription could overwrite the grant on a new one if
    attemptRecreate swapped pullPoint mid-flight. A generation
    counter on Stream tracks subscription rotation; the renew loop
    captures the generation before the SOAP call and discards the
    result if the subscription was rotated.
  - Credential leak when <Security> straddled the 64 KiB error cap:
    the non-greedy regex required a closing tag and missed the
    truncated case. wsseSecurityRE now matches close-tag-or-EOF.
    Belt-and-braces wssePasswordRE redacts <Password> elements
    outside any Security wrapper.
  - addHeaderChildren accepted well-formed-but-element-free input
    and produced a header-less request. Now errors out.

Important
  - Wrapper detection in buildRefParamsHeader was HasSuffix-based and
    misfired on children named *ReferenceParameters. Replaced with the
    unambiguous wrapper-only contract: input must be the full
    <*:ReferenceParameters> element returned by extractReferenceParameters.
  - Renamed SoapOption → SendSoapOption and WithHeader → WithSOAPHeader
    to disambiguate at call sites.
  - Renamed WithHeader's `xml` parameter to headerContent to avoid
    shadowing the encoding/xml package name.
  - Documented terminationTimeRE's "first match only" semantics so
    nobody re-uses it from PullMessages context where multiple
    TerminationTime elements appear.
  - goleak is now a direct require (go mod tidy).

Performance
  - Added gosoap.AddStringHeaderContents (plural) for multi-root
    header content. AddStringHeaderContent remains as-is for
    backwards compatibility with external consumers. Device.go's
    addHeaderChildren workaround is gone — one etree parse per
    SendSoapWithOptions call instead of two.

Migration
  - Device.go's own CallOnvifFunction and the three examples now
    call SendSoapWithOptions, modelling the canonical path.

Docs / tests
  - Trust-boundary warning on SendSoapWithHeader/Options godoc.
  - Comments on createPullPointResp/Alt explain why TerminationTime
    is intentionally omitted (renew timing fixtures).
  - New tests: digest retry strips WS-Security, duplicate
    WithSOAPHeader is last-wins, malformed TerminationTime yields
    zero, margin==base falls to base/2, empty SubscriptionReference
    yields empty ref params, Security straddling cap is redacted,
    bare Password redacted, wrapper-only contract is enforced.
2026-05-27 18:07:56 +02:00
Sebastian Norling
c7ef445d6a refactor(onvif): add SendSoapWithOptions as the variadic workhorse
Future per-call knobs (timeout, context, custom namespaces) will arrive
sooner than later. Adding them as SendSoapWithHeader2 / 3 / N would
turn the Device API into a combinatorial mess; adding them as new
required positional args breaks every existing consumer.

SendSoapWithOptions accepts variadic SoapOption values. SendSoap and
SendSoapWithHeader become one-line delegates so all current callers
keep their signatures, and the public surface is purely additive.

Only WithHeader ships today — wiring the AXIS ReferenceParameters
path through the same plumbing. New options land as WithX constructors
in this file rather than as new Device methods.
2026-05-27 18:07:56 +02:00
Sebastian Norling
d726ed8edb fix(event/stream): address review findings on AXIS compat work
Critical
  - Device.SendSoapWithHeader now parses the supplied header content
    with etree and adds each top-level child as its own SOAP Header
    block. gosoap.AddStringHeaderContent only accepts a single root
    element; previously a multi-child ref-params header silently
    produced a header-less request because the parse error was
    discarded. Errors are now propagated.
  - enrichSOAPErr scrubs <*:Security> blocks from response bodies
    before fault extraction or excerpt slicing so a camera that
    echoes the WS-Security header in a fault response cannot leak
    Username/Password into operator logs.

Important
  - extractSOAPFault falls back to the SOAP 1.2 Subcode (e.g.
    ter:InvalidArgs) when Reason/Text is empty — consistent with
    enrichSOAPErr and surfaces actionable detail on 200-OK fault
    bodies reached via unmarshalNode.
  - subscriptionRef captures the camera-granted TerminationTime
    from CreatePullPointSubscription and Renew responses. renewLoop
    schedules from it via the new nextRenewInterval helper so we
    never miss a renew when the camera grants less than requested.
    renew is now a sleep-loop driven by the latest granted time.
  - enrichSOAPErr reads at most 64 KiB from the body (vs. 10 MiB
    on success paths). Fault bodies are always small; the prior cap
    let a wedged camera churn 10 MiB/s through the retry loop.

Suggestions
  - extractReferenceParameters anchors to <SubscriptionReference> so
    a wsa:ReplyTo / wsa:FaultTo that also carries ReferenceParameters
    elsewhere in the envelope cannot leak through and break PullMessages.
  - buildRefParamsHeader accepts either raw children or the full
    <*:ReferenceParameters> wrapper, and propagates ancestor xmlns:*
    onto each child so a vendor that declares the prefix on the
    parent (not the child itself, as AXIS does) still produces valid
    standalone children on the wire.
  - SendSoapWithHeader documents that xmlHeaderContent must be
    well-formed XML and that the caller is responsible for escaping
    any externally sourced data.
  - Error-message ordering is now context-first
    ("SOAP fault: X: <wrapped err>") per Go convention.
  - Dead headerEnd slicing removed from the SendSoapWithHeader test.

Tests
  - End-to-end multi-child wiring through pullMessages.
  - Digest auth retry preserves the injected header.
  - Malformed-XML header content fast-fails before any request.
  - buildRefParamsHeader malformed / whitespace-only edge cases.
  - goleak.VerifyTestMain in event/stream catches any pull/renew
    goroutine that outlives its Stream.

No new behavioural surface added to onvif core; SendSoap retains
its signature, SendSoapWithHeader is the only new public method.
2026-05-27 18:07:56 +02:00
Sebastian Norling
de3f049a63 feat(event/stream): echo WS-Addressing ReferenceParameters for AXIS
AXIS encodes pull-point subscription identity in
<wsa:ReferenceParameters> inside the CreatePullPointSubscription
response — a generic /onvif/services endpoint plus a
<dom0:SubscriptionId> child — rather than a per-subscription URL.
We were discarding the ReferenceParameters and POSTing to the
generic endpoint, which AXIS rejected with ter:InvalidArgs on every
PullMessages/Renew/Unsubscribe.

Per WS-Addressing 1.0 §3.1 each reference parameter MUST be echoed
as a SOAP Header block carrying wsa:IsReferenceParameter="true".

  - subscriptionRef now carries Address + the verbatim
    ReferenceParameters inner XML extracted from the create response.
  - buildRefParamsHeader walks the children, adds the attribute, and
    produces the SOAP Header content.
  - pullMessages, renewPullPoint, unsubscribePullPoint switch from
    SendSoap to a new SendSoapWithHeader path on the caller interface.
  - onvif.Device gains SendSoapWithHeader as a thin variant of
    SendSoap (existing SendSoap is now a one-liner delegating to it
    with empty header content, so all external callers are unaffected).

Verified end-to-end against an AXIS camera at 192.168.1.10: pulls
now stream the full topic tree (VMD, Object Analytics, IO, storage,
hardware-failure topics) instead of looping on ter:InvalidArgs.
2026-05-27 18:07:56 +02:00
Cedric Verstraeten
8902e4e789 upgrade onvif library 2023-12-18 20:16:21 +01:00