1 /*****************************************************************
2 |
3 |   Platinum - Control/Event
4 |
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
8 |
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
13 |
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
21 |
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 | GNU General Public License for more details.
26 |
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
32 |
33 ****************************************************************/
34 
35 /*----------------------------------------------------------------------
36 |   includes
37 +---------------------------------------------------------------------*/
38 #include "PltTaskManager.h"
39 #include "PltEvent.h"
40 #include "PltService.h"
41 #include "PltUPnP.h"
42 #include "PltDeviceData.h"
43 #include "PltUtilities.h"
44 #include "PltCtrlPointTask.h"
45 
46 NPT_SET_LOCAL_LOGGER("platinum.core.event")
47 
48 /*----------------------------------------------------------------------
49 |   PLT_EventNotification::PLT_EventNotification
50 +---------------------------------------------------------------------*/
51 PLT_EventNotification*
Parse(const NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)52 PLT_EventNotification::Parse(const NPT_HttpRequest&        request,
53                              const NPT_HttpRequestContext& context,
54                              NPT_HttpResponse&             response)
55 {
56     NPT_COMPILER_UNUSED(context);
57 
58     PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessHttpNotify:", &request);
59 
60     PLT_EventNotification *notification = new PLT_EventNotification();
61     notification->m_RequestUrl = request.GetUrl();
62 
63     const NPT_String* sid = PLT_UPnPMessageHelper::GetSID(request);
64     const NPT_String* nt  = PLT_UPnPMessageHelper::GetNT(request);
65     const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
66 
67     if (!sid || sid->GetLength() == 0) {
68         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, bad_request);
69     }
70     notification->m_SID = *sid;
71 
72     if (!nt  || nt->GetLength()  == 0 || !nts || nts->GetLength() == 0) {
73         response.SetStatus(400, "Bad request");
74         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, bad_request);
75     }
76 
77     if (nt->Compare("upnp:event", true) || nts->Compare("upnp:propchange", true)) {
78         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, bad_request);
79     }
80 
81     // if the sequence number is less than our current one, we got it out of order
82     // so we disregard it
83     PLT_UPnPMessageHelper::GetSeq(request, notification->m_EventKey);
84 
85     // parse body
86     if (NPT_FAILED(PLT_HttpHelper::GetBody(request, notification->m_XmlBody))) {
87         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, bad_request);
88     }
89 
90     return notification;
91 
92 bad_request:
93     NPT_LOG_SEVERE("CtrlPoint received bad event notify request\r\n");
94     if (response.GetStatusCode() == 200) {
95         response.SetStatus(412, "Precondition Failed");
96     }
97     delete notification;
98     return NULL;
99 }
100 
101 /*----------------------------------------------------------------------
102 |   PLT_EventSubscriber::PLT_EventSubscriber
103 +---------------------------------------------------------------------*/
PLT_EventSubscriber(PLT_TaskManagerReference task_manager,PLT_Service * service,const char * sid,NPT_Timeout timeout_secs)104 PLT_EventSubscriber::PLT_EventSubscriber(PLT_TaskManagerReference task_manager,
105                                          PLT_Service*             service,
106                                          const char*              sid,
107                                          NPT_Timeout              timeout_secs /* = -1 */) :
108     m_TaskManager(task_manager),
109     m_Service(service),
110     m_EventKey(0),
111     m_SubscriberTask(NULL),
112     m_SID(sid)
113 {
114     NPT_LOG_FINE_1("Creating new subscriber (%s)", m_SID.GetChars());
115     SetTimeout(timeout_secs);
116 }
117 
118 /*----------------------------------------------------------------------
119 |   PLT_EventSubscriber::~PLT_EventSubscriber
120 +---------------------------------------------------------------------*/
~PLT_EventSubscriber()121 PLT_EventSubscriber::~PLT_EventSubscriber()
122 {
123     NPT_LOG_FINE_1("Deleting subscriber (%s)", m_SID.GetChars());
124     if (m_SubscriberTask) {
125         m_SubscriberTask->Kill();
126         m_SubscriberTask = NULL;
127     }
128 }
129 
130 /*----------------------------------------------------------------------
131 |   PLT_EventSubscriber::GetService
132 +---------------------------------------------------------------------*/
133 PLT_Service*
GetService()134 PLT_EventSubscriber::GetService()
135 {
136     return m_Service;
137 }
138 
139 /*----------------------------------------------------------------------
140 |   PLT_EventSubscriber::GetEventKey
141 +---------------------------------------------------------------------*/
142 NPT_Ordinal
GetEventKey()143 PLT_EventSubscriber::GetEventKey()
144 {
145     return m_EventKey;
146 }
147 
148 /*----------------------------------------------------------------------
149 |   PLT_EventSubscriber::SetEventKey
150 +---------------------------------------------------------------------*/
151 NPT_Result
SetEventKey(NPT_Ordinal value)152 PLT_EventSubscriber::SetEventKey(NPT_Ordinal value)
153 {
154     m_EventKey = value;
155     return NPT_SUCCESS;
156 }
157 
158 /*----------------------------------------------------------------------
159 |   PLT_EventSubscriber::GetLocalIf
160 +---------------------------------------------------------------------*/
161 NPT_SocketAddress
GetLocalIf()162 PLT_EventSubscriber::GetLocalIf()
163 {
164     return m_LocalIf;
165 }
166 
167 /*----------------------------------------------------------------------
168 |   PLT_EventSubscriber::SetLocalIf
169 +---------------------------------------------------------------------*/
170 NPT_Result
SetLocalIf(NPT_SocketAddress value)171 PLT_EventSubscriber::SetLocalIf(NPT_SocketAddress value)
172 {
173     m_LocalIf = value;
174     return NPT_SUCCESS;
175 }
176 
177 /*----------------------------------------------------------------------
178 |   PLT_EventSubscriber::GetExpirationTime
179 +---------------------------------------------------------------------*/
180 // a TimeStamp of 0 means no expiration
181 NPT_TimeStamp
GetExpirationTime()182 PLT_EventSubscriber::GetExpirationTime()
183 {
184     return m_ExpirationTime;
185 }
186 
187 /*----------------------------------------------------------------------
188 |   PLT_EventSubscriber::SetExpirationTime
189 +---------------------------------------------------------------------*/
190 NPT_Result
SetTimeout(NPT_Timeout seconds)191 PLT_EventSubscriber::SetTimeout(NPT_Timeout seconds)
192 {
193     NPT_LOG_FINE_2("subscriber (%s) expiring in %d seconds",
194         m_SID.GetChars(),
195         seconds);
196 
197     // -1 means infinite but we default to 300 secs
198     if (seconds == -1) seconds = 300;
199 
200     NPT_System::GetCurrentTimeStamp(m_ExpirationTime);
201     m_ExpirationTime += NPT_TimeInterval((double)seconds);
202 
203     return NPT_SUCCESS;
204 }
205 
206 /*----------------------------------------------------------------------
207 |   PLT_EventSubscriber::FindCallbackURL
208 +---------------------------------------------------------------------*/
209 NPT_Result
FindCallbackURL(const char * callback_url)210 PLT_EventSubscriber::FindCallbackURL(const char* callback_url)
211 {
212     NPT_String res;
213     return NPT_ContainerFind(m_CallbackURLs,
214                              NPT_StringFinder(callback_url),
215                              res);
216 }
217 
218 /*----------------------------------------------------------------------
219 |   PLT_EventSubscriber::AddCallbackURL
220 +---------------------------------------------------------------------*/
221 NPT_Result
AddCallbackURL(const char * callback_url)222 PLT_EventSubscriber::AddCallbackURL(const char* callback_url)
223 {
224     NPT_CHECK_POINTER_FATAL(callback_url);
225 
226     NPT_LOG_FINE_2("Adding callback \"%s\" to subscriber %s",
227         callback_url,
228         m_SID.GetChars());
229     return m_CallbackURLs.Add(callback_url);
230 }
231 
232 /*----------------------------------------------------------------------
233 |   PLT_EventSubscriber::Notify
234 +---------------------------------------------------------------------*/
235 NPT_Result
Notify(NPT_List<PLT_StateVariable * > & vars)236 PLT_EventSubscriber::Notify(NPT_List<PLT_StateVariable*>& vars)
237 {
238     // verify we have eventable variables
239     bool foundVars = false;
240     NPT_Reference<NPT_XmlElementNode> propertyset(new NPT_XmlElementNode("e", "propertyset"));
241     NPT_CHECK_SEVERE(propertyset->SetNamespaceUri(
242         "e",
243         "urn:schemas-upnp-org:event-1-0"));
244 
245     NPT_List<PLT_StateVariable*>::Iterator var = vars.GetFirstItem();
246     while (var) {
247         if ((*var)->IsSendingEvents()) {
248             NPT_XmlElementNode* property = new NPT_XmlElementNode("e", "property");
249             NPT_CHECK_FATAL(propertyset->AddChild(property));
250             NPT_CHECK_FATAL(PLT_XmlHelper::AddChildText(property,
251                                                         (*var)->GetName(),
252                                                         (*var)->GetValue()));
253             foundVars = true;
254         }
255         ++var;
256     }
257 
258     // no eventable state variables found!
259     if (foundVars == false) {
260         return NPT_FAILURE;
261     }
262 
263     // format the body with the xml
264     NPT_String xml;
265     if (NPT_FAILED(PLT_XmlHelper::Serialize(*propertyset, xml))) {
266         NPT_CHECK_FATAL(NPT_FAILURE);
267     }
268     propertyset = NULL;
269 
270     // parse the callback url
271     NPT_HttpUrl url(m_CallbackURLs[0]);
272     if (!url.IsValid()) {
273         NPT_CHECK_FATAL(NPT_FAILURE);
274     }
275     // format request
276     NPT_HttpRequest* request =
277         new NPT_HttpRequest(url,
278                             "NOTIFY",
279                             NPT_HTTP_PROTOCOL_1_1);
280     NPT_HttpEntity* entity;
281     PLT_HttpHelper::SetBody(*request, xml, &entity);
282 
283     // add the extra headers
284     entity->SetContentType("text/xml; charset=\"utf-8\"");
285     PLT_UPnPMessageHelper::SetNT(*request, "upnp:event");
286     PLT_UPnPMessageHelper::SetNTS(*request, "upnp:propchange");
287     PLT_UPnPMessageHelper::SetSID(*request, m_SID);
288     PLT_UPnPMessageHelper::SetSeq(*request, m_EventKey);
289 
290     // wrap around sequence to 1
291     if (++m_EventKey == 0) m_EventKey = 1;
292 
293     // start the task now if not started already
294     if (!m_SubscriberTask) {
295         // TODO: the subscriber task should inform subscriber if
296         // a notification failed to be received so it can be removed
297         // from the list of subscribers inside the device host
298         NPT_Reference<PLT_HttpClientSocketTask> task(new PLT_HttpClientSocketTask(request, true));
299 
300         // short connection time out in case subscriber is not alive
301         NPT_HttpClient::Config config;
302         config.m_ConnectionTimeout = 2000;
303         task->SetHttpClientConfig(config);
304 
305         // add initial delay to make sure ctrlpoint receives response to subscription
306         // before our first NOTIFY. Also make sure task is not auto-destroy
307         // since we want to destroy it manually when the subscriber goes away.
308         NPT_TimeInterval delay(0.05f);
309         NPT_CHECK_FATAL(m_TaskManager->StartTask(task.AsPointer(), NULL /*&delay*/, false));
310 
311         // Task successfully started, keep around for future notifications
312         m_SubscriberTask = task.AsPointer();
313         task.Detach();
314     } else {
315         m_SubscriberTask->AddRequest(request);
316     }
317 
318     return NPT_SUCCESS;
319 }
320 
321 /*----------------------------------------------------------------------
322 |   PLT_EventSubscriberFinderByService::operator()
323 +---------------------------------------------------------------------*/
324 bool
operator ()(PLT_EventSubscriberReference const & eventSub) const325 PLT_EventSubscriberFinderByService::operator()(PLT_EventSubscriberReference const & eventSub) const
326 {
327     return (m_Service == eventSub->GetService());
328 }
329