Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
PeriodicNotifier.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
10#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
12
15
16#include "PeriodicNotifier.h"
17
18using namespace Stroika::Foundation;
21using namespace Stroika::Foundation::IO;
23
24using namespace Stroika::Frameworks;
25using namespace Stroika::Frameworks::UPnP;
26using namespace Stroika::Frameworks::UPnP::SSDP;
27using namespace Stroika::Frameworks::UPnP::SSDP::Server;
28
29// Comment this in to turn on tracing in this module
30//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
31
32namespace {
33 // each set of NOTIFYs - every advertisement, out of every interface - goes out this many times, this far apart, as UDP loses
34 // packets: the UPnP Device Architecture (1.1, section 1.2.2) says more than once - "e.g. a few hundred milliseconds" apart - and
35 // not more than three times. 100 ms (libupnp's default too) keeps short the pause this adds to starting and stopping a device
36 constexpr unsigned int kSetsSent_{2};
37 constexpr Time::DurationSeconds kBetweenSets_{100ms};
38
39 // the first set waits a random time up to this - as does the first after a network appears - so devices starting together (after a
40 // power cut, say) do not all announce at once: 1.1, section 1.2.2, "e.g. between 0 and 100 milliseconds"
41 constexpr Time::DurationSeconds kBeforeFirstSetAtMost_{100ms};
42}
43
44/*
45 ********************************************************************************
46 ******************************** PeriodicNotifier ******************************
47 ********************************************************************************
48 */
49PeriodicNotifier::PeriodicNotifier (const Iterable<Advertisement>& advertisements, const LocationProvider& location, const Options& options)
50 : fNotifyingOn_{Memory::MakeSharedPtr<Execution::Synchronized<InterfacesByID>> ()}
51{
52 Require (options.fFrequencyInfo.fRepeatInterval < options.fFrequencyInfo.fMaxAge); // else listeners forget the device between NOTIFYs
53 InterfaceFilter interfaceFilter = options.fInterfaces;
54 Time::DurationSeconds maxAge = options.fFrequencyInfo.fMaxAge;
55
57 advertisements.Apply ([] ([[maybe_unused]] const auto& a) { Require (not a.fTarget.empty ()); });
58 }
59
60 // Construction of notifier will fail if we cannot make the sockets - instead of failing quietly inside the loop. They are NOT
61 // bound: they only send, and bound to SSDP's port they could take unicast M-SEARCHes meant for a SearchResponder's sockets
62 // there - of sockets sharing an address, a unicast datagram reaches just one, which the OS picks (Socket::BindFlags::fSO_REUSEADDR)
64 {
65 static constexpr Execution::Activity kActivity_{"creating SSDP sockets in PeriodicNotifier"sv};
66 Execution::DeclareActivity da{&kActivity_};
67 if (InternetProtocol::IP::SupportIPV4 (options.fIPVersion)) {
68 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (SocketAddress::INET, Socket::DGRAM);
69 s.SetMulticastTTL (options.fMulticastTTL);
70 sockets += make_pair (s, UPnP::SSDP::V4::kSocketAddress);
71 }
72 if (InternetProtocol::IP::SupportIPV6 (options.fIPVersion)) {
73 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (SocketAddress::INET6, Socket::DGRAM);
74 s.SetMulticastTTL (options.fMulticastTTL);
75 sockets += make_pair (s, UPnP::SSDP::V6::kSocketAddress);
76 }
77 }
78
79 if constexpr (qStroika_Foundation_Debug_DefaultTracingOn) {
80 Debug::TraceContextBumper ctx{"SSDP PeriodicNotifier - first time notifications"};
81 for ([[maybe_unused]] const auto& a : advertisements) {
82 DbgTrace ("(alive,usn={},...)"_f, a.fUSN);
83 }
84 }
85
86 // NOTIFYs on socket s go out of interface i - whose own address, of s's family, is local
87 auto goOutOf = [] (const ConnectionlessSocket::Ptr& s, const Interface& i, const InternetAddress& local) {
88 if (local.GetAddressFamily () == InternetAddress::AddressFamily::V4) {
89 s.SetMulticastInterface (local);
90 }
91 else {
93 }
94 };
95
96 // the NOTIFYs go out on the IntervalTimer's thread, and - right after a network appears - the network-change thread's: one at a time
97 shared_ptr<mutex> sendingNotifies = Memory::MakeSharedPtr<mutex> ();
98 shared_ptr<Execution::Synchronized<InterfacesByID>> notifyingOn = fNotifyingOn_;
99 Execution::IntervalTimer::TimerCallback callback = [=] () mutable {
100#if USE_NOISY_TRACE_IN_THIS_MODULE_
101 Debug::TraceContextBumper ctx{"SSDP PeriodicNotifier - notifications"};
102 for ([[maybe_unused]] const auto& a : advertisements) {
103#if USE_NOISY_TRACE_IN_THIS_MODULE_
104 String msg;
105 msg += "alive," sz;
106 msg += "location=" sz + a.fLocation + ", " sz;
107 msg += "ST=" sz + a.fST + ", " sz;
108 msg += "USN=" sz + a.fUSN;
109 DbgTrace (L"(%s)", msg.c_str ());
110#endif
111 }
112#endif
113 [[maybe_unused]] lock_guard critSec{*sendingNotifies}; // (through the sets' spacing too: only the other sender waits for it)
114 InterfacesByID sentOn;
115 // listed once for the round, not for each set: listing them can be slow, and would put that between the sets too
116 InterfacesByID interfaces;
117 try {
118 interfaces = SSDP::Private_::GetSSDPInterfaces (interfaceFilter);
119 }
120 catch (const Execution::Thread::AbortException&) {
122 }
123 catch (...) {
124 DbgTrace ("Ignoring inability to send SSDP notify packets: {} (try again later)"_f, current_exception ());
125 }
126 for (unsigned int nthSet = 0; nthSet < kSetsSent_; ++nthSet) {
127 if (nthSet != 0) {
128 Execution::Sleep (kBetweenSets_); // on the IntervalTimer's thread, which other timers share - so briefly
129 }
130 // out of each interface, with that interface's own LOCATION
131 for (const Interface& i : interfaces) {
132 for (const pair<ConnectionlessSocket::Ptr, SocketAddress>& s : sockets) {
133 try {
134 InternetAddress::AddressFamily family = s.second.GetInternetAddress ().GetAddressFamily ();
135 optional<InternetAddress> local = SSDP::Server::Private_::AdvertisableAddress (i, family);
136 if (not local) {
137 continue; // no address of this channel's family there
138 }
139 optional<URI> url = location (LocationContext{*local, nullopt});
140 if (not url) {
141 continue; // nothing to advertise there
142 }
143 goOutOf (s.first, i, *local);
144 for (Advertisement a : advertisements) {
145 a.fAlive = true; // (and ssdp:byebye as this goes - ~PeriodicNotifier)
146 a.fLocation = *url;
147 a.fMaxAge = maxAge;
148 s.first.SendTo (SSDP::Serialize ("NOTIFY * HTTP/1.1"sv, SearchOrNotify::Notify, a, s.second), s.second);
149 }
150 sentOn.Add (i);
151 }
152 catch (const Execution::Thread::AbortException&) {
154 }
155 catch (...) {
156 DbgTrace ("Ignoring inability to send SSDP notify packets on {}: {} (try again later)"_f, i.fInterfaceID, current_exception ());
157 }
158 }
159 }
160 }
161 notifyingOn->store (sentOn);
162 };
163 // as this goes (~PeriodicNotifier): an ssdp:byebye for each advertisement, out of each interface the last ssdp:alive went out of
164 fSayByebye_ = [sockets, advertisements, sendingNotifies, notifyingOn, goOutOf] () {
165 [[maybe_unused]] lock_guard critSec{*sendingNotifies};
166 // as many sets as of ssdp:alive: one byebye for each alive (UPnP Device Architecture 1.1, section 1.2.3)
167 for (unsigned int nthSet = 0; nthSet < kSetsSent_; ++nthSet) {
168 if (nthSet != 0) {
169 Execution::Sleep (kBetweenSets_);
170 }
171 for (const Interface& i : notifyingOn->load ()) {
172 for (const pair<ConnectionlessSocket::Ptr, SocketAddress>& s : sockets) {
173 try {
174 InternetAddress::AddressFamily family = s.second.GetInternetAddress ().GetAddressFamily ();
175 optional<InternetAddress> local = SSDP::Server::Private_::AdvertisableAddress (i, family);
176 if (not local) {
177 continue; // no address of this channel's family there
178 }
179 goOutOf (s.first, i, *local);
180 for (Advertisement a : advertisements) {
181 a.fAlive = false;
182 s.first.SendTo (SSDP::Serialize ("NOTIFY * HTTP/1.1"sv, SearchOrNotify::Notify, a, s.second), s.second);
183 }
184 }
185 catch (...) {
186 DbgTrace ("Ignoring inability to send SSDP byebye packets on {}: {}"_f, i.fInterfaceID, current_exception ());
187 }
188 }
189 }
190 }
191 notifyingOn->store (InterfacesByID{});
192 };
193 Execution::Sleep (SSDP::Private_::RandomDuration (kBeforeFirstSetAtMost_));
194 // then 3/4 of fRepeatInterval after each, give or take 1/4 at random: a random time between half of it and all of it after the
195 // last (1.1, section 1.2.2: "a randomly-distributed interval") - so devices do not keep announcing together
196 fIntervalTimerAdder_ = make_unique<Execution::IntervalTimer::Adder> (callback, Time::Duration{options.fFrequencyInfo.fRepeatInterval * 0.75},
198 Time::Duration{options.fFrequencyInfo.fRepeatInterval * 0.25});
199 if (options.fFollowNetworkChanges) {
200 fNetworkChanges_ = SSDP::Private_::FollowNetworkChanges ([callback] () mutable {
201 Execution::Sleep (SSDP::Private_::RandomDuration (kBeforeFirstSetAtMost_));
202 callback ();
203 });
204 }
205}
206
208{
209 Debug::TraceContextBumper ctx{"SSDP PeriodicNotifier - ssdp:byebye"};
210 // no more ssdp:alive - with these gone, none is under way, nor to come - then ssdp:byebye, where they went
211 fNetworkChanges_.reset ();
212 fIntervalTimerAdder_.reset ();
213 Execution::Thread::SuppressInterruptionInContext suppressInterruption; // a destructor: finish, even on a thread being aborted
214 IgnoreExceptionsForCall (fSayByebye_ ());
215}
216
218{
219 return fNotifyingOn_->load ();
220}
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
function< bool(const Foundation::IO::Network::Interface &i)> InterfaceFilter
Which network interfaces SSDP is to talk on - asked of each interface every time SSDP lists them,...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:327
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1053
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
nonvirtual bool Add(ArgByValueType< value_type > item)
nonvirtual void SendTo(span< const byte > data, const SocketAddress &sockAddr) const
nonvirtual void SetMulticastInterface(const Interface &i) const
nonvirtual SocketAddress::FamilyType GetAddressFamily() const
Definition Socket.inl:62
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:234
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement) const
Run the argument function (or lambda) on each element of the container.
PeriodicNotifier(const Iterable< Advertisement > &advertisements, const LocationProvider &location, const Options &options=kDefaultOptions)
nonvirtual IO::Network::InterfacesByID GetNetworkInterfaces() const
The network interfaces its last NOTIFYs went out of - as they were then (so with the addresses they h...
~PeriodicNotifier()
Stops the NOTIFYs, then sends an ssdp:byebye for each advertisement, out of each interface its last N...
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.inl:101
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
bool SupportIPV4(IPVersionSupport flag)
Definition IP.inl:8