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