Files
onvif/event/stream/renew.go
Sebastian Norling fcc3a90f9b docs(event/stream): trim comments to WHY, drop noise
Audit against the standard 'default to no comments; only add one when
the WHY is non-obvious'. Net: 238 lines removed across 8 files, no
behaviour change, tests still pass -race.

What went
---------
* Section banners (// ---------- Motion ----------): noise once
  per-rule citations exist.
* Per-rule 'Data: IsMotion (xsd:boolean)' wire-format lines in
  topics.go: that's WHAT; the spec citation carries WHY.
* Per-field doc on Event struct restating each field name (// Kind
  is the normalized event category) and the type-doc preamble.
* Stringer doc comments ('// String implements fmt.Stringer.') and
  similar conventional-method noise.
* 'Used by ErrPullFailed / ErrRenewFailed / ErrRecreateFailed' in
  the Op doc — the rule-named anti-pattern.
* doc.go Invariants and Reconnect sections duplicating per-function
  docs.
* Internal helper doc-comments restating what the function does
  (surfaceError, run, simpleItemsToMap first sentence, etc.).

What stayed
-----------
* Every spec / vendor-doc citation in topics.go.
* Race-condition WHY in stream.go run() close ordering.
* Workaround WHY in renew.go (absolute datetime vs duration).
* WS-BaseNotification UTC rationale + vendor format list in
  decode.go.
* Fleet-sizing and thundering-herd rationale in reconnect.go.
* Stream consumer invariants (NewStream synchronous I/O, Errors
  non-blocking, Close idempotent + bounded).

The change matches the codebase's stated style (CLAUDE.md): WHY only,
no WHAT, no cross-file references, no current-task narration.
2026-05-21 18:43:08 +02:00

59 lines
1.6 KiB
Go

package stream
import (
"context"
"encoding/xml"
"fmt"
"time"
"github.com/kerberos-io/onvif/event"
"github.com/kerberos-io/onvif/xsd"
)
// renewLoop surfaces renew failures and continues. A permanently
// failing renew lets the subscription die at the camera; the pull
// loop's reconnect path then recreates it — recreate is the only
// reliable recovery once a subscription is GC'd.
func (s *Stream) renewLoop(ctx context.Context) {
interval := s.opts.InitialTermination - s.opts.RenewMargin
if interval <= 0 {
// Pathological config (margin >= termination): renew at
// half termination so we still refresh.
interval = s.opts.InitialTermination / 2
if interval <= 0 {
interval = time.Second
}
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := renewPullPoint(s.caller, s.getPullPoint(), s.opts); err != nil {
s.surfaceError(ErrRenewFailed{Err: err})
}
}
}
}
// renewPullPoint sends Renew with an absolute UTC TerminationTime.
// WS-BaseNotification §6.1.1 also allows xsd:duration but older
// Hikvision, some Dahua and some Bosch firmwares reject the
// relative form.
func renewPullPoint(c caller, endpoint string, opts Options) error {
absoluteEnd := time.Now().UTC().Add(opts.InitialTermination).Format("2006-01-02T15:04:05Z")
req := event.Renew{TerminationTime: xsd.String(absoluteEnd)}
body, err := xml.Marshal(req)
if err != nil {
return fmt.Errorf("marshal Renew: %w", err)
}
resp, err := c.SendSoap(endpoint, string(body))
if err != nil {
return err
}
_, err = readClose(resp)
return err
}