mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-11-07 21:23:02 -05:00
* fix: schedule ONVIF subscription renewal for short and unreported lifetimes refs #5179 The Beward camera from #5165 grants a 60 second subscription and its RenewResponse carries no TerminationTime. Three things combined so that ZM renewed after every PullMessages that returned messages (61 renewals in 74 seconds of the reporter's log): - The renewal was scheduled a fixed ONVIF_RENEWAL_ADVANCE_SECONDS (60) before termination, which for a 60 second subscription is its creation time. ONVIFNextRenewalTime() now uses the smaller of that advance and half the remaining lifetime. - Renew() only logged a missing TerminationTime and left next_renewal_time in the past. assume_renewal_times() now schedules from the lifetime we asked for, capped at what the camera last granted (ONVIFAssumedLifetime()), so this camera renews every 30 seconds. - IsRenewalNeeded() was only checked after a response carrying messages, so a camera quiet for longer than its subscription was never renewed. It is now also checked after an empty (SOAP_EOF) poll. Since renewal is now checked after every poll, a camera that answers Renew with ActionNotSupported has renewal disabled rather than being asked again on each poll, matching the existing handling of unusable TerminationTimes. Tests cover the renewal time for long, short and very short subscriptions, and the assumed lifetime with no, smaller and larger previously granted lifetimes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: only disable ONVIF renewal for ActionNotSupported, keep sub-second grants refs #5179 Review follow-ups on the renewal scheduling change: - Renew() treated soap->error == 12 as ActionNotSupported, but 12 is gSOAP's generic SOAP_FAULT. With renewal now disabled on that path, a NotAuthorized or InvalidArgVal fault from Renew would have stopped renewal for good while marking the subscription healthy. ONVIFIsActionNotSupported() checks the fault subcode and string for ActionNotSupported (wsa: or ter:); any other fault now takes the existing cleanup and re-subscribe path. - granted_lifetime truncated the remaining time to whole seconds. A termination less than a second away stored 0, which reads as "never reported", so the next Renew without a TerminationTime assumed the full requested 300 seconds. ONVIFGrantedLifetime() rounds up instead. Tests cover ActionNotSupported subcodes and strings against other faults and non-fault results, and the granted lifetime for whole, fractional and sub-second remainders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: count an assumed ONVIF renewal lifetime from the request, not the response refs #5179 When a RenewResponse has no TerminationTime, assume_renewal_times() started the assumed lifetime when the response arrived, but the camera counts it from when it received the request. A slow response pushed the deadline and the next renewal later by the response time: with subscription_timeout=10 and a 6 second response, the camera's deadline was request+10 but the renewal was scheduled for request+11. In absolute renewal mode it also ignored the exact deadline that was sent. Renew() now records the time just before building the request and the deadline it asks for: request time plus subscription_timeout, or the absolute whole-second time it sends. ONVIFAssumedTermination() replaces ONVIFAssumedLifetime() and returns that deadline, capped at request time plus the lifetime the camera last granted. The renewal is scheduled from the request time; if the response arrived after it, it is already due and the next poll renews. Tests cover no, smaller and larger previous grants, an absolute deadline kept exactly, and the slow-response case from review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
471 lines
18 KiB
C++
471 lines
18 KiB
C++
/*
|
|
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "zm_catch2.h"
|
|
#include "zm_monitor_onvif.h"
|
|
#include "zm_time.h"
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
// Test the ONVIF subscription renewal timing logic
|
|
TEST_CASE("ONVIF Subscription Renewal Timing") {
|
|
SECTION("Calculate renewal time from termination time") {
|
|
// Simulate a termination time 60 seconds from now
|
|
auto now = std::chrono::system_clock::now();
|
|
time_t termination_time_t = std::chrono::system_clock::to_time_t(
|
|
now + std::chrono::seconds(60));
|
|
|
|
// Convert to SystemTimePoint
|
|
SystemTimePoint termination_time = std::chrono::system_clock::from_time_t(termination_time_t);
|
|
|
|
// Calculate renewal time (10 seconds before termination)
|
|
SystemTimePoint renewal_time = termination_time - std::chrono::seconds(10);
|
|
|
|
// Check that renewal time is 50 seconds from now (60 - 10)
|
|
auto seconds_until_renewal = std::chrono::duration_cast<std::chrono::seconds>(
|
|
renewal_time - now).count();
|
|
|
|
// Allow 1 second tolerance for test execution time
|
|
REQUIRE(seconds_until_renewal >= 49);
|
|
REQUIRE(seconds_until_renewal <= 51);
|
|
}
|
|
|
|
SECTION("Check if renewal is needed - not yet time") {
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
// Renewal time is 30 seconds in the future
|
|
SystemTimePoint renewal_time = now + std::chrono::seconds(30);
|
|
|
|
// Should not need renewal yet
|
|
bool renewal_needed = (now >= renewal_time);
|
|
REQUIRE_FALSE(renewal_needed);
|
|
}
|
|
|
|
SECTION("Check if renewal is needed - time has come") {
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
// Renewal time was 1 second ago
|
|
SystemTimePoint renewal_time = now - std::chrono::seconds(1);
|
|
|
|
// Should need renewal
|
|
bool renewal_needed = (now >= renewal_time);
|
|
REQUIRE(renewal_needed);
|
|
}
|
|
|
|
SECTION("Check if renewal times are uninitialized") {
|
|
// Default constructed SystemTimePoint has epoch (0)
|
|
SystemTimePoint uninitialized_time;
|
|
|
|
bool is_uninitialized = (uninitialized_time.time_since_epoch().count() == 0);
|
|
REQUIRE(is_uninitialized);
|
|
}
|
|
|
|
SECTION("Time conversion round-trip") {
|
|
// Test that time_t -> SystemTimePoint -> time_t conversion is accurate
|
|
time_t original_time = 1704844800; // 2024-01-10 00:00:00 UTC
|
|
|
|
SystemTimePoint tp = std::chrono::system_clock::from_time_t(original_time);
|
|
time_t converted_time = std::chrono::system_clock::to_time_t(tp);
|
|
|
|
REQUIRE(original_time == converted_time);
|
|
}
|
|
}
|
|
|
|
// Test the ONVIF subscription cleanup logic
|
|
// Note: These tests document the expected behavior. Full integration testing
|
|
// with actual ONVIF cameras would require a mock SOAP server.
|
|
TEST_CASE("ONVIF Subscription Cleanup Logic") {
|
|
SECTION("Cleanup should prevent subscription leaks on renewal failure") {
|
|
// When Renew() fails (non-ActionNotSupported error), cleanup_subscription()
|
|
// should be called to unsubscribe from the camera before returning false.
|
|
// This prevents orphaned subscriptions from accumulating on the camera.
|
|
//
|
|
// Expected behavior verified in zm_monitor_onvif.cpp:
|
|
// 1. Renew() calls proxyEvent.Renew()
|
|
// 2. If result != SOAP_OK and error != 12 (ActionNotSupported):
|
|
// a. Log the renewal failure
|
|
// b. Call cleanup_subscription() to unsubscribe
|
|
// c. Set healthy = false
|
|
// d. Return false
|
|
REQUIRE(true); // Behavior verified through code inspection
|
|
}
|
|
|
|
SECTION("Cleanup should be called before creating new subscription in start()") {
|
|
// When start() is called and soap != nullptr (from previous failed attempt),
|
|
// cleanup_subscription() should be called before creating a new subscription.
|
|
// This ensures any stale subscription is cleaned up first.
|
|
//
|
|
// Expected behavior verified in zm_monitor_onvif.cpp:
|
|
// 1. start() checks if soap != nullptr at beginning
|
|
// 2. If true:
|
|
// a. Log that existing soap context was found
|
|
// b. Call cleanup_subscription() to unsubscribe from stale subscription
|
|
// c. Clean up the old soap context (disable logging, destroy, end, free)
|
|
// d. Set soap = nullptr
|
|
// 3. Then proceed with normal subscription creation
|
|
REQUIRE(true); // Behavior verified through code inspection
|
|
}
|
|
|
|
SECTION("Destructor should log unsubscribe failures") {
|
|
// The destructor should check the result of Unsubscribe() and log warnings
|
|
// if it fails, helping identify cameras that don't properly handle cleanup.
|
|
//
|
|
// Expected behavior verified in zm_monitor_onvif.cpp:
|
|
// 1. Destructor attempts to unsubscribe
|
|
// 2. Captures result from proxyEvent.Unsubscribe()
|
|
// 3. If result != SOAP_OK:
|
|
// a. Log a Warning with error details
|
|
// b. Indicate that subscription may remain on camera
|
|
// 4. If result == SOAP_OK:
|
|
// a. Log Debug message confirming successful unsubscribe
|
|
REQUIRE(true); // Behavior verified through code inspection
|
|
}
|
|
|
|
SECTION("WS-Addressing failure in Renew should trigger cleanup") {
|
|
// If do_wsa_request() fails during Renew(), cleanup_subscription() should
|
|
// be called before returning false to prevent subscription leaks.
|
|
//
|
|
// Expected behavior verified in zm_monitor_onvif.cpp:
|
|
// 1. Renew() calls do_wsa_request() if WS-Addressing is enabled
|
|
// 2. If do_wsa_request() returns false:
|
|
// a. Log that WS-Addressing setup failed
|
|
// b. Call cleanup_subscription()
|
|
// c. Set healthy = false
|
|
// d. Return false
|
|
REQUIRE(true); // Behavior verified through code inspection
|
|
}
|
|
}
|
|
|
|
// Test the ISO 8601 absolute time formatting for ONVIF renewal requests
|
|
TEST_CASE("ONVIF Absolute Time Formatting") {
|
|
SECTION("Format known timestamp as ISO 8601") {
|
|
// Test with known timestamp: 2024-01-13 13:14:56 UTC
|
|
time_t test_time = 1705151696; // 2024-01-13 13:14:56 UTC
|
|
std::string result = format_absolute_time_iso8601(test_time);
|
|
|
|
// Should be formatted as ISO 8601 with .000Z suffix
|
|
REQUIRE(result == "2024-01-13T13:14:56.000Z");
|
|
}
|
|
|
|
SECTION("Format current time as ISO 8601") {
|
|
time_t now = time(nullptr);
|
|
std::string result = format_absolute_time_iso8601(now);
|
|
|
|
// Should not be empty
|
|
REQUIRE_FALSE(result.empty());
|
|
|
|
// Should have expected format with 'T' separator and 'Z' suffix
|
|
REQUIRE(result.find('T') != std::string::npos);
|
|
REQUIRE(result.find('Z') != std::string::npos);
|
|
REQUIRE(result.back() == 'Z');
|
|
|
|
// Should have the correct length (YYYY-MM-DDTHH:MM:SS.000Z = 24 characters)
|
|
REQUIRE(result.length() == 24);
|
|
}
|
|
|
|
SECTION("Format future time for renewal") {
|
|
// Simulate renewal: current time + 60 seconds
|
|
time_t now = time(nullptr);
|
|
time_t renewal_time = now + 60;
|
|
std::string result = format_absolute_time_iso8601(renewal_time);
|
|
|
|
// Should not be empty
|
|
REQUIRE_FALSE(result.empty());
|
|
|
|
// Should have expected format
|
|
REQUIRE(result.find('T') != std::string::npos);
|
|
REQUIRE(result.find('Z') != std::string::npos);
|
|
REQUIRE(result.length() == 24);
|
|
}
|
|
|
|
SECTION("Verify ISO 8601 format components") {
|
|
time_t test_time = 1705151696; // 2024-01-13 13:14:56 UTC
|
|
std::string result = format_absolute_time_iso8601(test_time);
|
|
|
|
// Check year
|
|
REQUIRE(result.substr(0, 4) == "2024");
|
|
|
|
// Check separators
|
|
REQUIRE(result[4] == '-'); // After year
|
|
REQUIRE(result[7] == '-'); // After month
|
|
REQUIRE(result[10] == 'T'); // Date/time separator
|
|
REQUIRE(result[13] == ':'); // After hour
|
|
REQUIRE(result[16] == ':'); // After minute
|
|
REQUIRE(result[19] == '.'); // After second
|
|
REQUIRE(result[23] == 'Z'); // UTC indicator
|
|
}
|
|
}
|
|
|
|
// Standalone AlarmEntry struct matching the one in zm_monitor_onvif.h.
|
|
// We replicate it here so tests don't depend on gSOAP headers.
|
|
namespace onvif_test {
|
|
struct AlarmEntry {
|
|
std::string value;
|
|
SystemTimePoint termination_time;
|
|
};
|
|
|
|
using AlarmMap = std::unordered_map<std::string, AlarmEntry>;
|
|
|
|
// Mirror of ONVIF::expire_stale_alarms logic for unit testing.
|
|
// Returns true if the map became empty (caller should setAlarmed(false)).
|
|
bool expire_stale_alarms(AlarmMap &alarms, const SystemTimePoint &now) {
|
|
auto it = alarms.begin();
|
|
while (it != alarms.end()) {
|
|
// Skip entries with no termination time set (epoch = uninitialized)
|
|
if (it->second.termination_time.time_since_epoch().count() == 0) {
|
|
++it;
|
|
continue;
|
|
}
|
|
if (it->second.termination_time <= now) {
|
|
it = alarms.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
return alarms.empty();
|
|
}
|
|
} // namespace onvif_test
|
|
|
|
// Test per-topic TerminationTime alarm expiry logic
|
|
TEST_CASE("ONVIF Per-Topic Alarm Expiry") {
|
|
using namespace onvif_test;
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
SECTION("Expired alarms are removed by sweep") {
|
|
AlarmMap alarms;
|
|
// Alarm with TerminationTime 10 seconds in the past
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", now - std::chrono::seconds(10)};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.empty());
|
|
REQUIRE(empty);
|
|
}
|
|
|
|
SECTION("Future alarms are retained by sweep") {
|
|
AlarmMap alarms;
|
|
// Alarm with TerminationTime 60 seconds in the future
|
|
alarms["MotionAlarm"] = AlarmEntry{"true", now + std::chrono::seconds(60)};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.size() == 1);
|
|
REQUIRE_FALSE(empty);
|
|
}
|
|
|
|
SECTION("Mixed expired and future alarms") {
|
|
AlarmMap alarms;
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", now - std::chrono::seconds(10)};
|
|
alarms["MotionAlarm"] = AlarmEntry{"true", now + std::chrono::seconds(60)};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.size() == 1);
|
|
REQUIRE(alarms.count("MotionAlarm") == 1);
|
|
REQUIRE(alarms.count("PeopleDetect") == 0);
|
|
REQUIRE_FALSE(empty);
|
|
}
|
|
|
|
SECTION("Re-triggering an alarm updates its TerminationTime") {
|
|
AlarmMap alarms;
|
|
// Initial alarm with TerminationTime 5 seconds from now
|
|
SystemTimePoint initial_term = now + std::chrono::seconds(5);
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", initial_term};
|
|
|
|
// Simulate re-trigger with new TerminationTime 65 seconds from now
|
|
SystemTimePoint new_term = now + std::chrono::seconds(65);
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", new_term};
|
|
|
|
// Sweep at now+10s - alarm should NOT be expired because it was refreshed
|
|
SystemTimePoint sweep_time = now + std::chrono::seconds(10);
|
|
bool empty = expire_stale_alarms(alarms, sweep_time);
|
|
REQUIRE(alarms.size() == 1);
|
|
REQUIRE_FALSE(empty);
|
|
|
|
// Verify the termination time was updated
|
|
REQUIRE(alarms["PeopleDetect"].termination_time == new_term);
|
|
}
|
|
|
|
SECTION("Alarms with epoch termination time (uninitialized) are not expired") {
|
|
AlarmMap alarms;
|
|
// Alarm with default-constructed (epoch) termination time
|
|
alarms["SomeAlarm"] = AlarmEntry{"true", SystemTimePoint{}};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.size() == 1);
|
|
REQUIRE_FALSE(empty);
|
|
}
|
|
|
|
SECTION("TerminationTime exactly equal to now is expired") {
|
|
AlarmMap alarms;
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", now};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.empty());
|
|
REQUIRE(empty);
|
|
}
|
|
|
|
SECTION("Multiple expired alarms are all removed") {
|
|
AlarmMap alarms;
|
|
alarms["PeopleDetect"] = AlarmEntry{"true", now - std::chrono::seconds(30)};
|
|
alarms["VehicleDetect"] = AlarmEntry{"true", now - std::chrono::seconds(20)};
|
|
alarms["DogCatDetect"] = AlarmEntry{"true", now - std::chrono::seconds(10)};
|
|
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(alarms.empty());
|
|
REQUIRE(empty);
|
|
}
|
|
|
|
SECTION("Empty alarms map is handled gracefully") {
|
|
AlarmMap alarms;
|
|
bool empty = expire_stale_alarms(alarms, now);
|
|
REQUIRE(empty);
|
|
}
|
|
|
|
SECTION("AlarmEntry stores value correctly") {
|
|
AlarmEntry entry{"true", now + std::chrono::seconds(60)};
|
|
REQUIRE(entry.value == "true");
|
|
|
|
AlarmEntry entry2{"false", now};
|
|
REQUIRE(entry2.value == "false");
|
|
}
|
|
|
|
SECTION("Alarm value accessible via map for SetNoteSet") {
|
|
AlarmMap alarms;
|
|
alarms["MyRuleDetector/PeopleDetect"] = AlarmEntry{"true", now + std::chrono::seconds(60)};
|
|
|
|
// Simulate SetNoteSet logic: iterate and access .value
|
|
for (auto it = alarms.begin(); it != alarms.end(); ++it) {
|
|
std::string note = it->first + "/" + it->second.value;
|
|
REQUIRE(note == "MyRuleDetector/PeopleDetect/true");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_GSOAP
|
|
|
|
TEST_CASE("ONVIFNextRenewalTime", "[onvif]") {
|
|
const SystemTimePoint now = std::chrono::system_clock::from_time_t(1790880716);
|
|
|
|
SECTION("Long subscription renews 60 seconds before it ends") {
|
|
SystemTimePoint termination = now + std::chrono::seconds(300);
|
|
REQUIRE(ONVIFNextRenewalTime(now, termination) == now + std::chrono::seconds(240));
|
|
}
|
|
|
|
SECTION("Subscription no longer than the advance renews halfway through") {
|
|
// The #5179 Beward grants 60 seconds. A fixed 60 second advance put the
|
|
// renewal at the creation time, so it was due immediately.
|
|
SystemTimePoint termination = now + std::chrono::seconds(60);
|
|
REQUIRE(ONVIFNextRenewalTime(now, termination) == now + std::chrono::seconds(30));
|
|
|
|
termination = now + std::chrono::seconds(10);
|
|
REQUIRE(ONVIFNextRenewalTime(now, termination) == now + std::chrono::seconds(5));
|
|
}
|
|
|
|
SECTION("Renewal is always strictly in the future for a future termination") {
|
|
SystemTimePoint termination = now + std::chrono::seconds(1);
|
|
REQUIRE(ONVIFNextRenewalTime(now, termination) > now);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ONVIFAssumedTermination", "[onvif]") {
|
|
using std::chrono::seconds;
|
|
// The moment Renew() sent the request
|
|
const SystemTimePoint request_time = std::chrono::system_clock::from_time_t(1790880716);
|
|
const SystemTimePoint requested = request_time + seconds(300);
|
|
|
|
SECTION("Camera never reported a lifetime: assume the deadline we asked for") {
|
|
REQUIRE(ONVIFAssumedTermination(request_time, requested, seconds(0)) == requested);
|
|
}
|
|
|
|
SECTION("Camera granted less than we asked for before: assume it does so again") {
|
|
// The #5179 Beward granted 60 seconds on Subscribe and sends no
|
|
// TerminationTime on Renew.
|
|
REQUIRE(ONVIFAssumedTermination(request_time, requested, seconds(60)) == request_time + seconds(60));
|
|
}
|
|
|
|
SECTION("Camera granted more than we asked for: assume only what we asked for") {
|
|
REQUIRE(ONVIFAssumedTermination(request_time, requested, seconds(3600)) == requested);
|
|
}
|
|
|
|
SECTION("An absolute deadline we sent is kept exactly") {
|
|
// Absolute renewal requests a whole-second time, which can be slightly
|
|
// less than request_time + subscription_timeout.
|
|
const SystemTimePoint absolute = std::chrono::system_clock::from_time_t(1790880716 + 10);
|
|
REQUIRE(ONVIFAssumedTermination(request_time + std::chrono::milliseconds(700), absolute, seconds(0)) == absolute);
|
|
}
|
|
|
|
SECTION("A slow response does not push the deadline or renewal later") {
|
|
// subscription_timeout=10 and the response arrives 6 seconds after the
|
|
// request. Counting from the response put the renewal at request+11,
|
|
// after the camera's deadline at request+10.
|
|
const SystemTimePoint deadline = request_time + seconds(10);
|
|
const SystemTimePoint response_time = request_time + seconds(6);
|
|
SystemTimePoint termination = ONVIFAssumedTermination(request_time, deadline, seconds(0));
|
|
REQUIRE(termination == deadline);
|
|
SystemTimePoint renewal = ONVIFNextRenewalTime(request_time, termination);
|
|
REQUIRE(renewal < termination);
|
|
// Already due when the response arrives, so the next poll renews
|
|
REQUIRE(renewal <= response_time);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ONVIFGrantedLifetime", "[onvif]") {
|
|
// Termination times have whole-second precision; now does not.
|
|
const SystemTimePoint termination = std::chrono::system_clock::from_time_t(1790880776);
|
|
|
|
SECTION("Whole seconds are kept") {
|
|
REQUIRE(ONVIFGrantedLifetime(termination - std::chrono::seconds(60), termination) == std::chrono::seconds(60));
|
|
}
|
|
|
|
SECTION("A fraction of a second rounds up, not down") {
|
|
REQUIRE(ONVIFGrantedLifetime(termination - std::chrono::milliseconds(59200), termination) == std::chrono::seconds(60));
|
|
}
|
|
|
|
SECTION("Less than a second left is still a known, positive lifetime") {
|
|
// Truncating to zero would read as "never reported" and make the next
|
|
// TerminationTime-less Renew assume the full requested lifetime.
|
|
REQUIRE(ONVIFGrantedLifetime(termination - std::chrono::milliseconds(300), termination) == std::chrono::seconds(1));
|
|
const SystemTimePoint request_time = termination - std::chrono::milliseconds(300);
|
|
REQUIRE(ONVIFAssumedTermination(request_time, request_time + std::chrono::seconds(300),
|
|
ONVIFGrantedLifetime(request_time, termination))
|
|
== request_time + std::chrono::seconds(1));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ONVIFIsActionNotSupported", "[onvif]") {
|
|
SECTION("WS-Addressing and ONVIF ActionNotSupported faults") {
|
|
REQUIRE(ONVIFIsActionNotSupported(SOAP_FAULT, "wsa:ActionNotSupported", nullptr));
|
|
REQUIRE(ONVIFIsActionNotSupported(SOAP_FAULT, "wsa5:ActionNotSupported", nullptr));
|
|
REQUIRE(ONVIFIsActionNotSupported(SOAP_FAULT, "ter:ActionNotSupported", "Optional Action Not Implemented"));
|
|
REQUIRE(ONVIFIsActionNotSupported(SOAP_FAULT, nullptr, "ActionNotSupported"));
|
|
}
|
|
|
|
SECTION("Other SOAP faults are not ActionNotSupported") {
|
|
// SOAP_FAULT (12) covers every fault. Treating them all as unsupported
|
|
// disabled renewal for good after e.g. an authorization failure.
|
|
REQUIRE_FALSE(ONVIFIsActionNotSupported(SOAP_FAULT, "ter:NotAuthorized", "Sender not authorized"));
|
|
REQUIRE_FALSE(ONVIFIsActionNotSupported(SOAP_FAULT, "ter:InvalidArgVal", nullptr));
|
|
REQUIRE_FALSE(ONVIFIsActionNotSupported(SOAP_FAULT, nullptr, nullptr));
|
|
}
|
|
|
|
SECTION("Non-fault results are never ActionNotSupported") {
|
|
REQUIRE_FALSE(ONVIFIsActionNotSupported(SOAP_EOF, "wsa:ActionNotSupported", nullptr));
|
|
REQUIRE_FALSE(ONVIFIsActionNotSupported(401, nullptr, "ActionNotSupported"));
|
|
}
|
|
}
|
|
|
|
#endif // WITH_GSOAP
|