Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
SearchResponder.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
10#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
13#include "Stroika/Foundation/Streams/MemoryStream.h"
14
17
18#include "SearchResponder.h"
19
20using std::byte;
21
22using namespace Stroika::Foundation;
25using namespace Stroika::Foundation::IO;
27using namespace Stroika::Foundation::Execution;
28
29using namespace Stroika::Frameworks;
30using namespace Stroika::Frameworks::UPnP;
31using namespace Stroika::Frameworks::UPnP::SSDP;
32using namespace Stroika::Frameworks::UPnP::SSDP::Server;
33
34// Comment this in to turn on tracing in this module
35//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
36
37/*
38********************************************************************************
39******************************** SearchResponder *******************************
40********************************************************************************
41*/
42namespace {
43 void ParsePacketAndRespond_ (Streams::InputStream::Ptr<Character> in, const Iterable<Advertisement>& advertisements,
45 {
46 String firstLine = in.ReadLine ().Trim ();
47
48#if USE_NOISY_TRACE_IN_THIS_MODULE_
49 Debug::TraceContextBumper ctx{"Read SSDP Packet"};
50 DbgTrace (L"firstLine: %s", firstLine.c_str ());
51#endif
52 static const String kNOTIFY_LEAD = "M-SEARCH "sv;
53 if (firstLine.length () > kNOTIFY_LEAD.length () and firstLine.SubString (0, kNOTIFY_LEAD.length ()) == kNOTIFY_LEAD) {
54 SSDP::Advertisement da;
55 while (true) {
56 String line = in.ReadLine ().Trim ();
57 if (line.empty ()) {
58 break;
59 }
60
61 // Need to simplify this code (stroika string util)
62 String label;
63 String value;
64 if (optional<size_t> n = line.Find (':')) {
65 label = line.SubString (0, *n);
66 value = line.SubString (*n + 1).Trim ();
67 }
68 if (not label.empty ()) {
69 da.fRawHeaders.Add (label, value);
70 }
71 constexpr auto kLabelComparer_ = String::ThreeWayComparer{Characters::eCaseInsensitive};
72 if (kLabelComparer_ (label, "ST"sv) == 0) {
73 da.fTarget = value;
74 }
75 }
76
77 bool matches = false;
78 auto targetEqComparer = String::EqualsComparer{eCaseInsensitive};
79 if (targetEqComparer (da.fTarget, kTarget_UPNPRootDevice)) {
80 matches = true;
81 }
82 else if (targetEqComparer (da.fTarget, kTarget_SSDPAll)) {
83 matches = true;
84 }
85 else {
86 for (const auto& a : advertisements) {
87 if (targetEqComparer (a.fTarget, da.fTarget)) {
88 matches = true;
89 break;
90 }
91 }
92 }
93 if (matches) {
94// if any match, I think we are supposed to send all
95#if USE_NOISY_TRACE_IN_THIS_MODULE_
96 DbgTrace (L"sending search responder advertisements...");
97#endif
98 for (auto a : advertisements) {
99 a.fAlive = nullopt; // in responder we don't set alive flag
100
101 bool includeThisAdvertisement = false;
102 if (targetEqComparer (da.fTarget, kTarget_SSDPAll)) {
103 includeThisAdvertisement = true;
104 }
105 else {
106 includeThisAdvertisement = targetEqComparer (a.fTarget, da.fTarget);
107 }
108
109 if (includeThisAdvertisement) {
110 Memory::BLOB data = SSDP::Serialize ("HTTP/1.1 200 OK"sv, SearchOrNotify::SearchResponse, a);
111 useSocket.SendTo (data, sendTo);
112#if USE_NOISY_TRACE_IN_THIS_MODULE_
113 DbgTrace ("(location={},TARGET(ST/NT)={},USN={})"_f, a.fLocation, a.fTarget, a.fUSN);
114#endif
115 }
116 }
117 }
118 }
119 }
120}
121
122SearchResponder::SearchResponder (const Iterable<Advertisement>& advertisements, ::Network::InternetProtocol::IP::IPVersionSupport ipVersion)
123{
125 advertisements.Apply ([] ([[maybe_unused]] const auto& a) { Require (not a.fTarget.empty ()); });
126 }
127
128 // Construction of search responder will fail if we cannot bind - instead of failing quietly inside the loop
130 {
131 static constexpr Activity kActivity_{"SSDP Binding in SearchResponder"sv};
132 DeclareActivity da{&kActivity_};
133 constexpr unsigned int kMaxHops_ = 4;
134 if (InternetProtocol::IP::SupportIPV4 (ipVersion)) {
135 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (SocketAddress::INET, Socket::DGRAM);
136 s.Bind (SocketAddress{Network::V4::kAddrAny, UPnP::SSDP::V4::kSocketAddress.GetPort ()}, Socket::BindFlags{.fSO_REUSEADDR = true});
137 s.SetMulticastLoopMode (true); // probably should make this configurable
138 s.SetMulticastTTL (kMaxHops_);
139 sockets += make_pair (s, UPnP::SSDP::V4::kSocketAddress);
140 }
141 if (InternetProtocol::IP::SupportIPV6 (ipVersion)) {
142 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (SocketAddress::INET6, Socket::DGRAM);
143 s.Bind (SocketAddress{Network::V6::kAddrAny, UPnP::SSDP::V6::kSocketAddress.GetPort ()}, Socket::BindFlags{.fSO_REUSEADDR = true});
144 s.SetMulticastLoopMode (true); // probably should make this configurable
145 s.SetMulticastTTL (kMaxHops_);
146 sockets += make_pair (s, UPnP::SSDP::V6::kSocketAddress);
147 }
148 }
149
150 // Use a thread to wait on a set of sockets we are listening for requests on
151 static const String kThreadName_{"SSDP Search Responder"sv};
152 fListenThread_ = Thread::New (
153 [advertisements, sockets] () {
154 Debug::TraceContextBumper ctx{"SSDP SearchResponder thread loop"};
155 {
156 Again:
157 try {
158 for (pair<ConnectionlessSocket::Ptr, SocketAddress> s : sockets) {
159 s.first.JoinMulticastGroup (s.second.GetInternetAddress ());
160 }
161 }
162 catch (const system_error& e) {
163 // SEE http://stroika-bugs.sophists.com/browse/STK-962 -
164 // If I migrate the retry logic to BasicServer, this stuff can go away, I believe... LOW PRIORITY - since this does work - last time I tried
165
166 if (e.code () == errc::no_such_device) {
167 // This can happen on Linux when you start before you have a network connection - no problem - just keep trying
168 DbgTrace ("Got exception (errno: ENODEV) - while joining multicast group, so try again"_f);
169 Sleep (1s);
170 goto Again;
171 }
172 else {
173 ReThrow ();
174 }
175 }
176 }
177
178 // only stopped by thread abort
179 auto inUseSockets = sockets.Map<Iterable<ConnectionlessSocket::Ptr>> ([] (auto i) { return i.first; });
180 while (true) {
181 try {
182 for (ConnectionlessSocket::Ptr s : WaitForIOReady{inUseSockets}.WaitQuietly ()) {
183 SocketAddress from;
184 byte buf[4 * 1024]; // not sure of max packet size
185 size_t nBytesRead = s.ReceiveFrom (buf, 0, &from).size ();
186 Assert (nBytesRead <= Memory::NEltsOf (buf));
187 using namespace Streams;
188 ParsePacketAndRespond_ (BinaryToText::Reader::New (ExternallyOwnedSpanInputStream::New<byte> (span{buf, nBytesRead})),
189 advertisements, s, from);
190 }
191 }
192 catch (const Thread::AbortException&) {
193 ReThrow ();
194 }
195 catch (...) {
196 // ignore errors - and keep on trucking
197 // but avoid wasting too much time if we get into an error storm
198 Sleep (1.0s);
199 }
200 }
201 },
202 Thread::eAutoStart, kThreadName_);
203}
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define DbgTrace
Definition Trace.h:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1045
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
Definition String.inl:1049
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1592
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
Definition String.inl:681
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
Definition Collection.h:102
nonvirtual RESULT_CONTAINER Map(ELEMENT_MAPPER &&elementMapper) const
'override' Iterable<>::Map () function so RESULT_CONTAINER defaults to Collection,...
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 Bind(const SocketAddress &sockAddr, BindFlags bindFlags=BindFlags{})
Definition Socket.cpp:212
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument function (or lambda) on each element of the container.
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
bool SupportIPV4(IPVersionSupport flag)
Definition IP.inl:8