4#include "Stroika/Frameworks/StroikaPreComp.h"
7#include "Stroika/Foundation/Characters/String2Int.h"
8#include "Stroika/Foundation/Containers/Sequence.h"
11#include "Stroika/Foundation/Execution/Throw.h"
13#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
18#include "Stroika/Foundation/Streams/MemoryStream.h"
24#include "SearchResponder.h"
31using namespace Stroika::Foundation::IO;
35using namespace Stroika::Frameworks;
36using namespace Stroika::Frameworks::UPnP;
37using namespace Stroika::Frameworks::UPnP::SSDP;
38using namespace Stroika::Frameworks::UPnP::SSDP::Server;
60 optional<URI> LocationFor_ (
const LocationProvider& location,
const SocketAddress& asker,
61 const optional<ConnectionlessSocket::PacketInfo>& arrived,
const InterfacesByID& listeningOn)
63 optional<InternetAddress> local;
64 optional<Interface> arrivedOn;
66 if (not arrived->fDestination.IsMulticastAddress ()) {
67 local = arrived->fDestination;
69 arrivedOn = listeningOn.
First ([&] (
const Interface& i) {
return i.
fIndex == arrived->fInterfaceIndex; });
73 listeningOn.
Any ([&] (
const Interface& i) { return i.fBindings.fAddresses.Contains (asker.GetInternetAddress ()); })) {
76 if (not local and arrivedOn) {
80 local = SSDP::Server::Private_::LocalAddressOnAskersNetwork (asker.
GetInternetAddress (), listeningOn);
82 if (not local and arrivedOn) {
91 if (local->GetAddressFamily () == InternetAddress::AddressFamily::V6 and local->IsLinkLocalAddress ()) {
95 local = SSDP::Server::Private_::AdvertisableAddress (*i, InternetAddress::AddressFamily::V6);
111 constexpr size_t kMaxWaitingAnswers_{1000};
114 optional<String> Header_ (
const SSDP::Advertisement& a,
const String& name)
126 optional<Time::DurationSeconds> MX_ (
const SSDP::Advertisement& search)
128 optional<String> mx = Header_ (search,
"MX"sv);
129 if (not mx or mx->empty () or not mx->All ([] (
Character c) { return c.IsDigit (); })) {
137 bool IsMulticast_ (
const SSDP::Advertisement& search,
const optional<ConnectionlessSocket::PacketInfo>& arrived)
140 return arrived->fDestination.IsMulticastAddress ();
142 if (optional<String> host = Header_ (search,
"HOST"sv)) {
144 if (optional<InternetAddress> ia = a->GetHost () ? a->GetHost ()->AsInternetAddress () : nullopt) {
145 return ia->IsMulticastAddress ();
153 optional<pair<String, unsigned int>> TypeAndVersion_ (
const String& target)
155 optional<size_t> lastColon = target.
RFind (
':');
156 if (not target.
StartsWith (
"urn:"sv, eCaseInsensitive) or not lastColon) {
160 if (version.empty () or version.
size () > 9 or not version.
All ([] (
Character c) { return c.IsDigit (); })) {
163 return make_pair (target.
SubString (0, *lastColon), Characters::String2Int<unsigned int> (version));
169 optional<Advertisement> AnswerTo_ (
const String& searchTarget,
const Advertisement& a)
172 if (equals (searchTarget, kTarget_SSDPAll) or equals (searchTarget, a.fTarget)) {
175 optional<pair<String, unsigned int>> searchedFor = TypeAndVersion_ (searchTarget);
176 optional<pair<String, unsigned int>> advertised = TypeAndVersion_ (a.fTarget);
177 if (searchedFor and advertised and equals (searchedFor->first, advertised->first) and 1 <= searchedFor->second and
178 searchedFor->second < advertised->second) {
179 Advertisement answer = a;
180 answer.fTarget = searchTarget;
181 if (a.fUSN.EndsWith (
"::"sv + a.fTarget, eCaseInsensitive)) {
182 answer.fUSN = a.fUSN.SubString (0, a.fUSN.size () - a.fTarget.size ()) + searchTarget;
194 const optional<ConnectionlessSocket::PacketInfo>& arrived,
const InterfacesByID& listeningOn)
197 SSDP::Advertisement da;
198 SSDP::DeSerialize (
Memory::BLOB{packet}, &headLine, &da);
199#if USE_NOISY_TRACE_IN_THIS_MODULE_
201 DbgTrace (
"headLine: {}"_f, headLine);
205 optional<Time::DurationSeconds> waitAtMost;
206 if (IsMulticast_ (da, arrived)) {
207 waitAtMost = MX_ (da);
208 if (not waitAtMost) {
213 for (
const Advertisement& a : advertisements) {
214 if (optional<Advertisement> answer = AnswerTo_ (da.fTarget, a)) {
215 answering += *answer;
218 optional<URI> url = answering.
empty () ? nullopt : LocationFor_ (location, sendTo, arrived, listeningOn);
220#if USE_NOISY_TRACE_IN_THIS_MODULE_
221 DbgTrace (
"sending search responder advertisements..."_f);
223 for (Advertisement a : answering) {
229 dueAt += SSDP::Private_::RandomDuration (*waitAtMost);
231 result += Answer_{dueAt, useSocket, sendTo, SSDP::Serialize (
"HTTP/1.1 200 OK"sv, SearchOrNotify::SearchResponse, a)};
232#if USE_NOISY_TRACE_IN_THIS_MODULE_
233 DbgTrace (
"(location={},TARGET(ST/NT)={},USN={})"_f, a.fLocation, a.fTarget, a.fUSN);
245 advertisements.
Apply ([] ([[maybe_unused]]
const auto& a) { Require (not a.fTarget.empty ()); });
250 static constexpr Activity kActivity_{
"SSDP Binding in SearchResponder"sv};
257 s.
Bind (
SocketAddress{Network::V4::kAddrAny, UPnP::SSDP::V4::kSocketAddress.
GetPort ()}, Socket::BindFlags{.fSO_REUSEADDR =
true});
259 socketsAndGroups += make_pair (s, UPnP::SSDP::V4::kSocketAddress.GetInternetAddress ());
264 s.
Bind (
SocketAddress{Network::V6::kAddrAny, UPnP::SSDP::V6::kSocketAddress.
GetPort ()}, Socket::BindFlags{.fSO_REUSEADDR =
true});
266 socketsAndGroups += make_pair (s, UPnP::SSDP::V6::kSocketAddress.GetInternetAddress ());
271 static const String kThreadName_{
"SSDP Search Responder"sv};
273 [
this, advertisements =
Sequence<Advertisement>{advertisements}, location, interfaceFilter = options.fInterfaces, socketsAndGroups] () {
277 fJoinAgain_->GetWaitInfo ()};
282 optional<Time::TimePointSeconds> joinAt = Time::GetTickCount ();
289 if (fJoinAgain_->IsSet () or (joinAt and *joinAt <= Time::GetTickCount ())) {
290 fJoinAgain_->Clear ();
291 listeningOn = SSDP::Private_::JoinOnEveryInterface (socketsAndGroups, interfaceFilter);
292 fListeningOn_.store (listeningOn);
294 if (listeningOn.
empty ()) {
296 DbgTrace (
"SSDP SearchResponder: could join on no interface, so try again in {}"_f, retryJoinAfter);
297 joinAt = Time::GetTickCount () + retryJoinAfter;
298 retryJoinAfter = min<Time::DurationSeconds> (retryJoinAfter * 2, 60s);
302 for (
const Answer_& a : waiting) {
303 nextDue = min (nextDue, a.fDueAt);
307 optional<ConnectionlessSocket::PacketInfo> arrived;
309 size_t nBytesRead = s.ReceiveFrom (buf, 0, &from, &arrived).size ();
310 Assert (nBytesRead <= std::size (buf));
311 for (
const Answer_& a : Answers_ (span{buf, nBytesRead}, advertisements, location, s, from, arrived, listeningOn)) {
312 if (waiting.
size () < kMaxWaitingAnswers_) {
319 for (
const Answer_& a : waiting) {
320 if (a.fDueAt <= now) {
321 IgnoreExceptionsExceptThreadAbortForCall (a.fSocket.SendTo (a.fPacket, a.fTo));
329 catch (
const Thread::AbortException&) {
339 Thread::eAutoStart, kThreadName_);
340 if (options.fFollowNetworkChanges) {
343 fNetworkChanges_ = SSDP::Private_::FollowNetworkChanges ([
this] () { fJoinAgain_->Set (); });
349 return fListeningOn_.load ();
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
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 size_t size() const noexcept
nonvirtual String SubString(SZ from) const
nonvirtual bool StartsWith(const Character &c, CompareOptions co=eWithCase) const
nonvirtual optional< size_t > RFind(Character c) const noexcept
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual RESULT_CONTAINER Map(ELEMENT_MAPPER &&elementMapper) const
'override' Iterable<>::Map () function so RESULT_CONTAINER defaults to Sequence, and improve that cas...
nonvirtual void SetReceivePacketInfo(bool on) const
Have ReceiveFrom say where each datagram arrived (.
nonvirtual void SetMulticastLoopMode(bool loopMode) const
nonvirtual constexpr AddressFamily GetAddressFamily() const
nonvirtual void Bind(const SocketAddress &sockAddr, BindFlags bindFlags=BindFlags{})
nonvirtual PortType GetPort() const
nonvirtual InternetAddress GetInternetAddress() const
nonvirtual bool IsInternetAddress() const
nonvirtual InterfacesByID GetAll()
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
nonvirtual bool Any() const
Any() same as not empty (); Any (includeIfTrue) returns true iff includeIfTrue returns true on any va...
nonvirtual optional< T > First() const
return first element in iterable, or if 'that' specified, first where 'that' is true,...
nonvirtual bool All(const function< bool(ArgByValueType< T >)> &testEachElt) const
return true iff argument predicate returns true for each element of the iterable
nonvirtual size_t size() const
Returns the number of items contained.
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement) const
Run the argument function (or lambda) on each element of the container.
nonvirtual bool empty() const
Returns true iff size() == 0.
SearchResponder(const Iterable< Advertisement > &advertisements, const LocationProvider &location, const Options &options=kDefaultOptions)
nonvirtual IO::Network::InterfacesByID GetNetworkInterfaces() const
The network interfaces it is listening for searches on now - as they were when it joined them (so wit...
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)
optional< InternetAddress > GetLocalAddressToReach(const SocketAddress &peer)
This machine's address for reaching peer - the local address a datagram sent to peer would carry,...
optional< unsigned int > fIndex