mirror of
https://github.com/kerberos-io/agent.git
synced 2026-08-23 15:08:32 +00:00
fix(onvif): ignore subscription state replays as recording triggers
A camera replays the current state of every property topic as PropertyOperation=Initialized whenever a pull-point subscription is created. dispatchEvent looked only at Kind and State, so any motion property that happened to be active at that moment counted as a fresh trigger — meaning every reconnect restarts a recording, and a flapping subscription manufactures motion with no motion. Observed on a camera whose pull-point was being recreated every ~18s: each recreate replayed ~90 property events, and once real motion made the VMD property active the replays kept re-triggering it. Rejects Initialized specifically rather than accepting only Changed. PropertyOperation is optional per WS-Notification and absent on many non-property events, which decode reports as PropertyUnknown; those are real events and must still trigger.
This commit is contained in:
@@ -123,6 +123,13 @@ func dispatchEvent(ctx context.Context, ev stream.Event, configuration *models.C
|
||||
if ev.State != stream.StateActive {
|
||||
return
|
||||
}
|
||||
// A camera replays every property topic's current state as
|
||||
// Initialized on each new subscription, so treating that as a
|
||||
// trigger lets a flapping pull-point manufacture motion.
|
||||
if ev.Operation == stream.PropertyInitialized {
|
||||
log.Log.Debug("onvif.dispatchEvent(): subscription state replay, not a trigger: topic=" + ev.Topic)
|
||||
return
|
||||
}
|
||||
if configuration.Config.Capture.Recording == "false" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -123,6 +123,54 @@ func TestDispatchEvent_LogsTheTriggeringTopic(t *testing.T) {
|
||||
"the dispatched event's Kind must appear in the log")
|
||||
}
|
||||
|
||||
// TestDispatchEvent_PropertyOperation — a camera replays the current
|
||||
// state of every property topic as Initialized whenever a pull-point
|
||||
// subscription is created. If that counts as a trigger, every
|
||||
// reconnect restarts a recording for any motion property that happens
|
||||
// to be active, and a flapping subscription manufactures motion out of
|
||||
// nothing. Only reject Initialized specifically: PropertyOperation is
|
||||
// optional per WS-Notification and absent on many non-property events,
|
||||
// which decode reports as PropertyUnknown.
|
||||
func TestDispatchEvent_PropertyOperation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
op stream.PropertyOperation
|
||||
wantSend bool
|
||||
}{
|
||||
{"changed is a real transition", stream.PropertyChanged, true},
|
||||
{"absent attribute still counts", stream.PropertyUnknown, true},
|
||||
{"initialized is a subscription state replay", stream.PropertyInitialized, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := makeConfig("true", "true", "cam-1")
|
||||
comm := makeCommunication(1)
|
||||
ev := stream.Event{
|
||||
Kind: stream.KindMotion,
|
||||
State: stream.StateActive,
|
||||
Operation: tt.op,
|
||||
Topic: "tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1",
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
dispatchEvent(ctx, ev, cfg, comm)
|
||||
|
||||
select {
|
||||
case <-comm.HandleMotion:
|
||||
if !tt.wantSend {
|
||||
t.Fatalf("%v must not trigger a recording", tt.op)
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
if tt.wantSend {
|
||||
t.Fatalf("%v must trigger a recording", tt.op)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDispatchEvent_RecordingDisabled_DoesNotSend(t *testing.T) {
|
||||
cfg := makeConfig("false", "true", "cam-1")
|
||||
comm := makeCommunication(1)
|
||||
|
||||
Reference in New Issue
Block a user