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"
15#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
27using namespace Stroika::Foundation::IO;
30using namespace Stroika::Frameworks;
31using namespace Stroika::Frameworks::UPnP;
32using namespace Stroika::Frameworks::UPnP::SSDP;
33using namespace Stroika::Frameworks::UPnP::SSDP::Client;
42 static constexpr Execution::Activity kConstructingSSDPSearcher_{
"constructing SSDP searcher"sv};
53 cs.SetMulticastLoopMode (
true);
57 void AddOnFoundCallback (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds)
59 [[maybe_unused]] lock_guard critSec{fCritSection_};
60 fFoundCallbacks_.push_back (callOnFinds);
62 void Start (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
64 if (fThread_ !=
nullptr) {
65 fThread_.AbortAndWaitForDone ();
67 fThread_ =
Execution::Thread::New ([
this, serviceType, autoRetryInterval] () { DoRun_ (serviceType, autoRetryInterval); },
68 Execution::Thread::eAutoStart,
"SSDP Searcher"sv);
72 if (fThread_ !=
nullptr) {
76 void DoRun_ (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
78 bool didFirstRetry =
false;
82 optional<Time::TimePointSeconds> retrySendAt;
83 if (not didFirstRetry) {
84 retrySendAt = Time::GetTickCount () + 2s;
87 else if (autoRetryInterval.has_value ()) {
88 retrySendAt = Time::GetTickCount () + *autoRetryInterval;
91#if USE_NOISY_TRACE_IN_THIS_MODULE_
102 const unsigned int kMaxHops_ = 4;
103 stringstream requestBuf;
104 requestBuf <<
"M-SEARCH * HTTP/1.1\r\n"sv;
107 case SocketAddress::FamilyType::INET: {
110 case SocketAddress::FamilyType::INET6: {
118 requestBuf <<
"Host: "sv << hostAuthority.
As<
String> ().AsUTF8<string> () <<
"\r\n";
119 requestBuf <<
"Man: \"ssdp:discover\"\r\n"sv;
120 requestBuf <<
"ST: "sv << serviceType.
AsUTF8<
string> ().c_str () <<
"\r\n";
121 requestBuf <<
"MX: "sv << kMaxHops_ <<
"\r\n";
122 requestBuf <<
"\r\n"sv;
123 request = requestBuf.str ();
126#if USE_NOISY_TRACE_IN_THIS_MODULE_
127 DbgTrace (
"DETAILS: {}"_f, request);
129 s.
SendTo (span{
reinterpret_cast<const byte*
> (request.c_str ()), request.length ()}, useSocketAddress);
139 size_t nBytesRead = s.
ReceiveFrom (buf, 0, &from).size ();
140 Assert (nBytesRead <= Memory::NEltsOf (buf));
141 using namespace Streams;
142 ReadPacketAndNotifyCallbacks_ (BinaryToText::Reader::New (ExternallyOwnedSpanInputStream::New<byte> (span{buf, nBytesRead})));
144 catch (
const Execution::Thread::AbortException&) {
153 if (retrySendAt and *retrySendAt < Time::GetTickCount ()) {
162#if USE_NOISY_TRACE_IN_THIS_MODULE_
164 DbgTrace (
"firstLine: {}"_f, firstLine);
167 static const String kOKRESPONSELEAD_ =
"HTTP/1.1 200"sv;
168 if (firstLine.
length () >= kOKRESPONSELEAD_.
length () and firstLine.
SubString (0, kOKRESPONSELEAD_.
length ()) == kOKRESPONSELEAD_) {
169 SSDP::Advertisement d;
172#if USE_NOISY_TRACE_IN_THIS_MODULE_
173 DbgTrace (L
"reply-line: {}"_f, line);
180 if (optional<size_t> n = line.
Find (
':')) {
183 if (String::ThreeWayComparer{eCaseInsensitive}(label,
"Location"sv) == 0) {
186 else if (String::ThreeWayComparer{eCaseInsensitive}(label,
"ST"sv) == 0) {
189 else if (String::ThreeWayComparer{eCaseInsensitive}(label,
"USN"sv) == 0) {
192 else if (String::ThreeWayComparer{eCaseInsensitive}(label,
"Server"sv) == 0) {
199 [[maybe_unused]] lock_guard critSec{fCritSection_};
200 for (
const auto& i : fFoundCallbacks_) {
208 recursive_mutex fCritSection_;
209 vector<function<void (
const SSDP::Advertisement& d)>> fFoundCallbacks_;
219const String Search::kSSDPAny =
"ssdp:any"sv;
220const String Search::kRootDevice =
"upnp:rootdevice"sv;
223 : fRep_{make_shared<Rep_> (ipVersion)}
230 AddOnFoundCallback (callOnFinds);
233Search::Search (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds,
const String& initialSearch,
235 : Search{callOnFinds, ipVersion}
237 Start (initialSearch);
240Search::Search (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds,
const String& initialSearch,
242 : Search{callOnFinds, ipVersion}
244 Start (initialSearch, autoRetryInterval);
249 IgnoreExceptionsForCall (fRep_->Stop ());
252void Search::AddOnFoundCallback (
const function<
void (
const SSDP::Advertisement& d)>& callOnFinds)
254 fRep_->AddOnFoundCallback (callOnFinds);
257void Search::Start (
const String& serviceType,
const optional<Time::Duration>& autoRetryInterval)
259 fRep_->Start (serviceType, autoRetryInterval);
#define AssertNotReached()
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
constexpr DurationSeconds kInfinity
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual T AsUTF8() const
nonvirtual size_t length() const noexcept
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () - but if doesn't finish fast enough, send extra aborts (...
nonvirtual void SendTo(span< const byte > data, const SocketAddress &sockAddr) const
nonvirtual span< byte > ReceiveFrom(span< byte > into, int flag, SocketAddress *fromAddress, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual void SetMulticastTTL(uint8_t ttl) const
nonvirtual SocketAddress::FamilyType GetAddressFamily() const
nonvirtual PortType GetPort() 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)