Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Action.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
7#include "Stroika/Foundation/Characters/String2Int.h"
10#include "Stroika/Foundation/DataExchange/BadFormatException.h"
12#include "Stroika/Foundation/DataExchange/XML/WriterUtils.h"
13#include "Stroika/Foundation/Execution/Throw.h"
14#include "Stroika/Foundation/IO/Network/Transfer/Connection.h"
15#if qStroika_Foundation_DataExchange_XML_SupportDOM
16#include "Stroika/Foundation/DataExchange/XML/DOM.h"
17#endif
18
19#include "Action.h"
20
21using namespace Stroika::Foundation;
24
25using namespace Stroika::Frameworks;
26using namespace Stroika::Frameworks::UPnP;
28
30
31namespace {
32 // UPnP's arguments as SOAP has them - each its name and text - and back
33 GenericSOAP_::Arguments ToGeneric_ (const Arguments& arguments)
34 {
36 for (const Argument& a : arguments) {
37 result.Append (a);
38 }
39 return result;
40 }
41 Arguments FromGeneric_ (const GenericSOAP_::Arguments& arguments)
42 {
43 Arguments result;
44 for (const Common::KeyValuePair<String, String>& a : arguments) {
45 result.Append (Argument{a});
46 }
47 return result;
48 }
49
50 // options as UPnP writes with them: encodingStyle on the Envelope, whatever they say - UPnP Device Architecture 1.1
51 // (Table 3-1) prohibits it on the Body, and requires it on the Envelope of a UPnP 1.0 device's or control point's messages
52 GenericSOAP_::Options UPnPWriting_ (const GenericSOAP_::Options& options)
53 {
54 GenericSOAP_::Options result = options;
55 result.fEncodingStyleAttribute = GenericSOAP_::EncodingStyleAttribute::eOnEnvelope;
56 return result;
57 }
58
59 // a UPnPError's own namespace (UPnP Device Architecture 1.1, section 3.2.2)
60 constexpr string_view kUPnPErrorNamespace_ = "urn:schemas-upnp-org:control-1-0"sv;
61
62 // the errorDescription UPnP gives each of its error codes
63 String StandardDescription_ (unsigned int errorCode)
64 {
65 switch (errorCode) {
67 return "Invalid Action"sv;
68 case ActionError::kInvalidArgs:
69 return "Invalid Args"sv;
70 case ActionError::kActionFailed:
71 return "Action Failed"sv;
72 case ActionError::kArgumentValueInvalid:
73 return "Argument Value Invalid"sv;
74 case ActionError::kArgumentValueOutOfRange:
75 return "Argument Value Out of Range"sv;
76 case ActionError::kOptionalActionNotImplemented:
77 return "Optional Action Not Implemented"sv;
78 case ActionError::kOutOfMemory:
79 return "Out of Memory"sv;
80 case ActionError::kHumanInterventionRequired:
81 return "Human Intervention Required"sv;
82 case ActionError::kStringArgumentTooLong:
83 return "String Argument Too Long"sv;
84 default:
85 return String{};
86 }
87 }
88}
89
90/*
91 ********************************************************************************
92 ************************** UPnP::SOAP::ActionRequest ***************************
93 ********************************************************************************
94 */
96{
97 return "\"{}#{}\""_f(fServiceType, fAction);
98}
99
101{
102 StringBuilder sb;
103 sb << "{"sv;
104 sb << "Service-Type: "sv << fServiceType;
105 sb << ", Action: "sv << fAction;
106 sb << ", Arguments: "sv << fArguments;
107 sb << "}"sv;
108 return sb;
109}
110
111/*
112 ********************************************************************************
113 ************************* UPnP::SOAP::ActionResponse ***************************
114 ********************************************************************************
115 */
117{
118 StringBuilder sb;
119 sb << "{"sv;
120 sb << "Service-Type: "sv << fServiceType;
121 sb << ", Action: "sv << fAction;
122 sb << ", Arguments: "sv << fArguments;
123 sb << "}"sv;
124 return sb;
125}
126
127/*
128 ********************************************************************************
129 *************************** UPnP::SOAP::ActionError ****************************
130 ********************************************************************************
131 */
132/*
133 * Deliberately NOT checked against UPnP's control-1-0.xsd, where the two descriptions are checked against theirs
134 * (@see UPnP::DeviceDescriptionSchema): an unsigned int and an escaped string, in two fixed elements, cannot come out
135 * invalid. The published schema would refuse it anyway - it omits the elementFormDefault="qualified" that device-1-0
136 * and service-1-0 both carry, so it wants errorCode and errorDescription in NO namespace, where UPnP Device
137 * Architecture's own example has them inheriting UPnPError's. Adding that one attribute makes what this writes hold
138 * (measured 2026-10-13).
139 */
141{
142 return GenericSOAP_::Fault{.fFaultCode = GenericSOAP_::Fault::kClient,
143 .fFaultString = "UPnPError"sv,
144 .fDetail = "<UPnPError xmlns=\"{}\"><errorCode>{}</errorCode><errorDescription>{}</errorDescription></UPnPError>"_f(
145 String{kUPnPErrorNamespace_}, fErrorCode, DataExchange::XML::QuoteForXMLW (fErrorDescription))};
146}
147
148optional<ActionError> ActionError::FromFault ([[maybe_unused]] const GenericSOAP_::Fault& fault, [[maybe_unused]] const GenericSOAP_::Options& options)
149{
150#if qStroika_Foundation_DataExchange_XML_SupportDOM
151 if (not fault.fDetail) {
152 return nullopt;
153 }
154 using namespace DataExchange::XML::DOM;
155 // the detail's elements - perhaps more than one - in an element of their own, so a document
156 Document::Ptr doc = Document::New ("<detail>{}</detail>"_f(*fault.fDetail), nullptr, options.fXMLOptions); // kept: its elements do not keep it
157 for (const Element::Ptr& e : doc.GetRootElement ().GetChildElements ()) {
158 if (e.GetName ().fName == "UPnPError"sv) {
159 optional<String> code;
160 ActionError result;
161 for (const Element::Ptr& c : e.GetChildElements ()) {
162 if (c.GetName ().fName == "errorCode"sv) {
163 code = c.GetValue ().Trim ();
164 }
165 else if (c.GetName ().fName == "errorDescription"sv) {
166 result.fErrorDescription = c.GetValue ();
167 }
168 }
169 if (code) {
170 result.fErrorCode = String2Int<unsigned int> (*code);
171 return result;
172 }
173 }
174 }
175#endif
176 return nullopt;
177}
178
180{
181 StringBuilder sb;
182 sb << "{"sv;
183 sb << "Error-Code: "sv << fErrorCode;
184 sb << ", Error-Description: "sv << fErrorDescription;
185 sb << "}"sv;
186 return sb;
187}
188
189/*
190 ********************************************************************************
191 ************************* UPnP::SOAP::ActionException **************************
192 ********************************************************************************
193 */
195 : Execution::Exception<runtime_error>{"UPnP error {}: {}"_f(error.fErrorCode, error.fErrorDescription)}
196 , fError_{error}
197{
198}
199
200ActionException::ActionException (unsigned int errorCode)
201 : ActionException{ActionError{.fErrorCode = errorCode, .fErrorDescription = StandardDescription_ (errorCode)}}
202{
203}
204
205ActionError ActionException::GetError () const
206{
207 return fError_;
208}
209
210/*
211 ********************************************************************************
212 ****************************** UPnP::SOAP::Serialize ***************************
213 ********************************************************************************
214 */
216{
218 GenericSOAP_::Request{.fNamespace = request.fServiceType, .fMethod = request.fAction, .fArguments = ToGeneric_ (request.fArguments)},
219 UPnPWriting_ (options));
220}
221
223{
225 GenericSOAP_::Response{.fNamespace = response.fServiceType, .fMethod = response.fAction, .fArguments = ToGeneric_ (response.fArguments)},
226 UPnPWriting_ (options));
227}
228
230{
231 return GenericSOAP_::Serialize (error.AsFault (), UPnPWriting_ (options));
232}
233
234/*
235 ********************************************************************************
236 ***************************** UPnP::SOAP::DeSerialize **************************
237 ********************************************************************************
238 */
239void SOAP::DeSerialize (const Memory::BLOB& b, ActionRequest* request, const GenericSOAP_::Options& options)
240{
241 RequireNotNull (request);
243 GenericSOAP_::DeSerialize (b, &r, options);
244 *request = ActionRequest{.fServiceType = r.fNamespace, .fAction = r.fMethod, .fArguments = FromGeneric_ (r.fArguments)};
245}
246
247void SOAP::DeSerialize (const Memory::BLOB& b, ActionResponse* response, const GenericSOAP_::Options& options)
248{
249 RequireNotNull (response);
251 GenericSOAP_::DeSerialize (b, &r, options);
252 *response = ActionResponse{.fServiceType = r.fNamespace, .fAction = r.fMethod, .fArguments = FromGeneric_ (r.fArguments)};
253}
254
256{
257 RequireNotNull (error);
259 GenericSOAP_::DeSerialize (b, &f, options);
260 optional<ActionError> e = ActionError::FromFault (f, options);
261 if (not e) {
262 Execution::Throw (BadFormatException{"not a UPnP error: a fault with no UPnPError"sv});
263 }
264 *error = *e;
265}
266
267/*
268 ********************************************************************************
269 ******************************* UPnP::SOAP::Invoke *****************************
270 ********************************************************************************
271 */
272ActionResponse SOAP::Invoke (const URI& controlURL, const ActionRequest& request, const GenericSOAP_::Options& options)
273{
274 return Invoke (IO::Network::Transfer::Connection::New (), controlURL, request, options);
275}
276
278 const GenericSOAP_::Options& options)
279{
280 try {
282 connection, controlURL, request.GetSOAPAction (),
283 GenericSOAP_::Request{.fNamespace = request.fServiceType, .fMethod = request.fAction, .fArguments = ToGeneric_ (request.fArguments)},
284 UPnPWriting_ (options));
285 return ActionResponse{.fServiceType = r.fNamespace, .fAction = r.fMethod, .fArguments = FromGeneric_ (r.fArguments)};
286 }
287 catch (const GenericSOAP_::FaultException& e) {
288 if (optional<ActionError> error = ActionError::FromFault (e.GetFault (), options)) {
290 }
292 }
293}
294
295/*
296 ********************************************************************************
297 **************************** UPnP::SOAP::HandleAction **************************
298 ********************************************************************************
299 */
300void SOAP::HandleAction (WebServer::Message& m, const String& serviceType, const ActionHandler& doAction, const GenericSOAP_::Options& options)
301{
303 m,
304 [&] (const GenericSOAP_::Request& r) {
305 try {
306 if (r.fNamespace != serviceType) {
307 Execution::Throw (ActionException{ActionError::kInvalidAction}); // another service's
308 }
310 .fMethod = r.fMethod,
311 .fArguments = ToGeneric_ (doAction (ActionRequest{
312 .fServiceType = r.fNamespace, .fAction = r.fMethod, .fArguments = FromGeneric_ (r.fArguments)}))};
313 }
314 catch (const ActionException& e) {
315 Execution::Throw (GenericSOAP_::FaultException{e.GetError ().AsFault ()});
316 }
317 },
318 UPnPWriting_ (options));
319}
320
321void SOAP::HandleAction (WebServer::Message& m, const String& serviceType, const ServiceDescription& description,
322 const ActionHandler& doAction, const GenericSOAP_::Options& options)
323{
325 m, serviceType,
326 [&] (const ActionRequest& request) {
327 CheckRequest (request, description);
328 return doAction (request);
329 },
330 options);
331}
332
333void SOAP::HandleAction (WebServer::Message& m, const String& serviceType, const ServiceDescription& description,
334 const ActionHandlers& actions, const GenericSOAP_::Options& options)
335{
336 Require (actions.Keys ().All (
337 [&] (const String& a) { return description.fActions.Any ([&] (const ServiceDescription::Action& d) { return d.fName == a; }); }));
338 // CheckRequest has answered an action not described (401), so this one is: done by its handler, or not done here
340 m, serviceType, description,
341 [&] (const ActionRequest& request) -> Arguments {
342 if (optional<ActionHandler> h = actions.Lookup (request.fAction)) {
343 return (*h) (request);
344 }
345 Execution::Throw (ActionException{ActionError::kOptionalActionNotImplemented});
346 },
347 options);
348}
349
350/*
351 ********************************************************************************
352 **************************** UPnP::SOAP::CheckRequest **************************
353 ********************************************************************************
354 */
355namespace {
356 // whether value is in range: each end read as value's data type, and compared as its C++ type - true if either end is not one
357 bool InRange_ (const DataTypes::Value& value, const ServiceDescription::AllowedValueRange& range, DataTypes::DataType t)
358 {
359 optional<DataTypes::Value> lo = DataTypes::FromText (range.fMinimum, t);
360 optional<DataTypes::Value> hi = DataTypes::FromText (range.fMaximum, t);
361 if (not lo or not hi) {
362 return true;
363 }
364 return visit (
365 [&]<typename T> (const T& v) {
366 if constexpr (is_arithmetic_v<T> and not same_as<T, bool>) {
367 return get<T> (*lo) <= v and v <= get<T> (*hi);
368 }
369 else {
370 return true; // a range is a number's (UPnP Device Architecture 1.1, section 2.5)
371 }
372 },
373 value);
374 }
375}
376
377void SOAP::CheckRequest (const ActionRequest& request, const ServiceDescription& description)
378{
379 using SD = ServiceDescription;
380 optional<SD::Action> action = description.fActions.First ([&] (const SD::Action& a) { return a.fName == request.fAction; });
381 if (not action) {
383 }
384 // its in arguments, in its order - each named as described, with a value of its state variable's type, and one it allows
385 auto in = action->fArguments.Where ([] (const SD::Argument& a) { return a.fDirection == SD::Argument::Direction::eIn; });
386 if (in.size () != request.fArguments.size ()) {
387 Execution::Throw (ActionException{ActionError::kInvalidArgs});
388 }
389 auto given = request.fArguments.begin ();
390 for (const SD::Argument& described : in) {
391 const Argument a = *given;
392 ++given;
393 if (a.fKey != described.fName) {
394 Execution::Throw (ActionException{ActionError::kInvalidArgs}); // misnamed, or out of order
395 }
396 optional<SD::StateVariable> v =
397 description.fStateVariables.First ([&] (const SD::StateVariable& sv) { return sv.fName == described.fRelatedStateVariable; });
398 optional<DataTypes::DataType> t = v ? Common::DefaultNames<DataTypes::DataType>{}.PeekValue (v->fDataType.As<wstring> ().c_str ()) : nullopt;
399 if (not t) {
400 continue; // a type not UPnP's - a vendor's - or none described: not checked
401 }
402 optional<DataTypes::Value> value = DataTypes::FromText (a.fValue, *t);
403 if (not value) {
404 Execution::Throw (ActionException{ActionError::kInvalidArgs});
405 }
406 if ((not v->fAllowedValues.empty () and not v->fAllowedValues.Contains (a.fValue)) or
407 (v->fAllowedValueRange and not InRange_ (*value, *v->fAllowedValueRange, *t))) {
408 Execution::Throw (ActionException{ActionError::kArgumentValueOutOfRange});
409 }
410 }
411}
#define RequireNotNull(p)
Definition Assertions.h:350
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
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual Iterable< key_type > Keys() const
Definition Mapping.inl:111
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< value_type > First() const
Definition Sequence.inl:749
nonvirtual void Append(ArgByValueType< value_type > item)
Definition Sequence.inl:615
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
An ActionError, thrown: Invoke throws the one a service answers with, and a HandleAction doAction thr...
Definition Action.h:223
A Fault, thrown: Invoke throws the one a server answers with, and a HandleRequest handler throws one ...
Definition SOAP.h:184
void Throw(T &&e2Throw)
like the builtin C++ 'throw', but stamps in the current Activity context and does helpful,...
Definition Throw.inl:54
variant< bool, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double, Foundation::Characters::Character, String, Foundation::Time::Date, Foundation::Time::DateTime, Foundation::Time::TimeOfDay, Foundation::Memory::BLOB, Foundation::IO::Network::URI, Foundation::Common::GUID > Value
A value of any of UPnP's data types, as its C++ type (the table above) - for code that learns a value...
Definition DataTypes.h:111
DataType
UPnP's data types - each named, as a service's description names it (Common::DefaultNames): ui2,...
Definition DataTypes.h:69
optional< T > FromText(const String &text)
text read as UPnP reads it: as t, if given, else as T's default data type (the table above) - nullopt...
Foundation::Memory::BLOB Serialize(const ActionRequest &request, const WebService::SOAP::Options &options={})
The SOAP message - its XML, UTF-8 - which sends it: a request, a response, or an error (a SOAP fault)
Definition Action.cpp:215
ActionResponse Invoke(const URI &controlURL, const ActionRequest &request, const WebService::SOAP::Options &options={})
Ask the service at controlURL to do request's action - and return its answer. Throws ActionException ...
Definition Action.cpp:272
void DeSerialize(const Foundation::Memory::BLOB &b, ActionRequest *request, const WebService::SOAP::Options &options={})
The SOAP message read: what Serialize writes, and any other's. Throws DataExchange::BadFormatExceptio...
Definition Action.cpp:239
void CheckRequest(const ActionRequest &request, const ServiceDescription &description)
Throws the ActionException a service answers request with if its description - its actions,...
Definition Action.cpp:377
void HandleAction(WebServer::Message &m, const String &serviceType, const ActionHandler &doAction, const WebService::SOAP::Options &options={})
Answer a control request, at the controlURL's route of the service of type serviceType: doAction's ou...
Definition Action.cpp:300
function< Arguments(const ActionRequest &)> ActionHandler
What a service does for a request for one of its actions: the action's out arguments - or,...
Definition Action.h:286
void DeSerialize(const Memory::BLOB &b, Request *request, const Options &options={})
The SOAP message read: what Serialize writes, and any other's. Throws DataExchange::BadFormatExceptio...
Definition SOAP.cpp:301
Memory::BLOB Serialize(const Request &request, const Options &options={})
The SOAP message - its XML, UTF-8 - which sends it: a call, its answer, or a fault.
Definition SOAP.cpp:249
Response Invoke(const URI &url, const String &soapAction, const Request &request, const Options &options={})
Call a method: POST request to url, with soapAction (in quotes, as SOAPAction headers are: section 6....
Definition SOAP.cpp:383
void HandleRequest(WebServer::Message &m, const function< Response(const Request &)> &handler, const Options &options={})
Answer a call, at its URL's route: 415 (Unsupported Media Type) if its body is not text/xml; else its...
A service's answer to an ActionRequest, it having not done the action: why not (UPnP Device Architect...
Definition Action.h:178
nonvirtual WebService::SOAP::Fault AsFault() const
As SOAP sends it: a Client fault, UPnPError, its detail a UPnPError element.
Definition Action.cpp:140
static constexpr unsigned int kInvalidAction
Definition Action.h:184
static optional< ActionError > FromFault(const WebService::SOAP::Fault &fault, const WebService::SOAP::Options &options={})
The UPnPError a SOAP fault's detail holds; nullopt if it holds none - the detail read with options' X...
Definition Action.cpp:148
A control point's request that a service do one of its actions.
Definition Action.h:99
nonvirtual String GetSOAPAction() const
Definition Action.cpp:95
A service's answer to an ActionRequest, it having done the action: the action's out arguments.
Definition Action.h:141
One of an action's arguments: its name, and its value as text, as UPnP writes it (DataTypes::ToText) ...
Definition Action.h:76
A service's description - its SCPD (UPnP Device Architecture 1.1, section 2.5): its actions,...
A call's answer, the method having not been done: why not (section 4.4) - sent in an HTTP response wh...
Definition SOAP.h:143
What SOAP must be told that has a sensible default - taken by Serialize and DeSerialize,...
Definition SOAP.h:252
EncodingStyleAttribute fEncodingStyleAttribute
Where Serialize writes encodingStyle - nowhere, the default, so what is written holds to SOAP's schem...
Definition SOAP.h:297
A call of a method (section 7.1): the Body's one element, named the method, in its namespace - its in...
Definition SOAP.h:84
A call's answer, the method having been done (section 7.1): the Body's one element,...
Definition SOAP.h:112