4#include "Stroika/Frameworks/StroikaPreComp.h"
8#include "Stroika/Foundation/Containers/Collection.h"
10#include "Stroika/Foundation/Execution/Activity.h"
11#include "Stroika/Foundation/Execution/Exceptions.h"
16#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
30using namespace Stroika::Foundation::IO;
33using Memory::MakeSharedPtr;
35using namespace Stroika::Frameworks;
36using namespace Stroika::Frameworks::UPnP;
37using namespace Stroika::Frameworks::UPnP::SSDP;
38using namespace Stroika::Frameworks::UPnP::SSDP::Client;
43class Search::Rep_ final {
45 Rep_ (
const Options& options)
46 : fInterfaceFilter_{options.fInterfaces}
48 static constexpr Activity kConstructingSSDPSearcher_{
"constructing SSDP searcher"sv};
59 cs.SetReceivePacketInfo (
true);
60 cs.SetMulticastLoopMode (
true);
61 cs.SetMulticastTTL (options.fMulticastTTL);
63 if (options.fFollowNetworkChanges) {
64 fNetworkChanges_ = SSDP::Private_::FollowNetworkChanges ([
this] () { SearchAgain_ (); });
68 CallbackID AddOnFoundCallback (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds)
70 return fFoundCallbacks_.Add (callOnFinds);
72 void RemoveOnFoundCallback (CallbackID callOnFinds)
74 fFoundCallbacks_.Remove (callOnFinds);
76 void Start (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
78 [[maybe_unused]] lock_guard lifecycle{fLifecycleMutex_};
79 StartThread_ (serviceType, autoRetryInterval);
83 return fSearchingOn_.load ();
87 [[maybe_unused]] lock_guard lifecycle{fLifecycleMutex_};
88 if (fThread_ !=
nullptr) {
89 fThread_.AbortAndWaitForDone ();
91 fSearching_ = nullopt;
93 void StartThread_ (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
95 if (fThread_ !=
nullptr) {
96 fThread_.AbortAndWaitForDone ();
98 fSearching_ = make_pair (serviceType, autoRetryInterval);
99 fSearchAgain_ =
false;
100 fThread_ =
Thread::New ([
this, serviceType, autoRetryInterval] () { DoRun_ (serviceType, autoRetryInterval); }, Thread::eAutoStart,
"SSDP Searcher"sv);
107 fSearchAgain_ =
true;
109 void DoRun_ (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
111 bool didFirstRetry =
false;
115 optional<Time::TimePointSeconds> retrySendAt;
116 if (not didFirstRetry) {
117 retrySendAt = Time::GetTickCount () + 2s;
118 didFirstRetry =
true;
120 else if (autoRetryInterval.has_value ()) {
121 retrySendAt = Time::GetTickCount () + *autoRetryInterval;
123 const InterfacesByID candidates = SSDP::Private_::GetSSDPInterfaces (fInterfaceFilter_);
126#if USE_NOISY_TRACE_IN_THIS_MODULE_
132 constexpr unsigned int kMX_ = 4;
133 stringstream requestBuf;
134 requestBuf <<
"M-SEARCH * HTTP/1.1\r\n"sv;
137 case SocketAddress::FamilyType::INET: {
140 case SocketAddress::FamilyType::INET6: {
148 requestBuf <<
"Host: "sv << hostAuthority.
As<
String> ().AsUTF8<string> () <<
"\r\n";
149 requestBuf <<
"Man: \"ssdp:discover\"\r\n"sv;
150 requestBuf <<
"ST: "sv << serviceType.
AsUTF8<
string> ().c_str () <<
"\r\n";
151 requestBuf <<
"MX: "sv << kMX_ <<
"\r\n";
152 requestBuf <<
"\r\n"sv;
153 request = requestBuf.str ();
155#if USE_NOISY_TRACE_IN_THIS_MODULE_
156 DbgTrace (
"DETAILS: {}"_f, request);
160 const span<const byte> data{
reinterpret_cast<const byte*
> (request.c_str ()), request.length ()};
163 if (not i.fBindings.fAddresses.Any ([&] (
const InternetAddress& a) { return a.GetAddressFamily () == family; })) {
168 s.
SendTo (data, useSocketAddress);
171 catch (
const Thread::AbortException&) {
175 DbgTrace (
"SSDP Search: could not send M-SEARCH on {}: {}"_f, i.fInterfaceID, current_exception ());
180 fSearchingOn_.store (searchedOn);
185 if (fSearchAgain_.exchange (
false)) {
186 didFirstRetry =
false;
190 if (retrySendAt and *retrySendAt < wakeAt) {
191 wakeAt = *retrySendAt;
197 optional<ConnectionlessSocket::PacketInfo> arrived;
198 size_t nBytesRead = s.
ReceiveFrom (buf, 0, &from, &arrived).size ();
199 Assert (nBytesRead <= std::size (buf));
200 ReadPacketAndNotifyCallbacks_ (span{buf, nBytesRead}, SSDP::Private_::ReceivedOn (arrived, fSearchingOn_.load ()));
202 catch (
const Thread::AbortException&) {
211 if (retrySendAt and *retrySendAt < Time::GetTickCount ()) {
216 void ReadPacketAndNotifyCallbacks_ (span<const byte> packet,
const optional<Interface::SystemIDType>& receivedOn)
219 SSDP::Advertisement d;
220 SSDP::DeSerialize (
Memory::BLOB{packet}, &headLine, &d);
221 d.fReceivedOn = receivedOn;
222#if USE_NOISY_TRACE_IN_THIS_MODULE_
224 DbgTrace (
"headLine: {}"_f, headLine);
227 fFoundCallbacks_.Call (d);
236 mutex fLifecycleMutex_;
237 atomic<bool> fSearchAgain_{
false};
238 optional<pair<String, optional<Time::Duration>>> fSearching_;
242 optional<SSDP::Private_::NetworkChangeFollower> fNetworkChanges_;
250const String Search::kSSDPAny = SSDP::kTarget_SSDPAll;
251const String Search::kRootDevice =
"upnp:rootdevice"sv;
253Search::Search (
const Options& options)
258Search::Search (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds,
const Options& options)
261 AddOnFoundCallback (callOnFinds);
264Search::Search (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds,
const String& initialSearch,
const Options& options)
265 : Search{callOnFinds, options}
267 Start (initialSearch);
270Search::Search (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds,
const String& initialSearch,
271 const optional<Time::Duration>& autoRetryInterval,
const Options& options)
272 : Search{callOnFinds, options}
274 Start (initialSearch, autoRetryInterval);
279 return fRep_->GetNetworkInterfaces ();
284 if (fRep_ !=
nullptr) {
285 IgnoreExceptionsForCall (fRep_->Stop ());
289auto Search::AddOnFoundCallback (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds) -> CallbackID
291 return fRep_->AddOnFoundCallback (callOnFinds);
294void Search::RemoveOnFoundCallback (CallbackID callOnFinds)
296 fRep_->RemoveOnFoundCallback (callOnFinds);
299void Search::Start (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
301 fRep_->Start (serviceType, autoRetryInterval);
#define AssertNotReached()
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
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,...
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
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)
The callbacks an object calls when something happens - each added, and removed by the ID Add gave it ...
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual void SendTo(span< const byte > data, const SocketAddress &sockAddr) const
nonvirtual void SetMulticastInterface(const Interface &i) const
nonvirtual span< byte > ReceiveFrom(span< byte > into, int flag, SocketAddress *fromAddress, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual constexpr AddressFamily GetAddressFamily() const
nonvirtual SocketAddress::FamilyType GetAddressFamily() const
nonvirtual PortType GetPort() const
nonvirtual InternetAddress GetInternetAddress() const
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
void Sleep(Time::Duration seconds2Wait)
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
bool SupportIPV4(IPVersionSupport flag)
bool SupportIPV6(IPVersionSupport flag)