Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
DeviceDescription.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
12#include "Stroika/Foundation/Execution/Throw.h"
15
16#include "DeviceDescription.h"
17
18// Comment this in to turn on aggressive noisy DbgTrace in this module
19// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
20
21using std::byte;
22
23using namespace Stroika::Foundation;
26
27using Memory::MakeSharedPtr;
28
29using namespace Stroika::Frameworks;
30using namespace Stroika::Frameworks::UPnP;
31
32namespace {
33 // the namespace every element of a device description is in (UPnP Device Architecture 1.1, section 2.3)
34 const IO::Network::URI kDeviceNamespace_{"urn:schemas-upnp-org:device-1-0"sv};
35
37
38 // DeviceDescription::kMapper - its fields, by UPnP's names - with one of its conversions changed for a description's XML
39 const ObjectVariantMapper& Mapper_ ()
40 {
41 static const ObjectVariantMapper kMapper_ = [] () {
43 // a URL that is not one reads as an empty URL - as UPnP's reader always has - rather than failing the whole
44 // description, as kMapper's would: devices in the field are loose
45 mapper.Add<URI> ([] (const ObjectVariantMapper&, const URI* u) -> VariantValue { return VariantValue{u->As<String> ()}; },
46 [] (const ObjectVariantMapper&, const VariantValue& v, URI* u) {
47 IgnoreExceptionsForCall (*u = URI::Parse (v.As<String> ()));
48 });
49 return mapper;
50 }();
51 return kMapper_;
52 }
53
54 // the description's shape (UPnP Device Architecture 1.1, section 2.3): <root>'s specVersion, URLBase and <device>, their
55 // elements' names and order, UPnP's namespace, each list and its items - and what no DeviceDescription field holds, a
56 // vendor's own elements or an embedded <deviceList> say, skipped
57 //
58 // @todo derive this from Mapper_ (https://github.com/SophistSolutions/Stroika/issues/1230), or from UPnP's own schema
59 // (DeviceDescriptionSchema), rather than spelling it out: what only XML says - item names, the namespace,
60 // skipping - would be all that is left
61 const Variant::XML::Binding& Binding_ ()
62 {
64 static const Binding kBinding_ = [] () {
65 constexpr auto kSkip = Binding::UndeclaredMembers::eSkip;
66 auto text = [] () { return Binding{.fType = VariantValue::eString}; };
67 auto size = [] () { return Binding{.fType = VariantValue::eUnsignedInteger}; };
68 auto list = [] (const Binding& item) {
69 return Binding{.fType = VariantValue::eArray,
70 .fUndeclaredMembers = Binding::UndeclaredMembers::eSkip,
71 .fItems = MakeSharedPtr<Binding> (item)};
72 };
73 Binding icon{.fName = "icon"sv,
74 .fType = VariantValue::eMap,
75 .fMembers = {{"mimetype"sv, text ()}, {"width"sv, size ()}, {"height"sv, size ()}, {"depth"sv, size ()}, {"url"sv, text ()}},
76 .fUndeclaredMembers = kSkip};
77 Binding service{
78 .fName = "service"sv,
79 .fType = VariantValue::eMap,
80 .fMembers = {{"serviceType"sv, text ()}, {"serviceId"sv, text ()}, {"SCPDURL"sv, text ()}, {"controlURL"sv, text ()}, {"eventSubURL"sv, text ()}},
81 .fUndeclaredMembers = kSkip};
82 // a <deviceList> of embedded devices comes after serviceList - skipped, as DeviceDescription has no field for one
83 Binding device{.fType = VariantValue::eMap,
84 .fMembers = {{"deviceType"sv, text ()},
85 {"friendlyName"sv, text ()},
86 {"manufacturer"sv, text ()},
87 {"manufacturerURL"sv, text ()},
88 {"modelDescription"sv, text ()},
89 {"modelName"sv, text ()},
90 {"modelNumber"sv, text ()},
91 {"modelURL"sv, text ()},
92 {"serialNumber"sv, text ()},
93 {"UDN"sv, text ()},
94 {"UPC"sv, text ()},
95 {"iconList"sv, list (icon)},
96 {"serviceList"sv, list (service)},
97 {"presentationURL"sv, text ()}},
98 .fUndeclaredMembers = kSkip};
99 Binding specVersion{.fType = VariantValue::eMap, .fMembers = {{"major"sv, text ()}, {"minor"sv, text ()}}, .fUndeclaredMembers = kSkip};
100 Binding root{.fName = XML::NameWithNamespace{kDeviceNamespace_, "root"sv},
101 .fType = VariantValue::eMap,
102 .fMembers = {{"specVersion"sv, specVersion}, {"URLBase"sv, text ()}, {"device"sv, device}},
103 .fUndeclaredMembers = kSkip};
104 Ensure (root.IsComplete ()); // so what Serialize writes, DeSerialize reads back exactly
105 return root;
106 }();
107 return kBinding_;
108 }
109}
110
111/*
112 ********************************************************************************
113 ************************* DeviceDescription::Icon ******************************
114 ********************************************************************************
115 */
117{
119 sb << "{"sv;
120 sb << "mimeType: "sv << fMimeType;
121 sb << ", horizontalPixels: "sv << fHorizontalPixels;
122 sb << ", nerticalPixels: "sv << fVerticalPixels;
123 sb << ", colorDepth : "sv << fColorDepth;
124 sb << ", URL: "sv << fURL;
125 sb << "}"sv;
126 return sb;
127}
128
129/*
130 ********************************************************************************
131 ************************ DeviceDescription::Service ****************************
132 ********************************************************************************
133 */
135{
137 sb << "{"sv;
138 sb << "Service-Type: "sv << fServiceType;
139 sb << ", Service-ID: "sv << fServiceID;
140 sb << ", SCPD-URL: "sv << fSCPDURL;
141 sb << ", Control-URL: "sv << fControlURL;
142 sb << ", Event-Sub-URL: "sv << fEventSubURL;
143 sb << "}"sv;
144 return sb;
145}
146
147/*
148 ********************************************************************************
149 ******************************* DeviceDescription ******************************
150 ********************************************************************************
151 */
153{
155 sb << "{"sv;
156 if (fPresentationURL) {
157 sb << "Presentation-URL: "sv << fPresentationURL;
158 }
159 if (fURLBase) {
160 sb << ", URL-Base: "sv << *fURLBase;
161 }
162 sb << ", Device-Type: "sv << fDeviceType;
163 sb << ", Manufacture-Name: "sv << fManufactureName;
164 sb << ", Friendly-Name: "sv << fFriendlyName;
165 if (fManufacturingURL) {
166 sb << ", Manufacturing-URL: "sv << fManufacturingURL;
167 }
168 if (fModelDescription) {
169 sb << ", Model-Description: "sv << fModelDescription;
170 }
171 sb << ", Model-Name: "sv << fModelName;
172 if (fModelNumber) {
173 sb << ", Model-Number: "sv << fModelNumber;
174 }
175 if (fModelURL) {
176 sb << ", Model-URL: "sv << *fModelURL;
177 }
178 if (fSerialNumber) {
179 sb << ", Serial-Number: "sv << fSerialNumber;
180 }
181 sb << ", UDN: "sv << fUDN;
182 if (fUPC) {
183 sb << ", UPC: "sv << fUPC;
184 }
185 if (fIcons) {
186 sb << ", Icons: "sv << fIcons;
187 }
188 if (fServices) {
189 sb << ", Services: "sv << fServices;
190 }
191 sb << "}"sv;
192 return sb;
193}
194
196{
197 const URI base = fURLBase.value_or (descriptionURL);
198 auto resolve = [&] (const URI& u) { return u ? base.Combine (u) : u; }; // an empty one stays empty: Combine gives the base
199 DeviceDescription result = *this;
200 result.fURLBase = nullopt; // nothing is relative to it now
201 for (optional<URI>* u : {&result.fPresentationURL, &result.fManufacturingURL, &result.fModelURL}) {
202 if (*u) {
203 **u = resolve (**u);
204 }
205 }
206 if (fIcons) {
207 result.fIcons = fIcons->Map<Collection<Icon>> ([&] (Icon i) {
208 i.fURL = resolve (i.fURL);
209 return i;
210 });
211 }
212 if (fServices) {
213 result.fServices = fServices->Map<Collection<Service>> ([&] (Service s) {
214 s.fSCPDURL = resolve (s.fSCPDURL);
215 s.fControlURL = resolve (s.fControlURL);
216 s.fEventSubURL = resolve (s.fEventSubURL);
217 return s;
218 });
219 }
220 return result;
221}
222
223ObjectVariantMapper DeviceDescription::mkMapper_ ()
224{
225 ObjectVariantMapper mapper;
226
227 mapper.AddClass<Icon> ({
228 {"mimetype"sv, &Icon::fMimeType},
229 {"width"sv, &Icon::fHorizontalPixels},
230 {"height"sv, &Icon::fVerticalPixels},
231 {"depth"sv, &Icon::fColorDepth},
232 {"url"sv, &Icon::fURL},
233 });
235 mapper.AddCommonType<optional<Collection<Icon>>> ();
236
237 mapper.AddClass<Service> ({
238 {"serviceType"sv, &Service::fServiceType},
239 {"serviceId"sv, &Service::fServiceID},
240 {"SCPDURL"sv, &Service::fSCPDURL},
241 {"controlURL"sv, &Service::fControlURL},
242 {"eventSubURL"sv, &Service::fEventSubURL},
243 });
245 mapper.AddCommonType<optional<Collection<Service>>> ();
246
247 mapper.AddCommonType<optional<String>> ();
248 mapper.AddCommonType<optional<URI>> ();
249
250 mapper.AddClass<DeviceDescription> ({
251 {"URLBase"sv, &DeviceDescription::fURLBase},
252 {"presentationURL"sv, &DeviceDescription::fPresentationURL},
253 {"deviceType"sv, &DeviceDescription::fDeviceType},
254 {"manufacturer"sv, &DeviceDescription::fManufactureName},
255 {"friendlyName"sv, &DeviceDescription::fFriendlyName},
256 {"manufacturerURL"sv, &DeviceDescription::fManufacturingURL},
257 {"modelDescription"sv, &DeviceDescription::fModelDescription},
258 {"modelName"sv, &DeviceDescription::fModelName},
259 {"modelNumber"sv, &DeviceDescription::fModelNumber},
260 {"modelURL"sv, &DeviceDescription::fModelURL},
261 {"serialNumber"sv, &DeviceDescription::fSerialNumber},
262 {"UDN"sv, &DeviceDescription::fUDN},
263 {"UPC"sv, &DeviceDescription::fUPC},
264 {"iconList"sv, &DeviceDescription::fIcons},
265 {"serviceList"sv, &DeviceDescription::fServices},
266 });
267 return mapper;
268};
269
270/*
271 ********************************************************************************
272 ********************************* UPnP::Serialize ******************************
273 ********************************************************************************
274 */
275Memory::BLOB UPnP::Serialize (const DeviceDescription& dd, [[maybe_unused]] const DeviceDescription::Options& options)
276{
277 // DeviceDescription is flat, where the document is not: <URLBase> is <device>'s sibling, in <root>
278 VVMap_ device = Mapper_ ().FromObject (dd).As<VVMap_> ();
279 // 1.0, not 1.1, on purpose - @see this framework's ReadMe.md
280 VVMap_ root{{"specVersion"sv, VariantValue{VVMap_{{"major"sv, VariantValue{"1"sv}}, {"minor"sv, VariantValue{"0"sv}}}}}};
281 if (optional<VariantValue> urlBase = device.Lookup ("URLBase"sv)) {
282 root.Add ("URLBase"sv, *urlBase);
283 device.Remove ("URLBase"sv);
284 }
285 root.Add ("device"sv, VariantValue{device});
286 // the Writer writes the text itself, with no XML provider - options' provider only checks it
287 static const Variant::XML::Writer kWriter_{{.fBinding = Binding_ ()}};
288 Memory::BLOB result = kWriter_.WriteAsBLOB (VariantValue{root});
289#if qStroika_Foundation_DataExchange_XML_SupportParsing and qStroika_Foundation_DataExchange_XML_SupportSchema and qStroika_Foundation_DataExchange_XML_SupportDOM
290 if (options.fValidateWrite) {
291 XML::SAXParse (result.As<Streams::InputStream::Ptr<byte>> (), nullptr, DeviceDescriptionSchema (options.fXMLOptions), nullptr,
292 options.fXMLOptions);
293 }
294#endif
295 return result;
296}
297
298/*
299 ********************************************************************************
300 ******************************* UPnP::DeSerialize ******************************
301 ********************************************************************************
302 */
303DeviceDescription UPnP::DeSerialize (const Memory::BLOB& b, const DeviceDescription::Options& options)
304{
305#if USE_NOISY_TRACE_IN_THIS_MODULE_
306 DbgTrace ("xml data: {}"_f, Streams::BinaryToText::Reader::New (b).ReadAll ());
307#endif
308 // leniently: devices in the field are loose (@see DeviceDescription::Options::fValidateReadAgainst)
309 const Variant::XML::Reader reader{
310 {.fBinding = Binding_ (), .fXMLOptions = options.fXMLOptions, .fValidateAgainst = options.fValidateReadAgainst, .fLenient = true}};
311 VVMap_ root = reader.Read (b.As<Streams::InputStream::Ptr<byte>> ()).As<VVMap_> ();
312 VVMap_ device = root.LookupValue ("device"sv).As<VVMap_> ();
313 // an empty <URLBase> means none (UPnP Device Architecture 1.0); one that is not a URL is taken for none too - ignored, as
314 // every <URLBase> was before Stroika v3.0d25 - rather than failing the whole description
315 if (optional<VariantValue> urlBase = root.Lookup ("URLBase"sv)) {
316 if (String u = urlBase->As<String> ().Trim (); not u.empty ()) {
317 try {
318 (void)URI::Parse (u);
319 device.Add ("URLBase"sv, VariantValue{u});
320 }
321 catch (...) {
322 DbgTrace ("Ignoring <URLBase>{}</URLBase>, which is not a URL: {}"_f, u, current_exception ());
323 }
324 }
325 }
326 DeviceDescription deviceDescription = Mapper_ ().ToObject<DeviceDescription> (VariantValue{device});
327#if USE_NOISY_TRACE_IN_THIS_MODULE_
328 DbgTrace ("deviceDescription: {}"_f, deviceDescription);
329#endif
330 return deviceDescription;
331}
#define DbgTrace
Definition Trace.h:327
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1651
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
nonvirtual void AddCommonType(ARGS &&... args)
Read XML as a VariantValue - the inverse of Variant::XML::Writer, whose documentation has the shape.
Write a VariantValue as XML - an object's member is an element, an array is repeated elements.
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
static URI Parse(const String &rawURL)
Definition URI.cpp:103
nonvirtual URI Combine(const URI &overridingURI) const
Combine overridingURI possibly relative url with this base url, to produce a new URI.
Definition URI.cpp:300
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
Ptr New(const InputStream::Ptr< byte > &src, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...
How a VariantValue is to be written as XML, and read back, where the default shape (.
Definition Binding.h:88
An element's or attribute's name (local-name, not including a prefix), plus the namespace that name i...
Definition Namespace.h:63
What Serialize and DeSerialize of a device description are told - defaulted, so a caller says only wh...
Foundation::DataExchange::XML::Options fXMLOptions
The XML provider that reads, and that checks what is written - by default, Stroika's default provider...
Foundation::DataExchange::XML::Schema::Ptr fValidateReadAgainst
Check a description being read against this schema - DeviceDescriptionSchema - throwing where it does...
nonvirtual DeviceDescription Resolve(const URI &descriptionURL) const
This description with every URL absolute - each relative one resolved against fURLBase,...
static const Foundation::Execution::LazyInitialized< Foundation::DataExchange::ObjectVariantMapper > kMapper