Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
DeviceDescription.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Frameworks_UPnP_DeviceDescription_h_
5#define _Stroika_Frameworks_UPnP_DeviceDescription_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
10#include "Stroika/Foundation/Common/Common.h"
11#include "Stroika/Foundation/Containers/Collection.h"
13#include "Stroika/Foundation/DataExchange/ObjectVariantMapper.h"
16#include "Stroika/Foundation/Execution/LazyInitialized.h"
19
20#include "Stroika/Frameworks/UPnP/Device.h"
21
22/*
23 * TODO:
24 * @todo Add image support
25 */
26
27namespace Stroika::Frameworks::UPnP {
28
29 using namespace Stroika::Foundation;
30
34 using IO::Network::URI;
35
36 /**
37 * high level device description - from ssdp. This is the BASIC device info
38 * which appears to the XML file (pointed to by Device location field).
39 *
40 * This is based on http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf, page 43-46 apx
41 */
43 /**
44 * The description's <URLBase>, if it has one: what its relative URLs - each service's, the icons', fPresentationURL -
45 * are relative to. Without one, they are relative to the URL the description was read from. So each resolves as
46 * fURLBase.value_or (descriptionURL).Combine (url) - @see Resolve, which does that for every one.
47 *
48 * An older (UPnP 1.0) device may send one, and a control point must then apply it; UPnP 1.1 forbids a device to send
49 * one (section 2.3), so a device built on Stroika leaves it unset. Read, an empty one - or one that is not a URL - is
50 * taken for none.
51 */
52 optional<URI> fURLBase;
53 optional<URI> fPresentationURL;
54 String fDeviceType; // http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf - <deviceType> - Page 44
55 String fManufactureName;
56 String fFriendlyName;
57 optional<URI> fManufacturingURL;
58 optional<String> fModelDescription;
59 String fModelName;
60 optional<String> fModelNumber;
61 optional<URI> fModelURL;
62 optional<String> fSerialNumber;
63 /**
64 * Unique Device Name. Universally-unique identifier for the device, whether root or embedded.
65 * MUST be the same over time for a specific device instance (i.e., MUST survive reboots).MUST match the
66 * field value of the NT header field in device discovery messages.MUST match the prefix of the USN
67 * header field in all discovery messages.
68 */
70
71 /*
72 * OPTIONAL. Universal Product Code. 12-digit, all-numeric code that identifies the consumer package.
73 * Managed by the Uniform Code Council. Specified by UPnP vendor. Single UPC.
74 */
75 optional<String> fUPC;
76
77 /**
78 * @todo made several fields optional - so Icon default constructible - but according to
79 * https://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf these sb required (so not optional).
80 * @todo FIX
81 */
82 struct Icon {
83 InternetMediaType fMimeType{};
84 uint16_t fHorizontalPixels{16};
85 uint16_t fVerticalPixels{16};
86 uint16_t fColorDepth{8};
87 URI fURL{}; // url to the icon image file
88
89 /**
90 * @see Characters::ToString ();
91 */
92 nonvirtual String ToString () const;
93 };
94 optional<Collection<Icon>> fIcons;
95
96 /**
97 * One of the device's services: what it is, and where its description, its actions and its events are - each URL
98 * relative to the device description's own (UPnP Device Architecture 1.1, section 2.3; an older device's may be
99 * absolute, or relative to its fURLBase) - @see DeviceDescription::Resolve, which makes them absolute.
100 */
101 struct Service {
102 String fServiceType; // e.g. urn:schemas-upnp-org:service:serviceType:v
103 String fServiceID; // e.g. urn:upnp-org:serviceId:serviceID
104 URI fSCPDURL; // URL to service description
105 URI fControlURL; // URL for control: its actions, as SOAP requests
106 URI fEventSubURL; // URL for eventing (subscribing to its state's changes); empty if it has no evented state
107
108 /**
109 * @see Characters::ToString ();
110 */
111 nonvirtual String ToString () const;
112 };
113 optional<Collection<Service>> fServices;
114
115 /**
116 * \brief What Serialize and DeSerialize of a device description are told - defaulted, so a caller says only what it
117 * wants different
118 *
119 * By default what Serialize writes is checked against UPnP's schema, and what DeSerialize reads is not.
120 *
121 * \par Example Usage
122 * \code
123 * // read leniently - the default
124 * DeviceDescription dd = UPnP::DeSerialize (fetched);
125 *
126 * // ... or be told a description is malformed, rather than silently getting half of one
127 * const DeviceDescription::Options kStrict_{.fValidateReadAgainst = UPnP::DeviceDescriptionSchema ()};
128 * DeviceDescription checked = UPnP::DeSerialize (fetched, kStrict_);
129 * \endcode
130 */
131 struct Options {
132 /**
133 * \brief The XML provider that reads, and that checks what is written - by default, Stroika's default provider;
134 * nearly always left as is (@see XML::Options)
135 *
136 * \note A UPnP option, not a process-wide one, so two libraries that each speak UPnP need not agree.
137 */
139
140 /**
141 * \brief Check a description being read against this schema - DeviceDescriptionSchema - throwing where it does
142 * not hold; nullptr, the default, checks nothing
143 *
144 * Off by default: UPnP devices in the field are loose - a vendor's own elements in no namespace, elements out of
145 * the order the schema fixes, required ones missing - so a control point that checked would see fewer devices
146 * than one that did not, and which to be is the application's choice, not the library's. Reading is lenient to
147 * match (@see ServiceDescription_Others_); this is how a caller that would rather be told says so.
148 *
149 * A schema rather than a flag, so a program that never checks what it reads does not carry it.
150 */
152
153 /**
154 * \brief Check what Serialize writes against DeviceDescriptionSchema, throwing BadFormatException where it does
155 * not hold - true, the default
156 *
157 * On by default, unlike reading: what a device writes is its own, so a description that does not hold - a device
158 * with no services, say, where device-1-0 requires a <service> - is a mistake better found by its author than by
159 * its control points. Turn it off to serve one that knowingly departs from the schema, or to save what each check
160 * costs: Serialize parses what it wrote, every call (the schema is compiled once). In a build with no XML parser,
161 * nothing is checked.
162 */
163 bool fValidateWrite{true};
164 };
165
166 DeviceDescription () = default;
167
168 /**
169 * \brief This description with every URL absolute - each relative one resolved against fURLBase, or else descriptionURL
170 * (the URL it was read from); fURLBase is cleared, as nothing is relative to it any more
171 *
172 * Its URLs are fPresentationURL, fManufacturingURL, fModelURL, each icon's fURL, and each service's fSCPDURL,
173 * fControlURL and fEventSubURL. An empty one - the fEventSubURL of a service with no events - stays empty, where
174 * URI::Combine would make it the base itself (RFC 3986, section 5.2.2).
175 *
176 * Throws as URI::Combine does - for a base with no scheme.
177 *
178 * \par Example Usage
179 * \code
180 * DeviceDescription dd = UPnP::DeSerialize (Connection::New ().GET (location).GetData ()).Resolve (location);
181 * \endcode
182 */
183 nonvirtual DeviceDescription Resolve (const URI& descriptionURL) const;
184
185 /**
186 * Mapper to facilitate serialization
187 */
189
190 /**
191 * @see Characters::ToString ();
192 */
193 nonvirtual String ToString () const;
194
195 private:
197 };
199
200 /**
201 * \brief The device's description XML: what its LOCATION serves - as UPnP's XML (text/xml), UTF-8
202 *
203 * The text is written directly (Variant::XML::Writer); an XML parser only checks it, as by default it does - throwing
204 * BadFormatException where it does not hold (@see DeviceDescription::Options::fValidateWrite).
205 */
206 Memory::BLOB Serialize (const DeviceDescription& dd, const DeviceDescription::Options& options = {});
207
208 /**
209 * \brief A device description read - what Serialize writes, and any other's, leniently: what it does not know - a
210 * vendor's own elements, an embedded <deviceList> - is skipped, an element given twice keeps its last, a URL
211 * that is not one reads as an empty URL and an icon size that is not a number as its default. Throws if it is
212 * not XML, its document element is not <root>, or the build has no XML parser
213 * (qStroika_Foundation_DataExchange_XML_SupportParsing). @see Variant::XML::Reader::Options::fLenient
214 */
215 DeviceDescription DeSerialize (const Memory::BLOB& b, const DeviceDescription::Options& options = {});
216
217#if qStroika_Foundation_DataExchange_XML_SupportParsing and qStroika_Foundation_DataExchange_XML_SupportSchema and qStroika_Foundation_DataExchange_XML_SupportDOM
218 /**
219 * \brief UPnP's own device description schema, compiled by the provider o names - what Serialize checks what it
220 * writes against (DeviceDescription::Options::fValidateWrite), and what to give fValidateReadAgainst
221 *
222 * Compiled once per provider, the first time that provider asks, and kept - so a call after the first is cheap.
223 *
224 * It lives in a translation unit of its own (Stroika is a static library), so its ~11K is linked only into a program
225 * that calls it - as Serialize does, to check what it writes.
226 *
227 * \note Give it the matching Options' fXMLOptions: a schema and a parser from different providers do work together,
228 * but slowly (@see XML::SAXParse).
229 *
230 * \note The schema is https://upnp.org/schemas/device-1-0.xsd as it stood 2026-10-13.
231 */
233#endif
234
235}
236
237/*
238 ********************************************************************************
239 ***************************** Implementation Details ***************************
240 ********************************************************************************
241 */
242#include "DeviceDescription.inl"
243
244#endif /*_Stroika_Frameworks_UPnP_DeviceDescription_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
value-object, where the value construction is delayed until first needed (can be handy to avoid c++ i...
How XML is read and written - for now, with which provider - taken last by each XML function (DOM::Do...
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...
bool fValidateWrite
Check what Serialize writes against DeviceDescriptionSchema, throwing BadFormatException where it doe...
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