MythTV master
eventing.cpp
Go to the documentation of this file.
1
2// Program Name: eventing.cpp
3// Created : Dec. 22, 2006
4//
5// Purpose : uPnp Eventing Base Class Implementation
6//
7// Copyright (c) 2006 David Blain <dblain@mythtv.org>
8//
9// Licensed under the GPL v2 or later, see LICENSE for details
10//
12#include "eventing.h"
13
14#include <cmath>
15
16#include <QStringList>
17#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
18#include <QStringConverter>
19#else
20#include <QTextCodec>
21#endif
22#include <QTextStream>
23
24#include "upnp.h"
25#include "httprequest.h"
26#include "taskqueue.h"
27#include "upnptaskevent.h"
30
32//
34
36 QTextStream &ts, std::chrono::microseconds ttLastNotified) const
37{
38 uint nCount = 0;
39
40 ts << "<?xml version=\"1.0\"?>" << Qt::endl
41 << "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">" << Qt::endl;
42
43 for (auto *prop : std::as_const(m_map))
44 {
45 if ( ttLastNotified < prop->m_ttLastChanged )
46 {
47 nCount++;
48
49 ts << "<e:property>" << Qt::endl;
50 ts << "<" << prop->m_sName << ">";
51 ts << prop->ToString();
52 ts << "</" << prop->m_sName << ">";
53 ts << "</e:property>" << Qt::endl;
54 }
55 }
56
57 ts << "</e:propertyset>" << Qt::endl;
58 ts << Qt::flush;
59
60 return nCount;
61}
62
64//
66
67Eventing::Eventing(const QString &sExtensionName,
68 QString sEventMethodName,
69 const QString &sSharePath) :
70 HttpServerExtension(sExtensionName, sSharePath),
71 m_sEventMethodName(std::move(sEventMethodName)),
72 m_nSubscriptionDuration(
73 XmlConfiguration().GetDuration<std::chrono::seconds>("UPnP/SubscriptionDuration", 30min))
74{
76}
77
79//
81
83{
84 for (const auto *subscriber : std::as_const(m_subscribers))
85 delete subscriber;
86 m_subscribers.clear();
87}
88
90//
92
94{
95 // -=>TODO: Should use an Atomic increment...
96 // need to research available functions.
97
98 m_mutex.lock();
99 bool err = (m_nHoldCount >= 127);
100 short nVal = (m_nHoldCount++);
101 m_mutex.unlock();
102
103 if (err)
104 {
105 LOG(VB_GENERAL, LOG_ERR, "Exceeded maximum guarranteed range of "
106 "m_nHoldCount short [-128..127]");
107 LOG(VB_GENERAL, LOG_ERR,
108 "UPnP may not exhibit strange behavior or crash mythtv");
109 }
110
111 return nVal;
112}
113
115//
117
119{
120 // -=>TODO: Should use an Atomic decrement...
121
122 m_mutex.lock();
123 short nVal = (m_nHoldCount--);
124 m_mutex.unlock();
125
126 if (nVal == 0)
127 Notify();
128
129 return nVal;
130}
131
133//
135
137{
138 // -=>TODO: This isn't very efficient... Need to find out if we can make
139 // this something unique, other than root.
140
141 return QStringList( "/" );
142}
143
145//
147
149{
150 if (pRequest)
151 {
152 if ( pRequest->m_sBaseUrl != "/" )
153 return false;
154
155 if ( pRequest->m_sMethod != m_sEventMethodName )
156 return false;
157
158 LOG(VB_UPNP, LOG_INFO, QString("Eventing::ProcessRequest - Method (%1)")
159 .arg(pRequest->m_sMethod ));
160
161 switch( pRequest->m_eType )
162 {
163 case RequestTypeSubscribe : HandleSubscribe ( pRequest ); break;
164 case RequestTypeUnsubscribe : HandleUnsubscribe ( pRequest ); break;
165 default:
167 break;
168 }
169 }
170
171 return true;
172
173}
174
176//
178
180{
181 // Use PostProcessing Hook to perform Initial Notification
182 // to make sure they receive it AFTER the subscription results
183
184 if (m_pInitializeSubscriber != nullptr)
185 {
187
188 m_pInitializeSubscriber = nullptr;
189 }
190}
191
193//
195
197{
199 pRequest->m_nResponseStatus = 412;
200
201 QString sCallBack = pRequest->GetRequestHeader( "CALLBACK", "" );
202 QString sNT = pRequest->GetRequestHeader( "NT" , "" );
203 QString sTimeout = pRequest->GetRequestHeader( "TIMEOUT" , "" );
204 QString sSID = pRequest->GetRequestHeader( "SID" , "" );
205
206 SubscriberInfo *pInfo = nullptr;
207
208 // ----------------------------------------------------------------------
209 // Validate Header Values...
210 // ----------------------------------------------------------------------
211
212 // -=>TODO: Need to add support for more than one CallBack URL.
213
214 if ( sCallBack.length() != 0 )
215 {
216 // ------------------------------------------------------------------
217 // New Subscription
218 // ------------------------------------------------------------------
219
220 if ( sSID.length() != 0 )
221 {
222 pRequest->m_nResponseStatus = 400;
223 return;
224 }
225
226 if ( sNT != "upnp:event" )
227 return;
228
229 // ----------------------------------------------------------------------
230 // Process Subscription
231 // ----------------------------------------------------------------------
232
233 // -=>TODO: Temp code until support for multiple callbacks are supported.
234
235 sCallBack = sCallBack.mid( 1, sCallBack.indexOf(">") - 1);
236
237 std::chrono::seconds nDuration = m_nSubscriptionDuration;
238 if ( sTimeout.startsWith("Second-") )
239 {
240 bool ok = false;
241 auto nValue = std::chrono::seconds(sTimeout.section("-", 1).toInt(&ok));
242 if (ok)
243 nDuration = nValue;
244 }
245
246 pInfo = new SubscriberInfo( sCallBack, nDuration );
247
248 Subscribers::iterator it = m_subscribers.find(pInfo->m_sUUID);
249 if (it != m_subscribers.end())
250 {
251 delete *it;
252 m_subscribers.erase(it);
253 }
254 m_subscribers[pInfo->m_sUUID] = pInfo;
255
256 // Use PostProcess Hook to Send Initial FULL Notification...
257 // *** Must send this response first then notify.
258
260 pRequest->m_pPostProcess = (IPostProcess *)this;
261
262 }
263 else
264 {
265 // ------------------------------------------------------------------
266 // Renewal
267 // ------------------------------------------------------------------
268
269 if ( sSID.length() != 0 )
270 {
271 sSID = sSID.mid( 5 );
272 pInfo = m_subscribers[sSID];
273 }
274
275 }
276
277 if (pInfo != nullptr)
278 {
279 pRequest->m_mapRespHeaders[ "SID" ] = QString( "uuid:%1" )
280 .arg( pInfo->m_sUUID );
281
282 pRequest->m_mapRespHeaders[ "TIMEOUT"] = QString( "Second-%1" )
283 .arg( pInfo->m_nDuration.count() );
284
285 pRequest->m_nResponseStatus = 200;
286
287 }
288
289}
290
292//
294
296{
298 pRequest->m_nResponseStatus = 412;
299
300 QString sCallBack = pRequest->GetRequestHeader( "CALLBACK", "" );
301 QString sNT = pRequest->GetRequestHeader( "NT" , "" );
302 QString sSID = pRequest->GetRequestHeader( "SID" , "" );
303
304 if ((sCallBack.length() != 0) || (sNT.length() != 0))
305 {
306 pRequest->m_nResponseStatus = 400;
307 return;
308 }
309
310 sSID = sSID.mid( 5 );
311
312 Subscribers::iterator it = m_subscribers.find(sSID);
313 if (it != m_subscribers.end())
314 {
315 delete *it;
316 m_subscribers.erase(it);
317 pRequest->m_nResponseStatus = 200;
318 }
319}
320
322//
324
326{
327 auto tt = nowAsDuration<std::chrono::microseconds>();
328
329 m_mutex.lock();
330
331 Subscribers::iterator it = m_subscribers.begin();
332 while (it != m_subscribers.end())
333 {
334 if (!(*it))
335 { // This should never happen, but if someone inserted bad data...
336 ++it;
337 continue;
338 }
339
340 if (tt < (*it)->m_ttExpires)
341 {
342 // Subscription not expired yet. Send event notification.
343 NotifySubscriber(*it);
344 ++it;
345 }
346 else
347 {
348 // Time to expire this subscription. Remove subscriber from list.
349 delete *it;
350 it = m_subscribers.erase(it);
351 }
352 }
353
354 m_mutex.unlock();
355}
356
358//
360
362{
363 if (pInfo == nullptr)
364 return;
365
366 QByteArray aBody;
367#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
368 QTextStream tsBody( &aBody, QIODevice::WriteOnly );
369 tsBody.setCodec(QTextCodec::codecForName("UTF-8"));
370#else
371 QTextStream tsBody(&aBody, QIODeviceBase::WriteOnly);
372 tsBody.setEncoding(QStringConverter::Utf8);
373#endif
374
375 // ----------------------------------------------------------------------
376 // Build Body... Only send if there are changes
377 // ----------------------------------------------------------------------
378
379 uint nCount = BuildNotifyBody(tsBody, pInfo->m_ttLastNotified);
380 if (nCount)
381 {
382
383 // -=>TODO: Need to add support for more than one CallBack URL.
384
385 auto *pBuffer = new QByteArray(); // UPnpEventTask will delete this pointer.
386#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
387 QTextStream tsMsg( pBuffer, QIODevice::WriteOnly );
388 tsMsg.setCodec(QTextCodec::codecForName("UTF-8"));
389#else
390 QTextStream tsMsg(pBuffer, QIODeviceBase::WriteOnly);
391 tsMsg.setEncoding(QStringConverter::Utf8);
392#endif
393
394 // ----------------------------------------------------------------------
395 // Build Message Header
396 // ----------------------------------------------------------------------
397
398 int nPort = (pInfo->m_qURL.port()>=0) ? pInfo->m_qURL.port() : 80;
399 QString sHost = QString( "%1:%2" ).arg( pInfo->m_qURL.host() )
400 .arg( nPort );
401
402 tsMsg << "NOTIFY " << pInfo->m_qURL.path() << " HTTP/1.1\r\n";
403 tsMsg << "HOST: " << sHost << "\r\n";
404 tsMsg << "CONTENT-TYPE: \"text/xml\"\r\n";
405 tsMsg << "Content-Length: " << QString::number( aBody.size() ) << "\r\n";
406 tsMsg << "NT: upnp:event\r\n";
407 tsMsg << "NTS: upnp:propchange\r\n";
408 tsMsg << "SID: uuid:" << pInfo->m_sUUID << "\r\n";
409 tsMsg << "SEQ: " << QString::number( pInfo->m_nKey ) << "\r\n";
410 tsMsg << "\r\n";
411 tsMsg << aBody;
412 tsMsg << Qt::flush;
413
414 // ------------------------------------------------------------------
415 // Add new EventTask to the TaskQueue to do the actual sending.
416 // ------------------------------------------------------------------
417
418 LOG(VB_UPNP, LOG_INFO,
419 QString("UPnp::Eventing::NotifySubscriber( %1 ) : %2 Variables")
420 .arg( sHost ).arg(nCount));
421
422 auto *pEventTask = new UPnpEventTask(QHostAddress(pInfo->m_qURL.host()),
423 nPort, pBuffer);
424
425 TaskQueue::Instance()->AddTask( 250ms, pEventTask );
426
427 pEventTask->DecrRef();
428
429 // ------------------------------------------------------------------
430 // Update the subscribers Key & last Notified fields
431 // ------------------------------------------------------------------
432
433 pInfo->IncrementKey();
434
435 pInfo->m_ttLastNotified = nowAsDuration<std::chrono::microseconds>();
436 }
437}
438
short m_nHoldCount
Definition: eventing.h:260
void HandleUnsubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:295
QString m_sEventMethodName
Definition: eventing.h:255
~Eventing() override
Definition: eventing.cpp:82
short ReleaseEvents()
Definition: eventing.cpp:118
short HoldEvents()
Definition: eventing.cpp:93
void HandleSubscribe(HTTPRequest *pRequest)
Definition: eventing.cpp:196
QMutex m_mutex
Definition: eventing.h:253
bool ProcessRequest(HTTPRequest *pRequest) override
Definition: eventing.cpp:148
void NotifySubscriber(SubscriberInfo *pInfo)
Definition: eventing.cpp:361
QStringList GetBasePaths() override
Definition: eventing.cpp:136
void Notify() override
Definition: eventing.cpp:325
void ExecutePostProcess() override
Definition: eventing.cpp:179
Eventing(const QString &sExtensionName, QString sEventMethodName, const QString &sSharePath)
Definition: eventing.cpp:67
SubscriberInfo * m_pInitializeSubscriber
Definition: eventing.h:262
Subscribers m_subscribers
Definition: eventing.h:256
std::chrono::seconds m_nSubscriptionDuration
Definition: eventing.h:258
HttpResponseType m_eResponseType
Definition: httprequest.h:150
long m_nResponseStatus
Definition: httprequest.h:153
void FormatErrorResponse(bool bServerError, const QString &sFaultString, const QString &sDetails)
QString m_sMethod
Definition: httprequest.h:131
QString GetRequestHeader(const QString &sKey, const QString &sDefault)
IPostProcess * m_pPostProcess
Definition: httprequest.h:160
HttpRequestType m_eType
Definition: httprequest.h:122
QStringMap m_mapRespHeaders
Definition: httprequest.h:154
QString m_sBaseUrl
Definition: httprequest.h:129
uint BuildNotifyBody(QTextStream &ts, std::chrono::microseconds ttLastNotified) const
Definition: eventing.cpp:35
std::chrono::microseconds m_ttLastNotified
Definition: eventing.h:64
unsigned long IncrementKey()
Definition: eventing.h:54
QString m_sUUID
Definition: eventing.h:66
unsigned short m_nKey
Definition: eventing.h:68
std::chrono::seconds m_nDuration
Definition: eventing.h:69
void AddTask(std::chrono::milliseconds msec, Task *pTask)
Add a task to run in the future.
Definition: taskqueue.cpp:170
static TaskQueue * Instance()
Definition: taskqueue.cpp:57
unsigned int uint
Definition: compat.h:60
@ ResponseTypeXML
Definition: httprequest.h:80
@ RequestTypeSubscribe
Definition: httprequest.h:60
@ RequestTypeUnsubscribe
Definition: httprequest.h:61
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ UPnPResult_InvalidAction