Mercurial > projects > hoofbaby
comparison deps/Platinum/Source/Core/PltCtrlPoint.cpp @ 0:3425707ddbf6
Initial import (hopefully this mercurial stuff works...)
author | fraserofthenight |
---|---|
date | Mon, 06 Jul 2009 08:06:28 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:3425707ddbf6 |
---|---|
1 /***************************************************************** | |
2 | | |
3 | Platinum - Control Point | |
4 | | |
5 | Copyright (c) 2004-2008, 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 | | |
21 | This program is distributed in the hope that it will be useful, | |
22 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
24 | GNU General Public License for more details. | |
25 | | |
26 | You should have received a copy of the GNU General Public License | |
27 | along with this program; see the file LICENSE.txt. If not, write to | |
28 | the Free Software Foundation, Inc., | |
29 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
30 | http://www.gnu.org/licenses/gpl-2.0.html | |
31 | | |
32 ****************************************************************/ | |
33 | |
34 /*---------------------------------------------------------------------- | |
35 | includes | |
36 +---------------------------------------------------------------------*/ | |
37 #include "PltCtrlPoint.h" | |
38 #include "PltUPnP.h" | |
39 #include "PltDeviceData.h" | |
40 #include "PltXmlHelper.h" | |
41 #include "PltCtrlPointTask.h" | |
42 #include "PltSsdp.h" | |
43 #include "PltHttpServer.h" | |
44 | |
45 NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint") | |
46 | |
47 /*---------------------------------------------------------------------- | |
48 | typedef | |
49 +---------------------------------------------------------------------*/ | |
50 typedef PLT_HttpRequestHandler<PLT_CtrlPoint> PLT_HttpCtrlPointRequestHandler; | |
51 | |
52 /*---------------------------------------------------------------------- | |
53 | PLT_CtrlPointListenerOnDeviceAddedIterator class | |
54 +---------------------------------------------------------------------*/ | |
55 class PLT_CtrlPointListenerOnDeviceAddedIterator | |
56 { | |
57 public: | |
58 PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) : | |
59 m_Device(device) {} | |
60 | |
61 NPT_Result operator()(PLT_CtrlPointListener*& listener) const { | |
62 return listener->OnDeviceAdded(m_Device); | |
63 } | |
64 | |
65 private: | |
66 PLT_DeviceDataReference& m_Device; | |
67 }; | |
68 | |
69 /*---------------------------------------------------------------------- | |
70 | PLT_CtrlPointListenerOnDeviceRemovedIterator class | |
71 +---------------------------------------------------------------------*/ | |
72 class PLT_CtrlPointListenerOnDeviceRemovedIterator | |
73 { | |
74 public: | |
75 PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) : | |
76 m_Device(device) {} | |
77 | |
78 NPT_Result operator()(PLT_CtrlPointListener*& listener) const { | |
79 return listener->OnDeviceRemoved(m_Device); | |
80 } | |
81 | |
82 private: | |
83 PLT_DeviceDataReference& m_Device; | |
84 }; | |
85 | |
86 /*---------------------------------------------------------------------- | |
87 | PLT_CtrlPointListenerOnActionResponseIterator class | |
88 +---------------------------------------------------------------------*/ | |
89 class PLT_CtrlPointListenerOnActionResponseIterator | |
90 { | |
91 public: | |
92 PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res, | |
93 PLT_ActionReference& action, | |
94 void* userdata) : | |
95 m_Res(res), m_Action(action), m_Userdata(userdata) {} | |
96 | |
97 NPT_Result operator()(PLT_CtrlPointListener*& listener) const { | |
98 return listener->OnActionResponse(m_Res, m_Action, m_Userdata); | |
99 } | |
100 | |
101 private: | |
102 NPT_Result m_Res; | |
103 PLT_ActionReference& m_Action; | |
104 void* m_Userdata; | |
105 }; | |
106 | |
107 /*---------------------------------------------------------------------- | |
108 | PLT_CtrlPointListenerOnEventNotifyIterator class | |
109 +---------------------------------------------------------------------*/ | |
110 class PLT_CtrlPointListenerOnEventNotifyIterator | |
111 { | |
112 public: | |
113 PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service* service, | |
114 NPT_List<PLT_StateVariable*>* vars) : | |
115 m_Service(service), m_Vars(vars) {} | |
116 | |
117 NPT_Result operator()(PLT_CtrlPointListener*& listener) const { | |
118 return listener->OnEventNotify(m_Service, m_Vars); | |
119 } | |
120 | |
121 private: | |
122 PLT_Service* m_Service; | |
123 NPT_List<PLT_StateVariable*>* m_Vars; | |
124 }; | |
125 | |
126 /*---------------------------------------------------------------------- | |
127 | PLT_AddGetSCPDRequestIterator class | |
128 +---------------------------------------------------------------------*/ | |
129 class PLT_AddGetSCPDRequestIterator | |
130 { | |
131 public: | |
132 PLT_AddGetSCPDRequestIterator(PLT_TaskManager* task_manager, | |
133 PLT_CtrlPoint* ctrl_point, | |
134 PLT_DeviceDataReference& device) : | |
135 m_TaskManager(task_manager), | |
136 m_CtrlPoint(ctrl_point), | |
137 m_Device(device) {} | |
138 | |
139 NPT_Result operator()(PLT_Service*& service) const { | |
140 // look for the host and port of the device | |
141 PLT_DeviceData* device = service->GetDevice(); | |
142 NPT_String scpd_url = service->GetSCPDURL(); | |
143 | |
144 NPT_LOG_INFO_2("Fetching SCPD for service \"%s\" of device \"%s\"", | |
145 (const char*)service->GetServiceID(), | |
146 (const char*)device->GetFriendlyName()); | |
147 | |
148 // if the SCPD Url starts with a "/", this means we should not append it to the base URI | |
149 // but instead go there directly | |
150 if (!scpd_url.StartsWith("/")) { | |
151 scpd_url = device->GetURLBase().GetPath() + scpd_url; | |
152 } | |
153 | |
154 NPT_HttpUrl url(device->GetURLBase().GetHost(), | |
155 device->GetURLBase().GetPort(), | |
156 scpd_url); | |
157 | |
158 // Add a delay, some devices need it (aka Rhapsody) | |
159 NPT_TimeInterval delay(0.1f); | |
160 return m_TaskManager->StartTask( | |
161 new PLT_CtrlPointGetSCPDTask(url, | |
162 m_CtrlPoint, | |
163 (PLT_DeviceDataReference&)m_Device), | |
164 &delay); | |
165 } | |
166 | |
167 private: | |
168 PLT_TaskManager* m_TaskManager; | |
169 PLT_CtrlPoint* m_CtrlPoint; | |
170 PLT_DeviceDataReference m_Device; | |
171 }; | |
172 | |
173 /*---------------------------------------------------------------------- | |
174 | PLT_EventSubscriberRemoverIterator class | |
175 +---------------------------------------------------------------------*/ | |
176 class PLT_EventSubscriberRemoverIterator | |
177 { | |
178 public: | |
179 PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) : | |
180 m_CtrlPoint(ctrl_point) { | |
181 m_CtrlPoint->m_Subscribers.Lock(); | |
182 } | |
183 ~PLT_EventSubscriberRemoverIterator() { | |
184 m_CtrlPoint->m_Subscribers.Unlock(); | |
185 } | |
186 | |
187 NPT_Result operator()(PLT_Service*& service) const { | |
188 PLT_EventSubscriber* sub = NULL; | |
189 if (NPT_SUCCEEDED(NPT_ContainerFind(m_CtrlPoint->m_Subscribers, | |
190 PLT_EventSubscriberFinderByService(service), sub))) { | |
191 m_CtrlPoint->m_Subscribers.Remove(sub); | |
192 delete sub; | |
193 } | |
194 | |
195 return NPT_SUCCESS; | |
196 } | |
197 | |
198 private: | |
199 PLT_CtrlPoint* m_CtrlPoint; | |
200 }; | |
201 | |
202 /*---------------------------------------------------------------------- | |
203 | PLT_ServiceReadyIterator class | |
204 +---------------------------------------------------------------------*/ | |
205 class PLT_ServiceReadyIterator | |
206 { | |
207 public: | |
208 PLT_ServiceReadyIterator() {} | |
209 | |
210 NPT_Result operator()(PLT_Service*& service) const { | |
211 return service->IsInitted()?NPT_SUCCESS:NPT_FAILURE; | |
212 } | |
213 }; | |
214 | |
215 /*---------------------------------------------------------------------- | |
216 | PLT_DeviceReadyIterator class | |
217 +---------------------------------------------------------------------*/ | |
218 class PLT_DeviceReadyIterator | |
219 { | |
220 public: | |
221 PLT_DeviceReadyIterator() {} | |
222 NPT_Result operator()(PLT_DeviceDataReference& device) const { | |
223 NPT_CHECK(device->m_Services.ApplyUntil( | |
224 PLT_ServiceReadyIterator(), | |
225 NPT_UntilResultNotEquals(NPT_SUCCESS))); | |
226 | |
227 NPT_CHECK(device->m_EmbeddedDevices.ApplyUntil( | |
228 PLT_DeviceReadyIterator(), | |
229 NPT_UntilResultNotEquals(NPT_SUCCESS))); | |
230 | |
231 // a device must have at least one service or embedded device | |
232 // otherwise it's not ready | |
233 if (device->m_Services.GetItemCount() == 0 && | |
234 device->m_EmbeddedDevices.GetItemCount() == 0) { | |
235 return NPT_FAILURE; | |
236 } | |
237 | |
238 return NPT_SUCCESS; | |
239 } | |
240 }; | |
241 | |
242 /*---------------------------------------------------------------------- | |
243 | PLT_CtrlPoint::PLT_CtrlPoint | |
244 +---------------------------------------------------------------------*/ | |
245 PLT_CtrlPoint::PLT_CtrlPoint(const char* autosearch /* = "upnp:rootdevice" */) : | |
246 m_EventHttpServer(new PLT_HttpServer()), | |
247 m_AutoSearch(autosearch) | |
248 { | |
249 m_EventHttpServerHandler = new PLT_HttpCtrlPointRequestHandler(this); | |
250 m_EventHttpServer->AddRequestHandler(m_EventHttpServerHandler, "/", true); | |
251 } | |
252 | |
253 /*---------------------------------------------------------------------- | |
254 | PLT_CtrlPoint::~PLT_CtrlPoint | |
255 +---------------------------------------------------------------------*/ | |
256 PLT_CtrlPoint::~PLT_CtrlPoint() | |
257 { | |
258 delete m_EventHttpServer; | |
259 delete m_EventHttpServerHandler; | |
260 } | |
261 | |
262 /*---------------------------------------------------------------------- | |
263 | PLT_CtrlPoint::IgnoreUUID | |
264 +---------------------------------------------------------------------*/ | |
265 void | |
266 PLT_CtrlPoint::IgnoreUUID(const char* uuid) | |
267 { | |
268 if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { | |
269 m_UUIDsToIgnore.Add(uuid); | |
270 } | |
271 } | |
272 | |
273 /*---------------------------------------------------------------------- | |
274 | PLT_CtrlPoint::Start | |
275 +---------------------------------------------------------------------*/ | |
276 NPT_Result | |
277 PLT_CtrlPoint::Start(PLT_SsdpListenTask* task) | |
278 { | |
279 m_EventHttpServer->Start(); | |
280 | |
281 // house keeping task | |
282 m_TaskManager.StartTask(new PLT_CtrlPointHouseKeepingTask(this)); | |
283 | |
284 task->AddListener(this); | |
285 return m_AutoSearch.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_AutoSearch):NPT_SUCCESS; | |
286 } | |
287 | |
288 /*---------------------------------------------------------------------- | |
289 | PLT_CtrlPoint::Stop | |
290 +---------------------------------------------------------------------*/ | |
291 NPT_Result | |
292 PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task) | |
293 { | |
294 task->RemoveListener(this); | |
295 | |
296 m_TaskManager.StopAllTasks(); | |
297 m_EventHttpServer->Stop(); | |
298 | |
299 // we can safely clear everything without a lock | |
300 // as there are no more tasks pending | |
301 m_Devices.Clear(); | |
302 | |
303 m_Subscribers.Apply(NPT_ObjectDeleter<PLT_EventSubscriber>()); | |
304 m_Subscribers.Clear(); | |
305 | |
306 return NPT_SUCCESS; | |
307 } | |
308 | |
309 /*---------------------------------------------------------------------- | |
310 | PLT_CtrlPoint::AddListener | |
311 +---------------------------------------------------------------------*/ | |
312 NPT_Result | |
313 PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener) | |
314 { | |
315 NPT_AutoLock lock(m_ListenerList); | |
316 if (!m_ListenerList.Contains(listener)) { | |
317 m_ListenerList.Add(listener); | |
318 } | |
319 return NPT_SUCCESS; | |
320 } | |
321 | |
322 /*---------------------------------------------------------------------- | |
323 | PLT_CtrlPoint::RemoveListener | |
324 +---------------------------------------------------------------------*/ | |
325 NPT_Result | |
326 PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener) | |
327 { | |
328 NPT_AutoLock lock(m_ListenerList); | |
329 m_ListenerList.Remove(listener); | |
330 return NPT_SUCCESS; | |
331 } | |
332 | |
333 /*---------------------------------------------------------------------- | |
334 | PLT_CtrlPoint::CreateSearchTask | |
335 +---------------------------------------------------------------------*/ | |
336 PLT_SsdpSearchTask* | |
337 PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl& url, | |
338 const char* target, | |
339 NPT_Cardinal mx, | |
340 const NPT_IpAddress& address) | |
341 { | |
342 // make sure mx is at least 1 | |
343 if (mx<1) mx=1; | |
344 | |
345 // create socket | |
346 NPT_UdpMulticastSocket* socket = new NPT_UdpMulticastSocket(); | |
347 socket->SetInterface(address); | |
348 socket->SetTimeToLive(4); | |
349 | |
350 // create request | |
351 NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1); | |
352 PLT_UPnPMessageHelper::SetMX(*request, mx); | |
353 PLT_UPnPMessageHelper::SetST(*request, target); | |
354 PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\""); | |
355 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader); | |
356 | |
357 // create task | |
358 PLT_SsdpSearchTask* task = new PLT_SsdpSearchTask( | |
359 socket, | |
360 this, | |
361 request, | |
362 mx*10000); | |
363 return task; | |
364 } | |
365 | |
366 /*---------------------------------------------------------------------- | |
367 | PLT_CtrlPoint::Search | |
368 +---------------------------------------------------------------------*/ | |
369 NPT_Result | |
370 PLT_CtrlPoint::Search(const NPT_HttpUrl& url, | |
371 const char* target, | |
372 NPT_Cardinal mx /* = 5 */) | |
373 { | |
374 NPT_List<NPT_NetworkInterface*> if_list; | |
375 NPT_List<NPT_NetworkInterface*>::Iterator net_if; | |
376 NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr; | |
377 | |
378 NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list)); | |
379 | |
380 for (net_if = if_list.GetFirstItem(); net_if; net_if++) { | |
381 // make sure the interface is at least broadcast or multicast | |
382 if (!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) && | |
383 !((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) { | |
384 continue; | |
385 } | |
386 | |
387 for (net_if_addr = (*net_if)->GetAddresses().GetFirstItem(); net_if_addr; net_if_addr++) { | |
388 // create task | |
389 PLT_SsdpSearchTask* task = CreateSearchTask(url, | |
390 target, | |
391 mx, | |
392 (*net_if_addr).GetPrimaryAddress()); | |
393 m_TaskManager.StartTask(task); | |
394 } | |
395 } | |
396 | |
397 // { | |
398 // // create task on 127.0.0.1 | |
399 // NPT_IpAddress address; | |
400 // address.ResolveName("127.0.0.1"); | |
401 // | |
402 // PLT_ThreadTask* task = CreateSearchTask(url, | |
403 // target, | |
404 // mx, | |
405 // address); | |
406 // m_TaskManager.StartTask(task); | |
407 // } | |
408 | |
409 if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>()); | |
410 return NPT_SUCCESS; | |
411 } | |
412 | |
413 /*---------------------------------------------------------------------- | |
414 | PLT_CtrlPoint::Discover | |
415 +---------------------------------------------------------------------*/ | |
416 NPT_Result | |
417 PLT_CtrlPoint::Discover(const NPT_HttpUrl& url, | |
418 const char* target, | |
419 NPT_Cardinal mx /* = 5 */, | |
420 NPT_Timeout repeat /* = 50000 */) | |
421 { | |
422 // make sure mx is at least 1 | |
423 if (mx<1) mx = 1; | |
424 | |
425 // create socket | |
426 NPT_UdpSocket* socket = new NPT_UdpSocket(); | |
427 | |
428 // create request | |
429 NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1); | |
430 PLT_UPnPMessageHelper::SetMX(*request, mx); | |
431 PLT_UPnPMessageHelper::SetST(*request, target); | |
432 PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\""); | |
433 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader); | |
434 | |
435 // force HOST to be the regular multicast address:port | |
436 // Some servers do care (like WMC) otherwise they won't respond to us | |
437 request->GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, "239.255.255.250:1900"); | |
438 | |
439 // create task | |
440 PLT_ThreadTask* task = new PLT_SsdpSearchTask( | |
441 socket, | |
442 this, | |
443 request, | |
444 repeat<(NPT_Timeout)mx*5000?(NPT_Timeout)mx*5000:repeat); /* repeat no less than every 5 secs */ | |
445 return m_TaskManager.StartTask(task); | |
446 } | |
447 | |
448 /*---------------------------------------------------------------------- | |
449 | PLT_CtrlPoint::DoHouseKeeping | |
450 +---------------------------------------------------------------------*/ | |
451 NPT_Result | |
452 PLT_CtrlPoint::DoHouseKeeping() | |
453 { | |
454 NPT_AutoLock lock_devices(m_Devices); | |
455 NPT_TimeStamp now; | |
456 int count = m_Devices.GetItemCount(); | |
457 NPT_System::GetCurrentTimeStamp(now); | |
458 | |
459 PLT_DeviceDataReference device; | |
460 while (count--) { | |
461 NPT_Result res = m_Devices.PopHead(device); | |
462 if (NPT_SUCCEEDED(res)) { | |
463 NPT_TimeStamp last_update = device->GetLeaseTimeLastUpdate(); | |
464 NPT_TimeInterval lease_time = device->GetLeaseTime(); | |
465 | |
466 // check if device lease time has expired | |
467 if (now > last_update + NPT_TimeInterval((unsigned long)(((float)lease_time)*2), 0)) { | |
468 RemoveDevice(device); | |
469 } else { | |
470 // add the device back to our list since it is still alive | |
471 m_Devices.Add(device); | |
472 } | |
473 } else { | |
474 NPT_LOG_SEVERE("DoHouseKeeping failure!"); | |
475 return NPT_FAILURE; | |
476 } | |
477 } | |
478 | |
479 return NPT_SUCCESS; | |
480 } | |
481 | |
482 /*---------------------------------------------------------------------- | |
483 | PLT_CtrlPoint::FindDevice | |
484 +---------------------------------------------------------------------*/ | |
485 NPT_Result | |
486 PLT_CtrlPoint::FindDevice(const char* uuid, | |
487 PLT_DeviceDataReference& device) | |
488 { | |
489 NPT_AutoLock lock(m_Devices); | |
490 return NPT_ContainerFind(m_Devices, PLT_DeviceDataFinder(uuid), device); | |
491 } | |
492 | |
493 /*---------------------------------------------------------------------- | |
494 | PLT_CtrlPoint::ProcessHttpRequest | |
495 +---------------------------------------------------------------------*/ | |
496 NPT_Result | |
497 PLT_CtrlPoint::ProcessHttpRequest(NPT_HttpRequest& request, | |
498 const NPT_HttpRequestContext& context, | |
499 NPT_HttpResponse& response) | |
500 { | |
501 NPT_COMPILER_UNUSED(context); | |
502 if (!request.GetMethod().Compare("NOTIFY")) { | |
503 return ProcessHttpNotify(request, context, response); | |
504 } | |
505 | |
506 NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n"); | |
507 response.SetStatus(412, "Precondition Failed"); | |
508 return NPT_SUCCESS; | |
509 } | |
510 | |
511 /*---------------------------------------------------------------------- | |
512 | PLT_CtrlPoint::ProcessHttpNotify | |
513 +---------------------------------------------------------------------*/ | |
514 NPT_Result | |
515 PLT_CtrlPoint::ProcessHttpNotify(NPT_HttpRequest& request, | |
516 const NPT_HttpRequestContext& context, | |
517 NPT_HttpResponse& response) | |
518 { | |
519 NPT_COMPILER_UNUSED(context); | |
520 | |
521 NPT_List<PLT_StateVariable*> vars; | |
522 PLT_EventSubscriber* sub = NULL; | |
523 NPT_String str; | |
524 NPT_XmlElementNode* xml = NULL; | |
525 NPT_String callback_uri; | |
526 NPT_String uuid; | |
527 NPT_String service_id; | |
528 NPT_UInt32 seq = 0; | |
529 PLT_Service* service = NULL; | |
530 PLT_DeviceData* device = NULL; | |
531 NPT_String content_type; | |
532 | |
533 NPT_String method = request.GetMethod(); | |
534 NPT_String uri = request.GetUrl().GetPath(); | |
535 | |
536 // NPT_LOG_FINE_3("CtrlPoint received %s request from %s:%d\r\n", | |
537 // request.GetMethod(), | |
538 // client_info.remote_address.GetIpAddress(), | |
539 // client_info.remote_address.GetPort()); | |
540 // | |
541 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, &request); | |
542 | |
543 const NPT_String* sid = PLT_UPnPMessageHelper::GetSID(request); | |
544 const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request); | |
545 const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request); | |
546 PLT_HttpHelper::GetContentType(request, content_type); | |
547 | |
548 { | |
549 NPT_AutoLock lock_subs(m_Subscribers); | |
550 | |
551 // look for the subscriber with that subscription url | |
552 if (!sid || NPT_FAILED(NPT_ContainerFind(m_Subscribers, | |
553 PLT_EventSubscriberFinderBySID(*sid), | |
554 sub))) { | |
555 NPT_LOG_FINE_1("Subscriber %s not found\n", (const char*)sid); | |
556 goto bad_request; | |
557 } | |
558 | |
559 // verify the request is syntactically correct | |
560 service = sub->GetService(); | |
561 device = service->GetDevice(); | |
562 | |
563 uuid = device->GetUUID(); | |
564 service_id = service->GetServiceID(); | |
565 | |
566 // callback uri for this sub | |
567 callback_uri = "/" + uuid + "/" + service_id; | |
568 | |
569 if (uri.Compare(callback_uri, true) || | |
570 !nt || nt->Compare("upnp:event", true) || | |
571 !nts || nts->Compare("upnp:propchange", true)) { | |
572 goto bad_request; | |
573 } | |
574 | |
575 // if the sequence number is less than our current one, we got it out of order | |
576 // so we disregard it | |
577 PLT_UPnPMessageHelper::GetSeq(request, seq); | |
578 if (sub->GetEventKey() && seq <= sub->GetEventKey()) { | |
579 goto bad_request; | |
580 } | |
581 | |
582 // parse body | |
583 if (NPT_FAILED(PLT_HttpHelper::ParseBody(request, xml))) { | |
584 goto bad_request; | |
585 } | |
586 | |
587 // check envelope | |
588 if (xml->GetTag().Compare("propertyset", true)) | |
589 goto bad_request; | |
590 | |
591 // check namespace | |
592 // xml.GetAttrValue("xmlns:e", str); | |
593 // if (str.Compare("urn:schemas-upnp-org:event-1-0")) | |
594 // goto bad_request; | |
595 | |
596 // check property set | |
597 // keep a vector of the state variables that changed | |
598 NPT_XmlElementNode* property; | |
599 PLT_StateVariable* var; | |
600 for (NPT_List<NPT_XmlNode*>::Iterator children = xml->GetChildren().GetFirstItem(); children; children++) { | |
601 NPT_XmlElementNode* child = (*children)->AsElementNode(); | |
602 if (!child) continue; | |
603 | |
604 // check property | |
605 if (child->GetTag().Compare("property", true)) | |
606 goto bad_request; | |
607 | |
608 if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) { | |
609 goto bad_request; | |
610 } | |
611 | |
612 var = service->FindStateVariable(property->GetTag()); | |
613 if (var == NULL) { | |
614 goto bad_request; | |
615 } | |
616 | |
617 if (NPT_FAILED(var->SetValue(property->GetText()?*property->GetText():""))) { | |
618 goto bad_request; | |
619 } | |
620 vars.Add(var); | |
621 } | |
622 | |
623 // update sequence | |
624 sub->SetEventKey(seq); | |
625 } | |
626 | |
627 // notify listener we got an update | |
628 if (vars.GetItemCount()) { | |
629 NPT_AutoLock lock(m_ListenerList); | |
630 m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars)); | |
631 } | |
632 | |
633 delete xml; | |
634 return NPT_SUCCESS; | |
635 | |
636 bad_request: | |
637 NPT_LOG_SEVERE("CtrlPoint received bad request\r\n"); | |
638 response.SetStatus(412, "Precondition Failed"); | |
639 delete xml; | |
640 return NPT_SUCCESS; | |
641 } | |
642 | |
643 /*---------------------------------------------------------------------- | |
644 | PLT_CtrlPoint::ProcessSsdpSearchResponse | |
645 +---------------------------------------------------------------------*/ | |
646 NPT_Result | |
647 PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result res, | |
648 const NPT_HttpRequestContext& context, | |
649 NPT_HttpResponse* response) | |
650 { | |
651 NPT_CHECK_SEVERE(res); | |
652 NPT_CHECK_POINTER_SEVERE(response); | |
653 | |
654 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString(); | |
655 NPT_String protocol = response->GetProtocol(); | |
656 | |
657 NPT_LOG_FINE_2("CtrlPoint received SSDP search response from %s:%d", | |
658 (const char*)context.GetRemoteAddress().GetIpAddress().ToString() , | |
659 context.GetRemoteAddress().GetPort()); | |
660 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response); | |
661 | |
662 if (response->GetStatusCode() == 200) { | |
663 const NPT_String* st = response->GetHeaders().GetHeaderValue("st"); | |
664 const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn"); | |
665 const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext"); | |
666 NPT_CHECK_POINTER_SEVERE(st); | |
667 NPT_CHECK_POINTER_SEVERE(usn); | |
668 NPT_CHECK_POINTER_SEVERE(ext); | |
669 | |
670 NPT_String uuid; | |
671 // if we get an advertisement other than uuid | |
672 // verify it's formatted properly | |
673 if (usn != st) { | |
674 char tmp_uuid[200]; | |
675 char tmp_st[200]; | |
676 int ret; | |
677 // FIXME: We can't use sscanf directly! | |
678 ret = sscanf(((const char*)*usn)+5, "%[^::]::%s", | |
679 tmp_uuid, | |
680 tmp_st); | |
681 if (ret != 2) | |
682 return NPT_FAILURE; | |
683 | |
684 if (st->Compare(tmp_st, true)) | |
685 return NPT_FAILURE; | |
686 | |
687 uuid = tmp_uuid; | |
688 } else { | |
689 uuid = ((const char*)*usn)+5; | |
690 } | |
691 | |
692 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { | |
693 NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid); | |
694 return NPT_SUCCESS; | |
695 } | |
696 | |
697 return ProcessSsdpMessage(response, context, uuid); | |
698 } | |
699 | |
700 return NPT_FAILURE; | |
701 } | |
702 | |
703 /*---------------------------------------------------------------------- | |
704 | PLT_CtrlPoint::OnSsdpPacket | |
705 +---------------------------------------------------------------------*/ | |
706 NPT_Result | |
707 PLT_CtrlPoint::OnSsdpPacket(NPT_HttpRequest& request, | |
708 const NPT_HttpRequestContext& context) | |
709 { | |
710 return ProcessSsdpNotify(request, context); | |
711 } | |
712 | |
713 /*---------------------------------------------------------------------- | |
714 | PLT_CtrlPoint::ProcessSsdpNotify | |
715 +---------------------------------------------------------------------*/ | |
716 NPT_Result | |
717 PLT_CtrlPoint::ProcessSsdpNotify(NPT_HttpRequest& request, | |
718 const NPT_HttpRequestContext& context) | |
719 { | |
720 // get the address of who sent us some data back | |
721 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString(); | |
722 NPT_String method = request.GetMethod(); | |
723 NPT_String uri = (const char*)request.GetUrl().GetPath(); | |
724 NPT_String protocol = request.GetProtocol(); | |
725 | |
726 if (method.Compare("NOTIFY") == 0) { | |
727 NPT_LOG_INFO_2("Received SSDP NOTIFY from %s:%d", | |
728 context.GetRemoteAddress().GetIpAddress().ToString().GetChars(), | |
729 context.GetRemoteAddress().GetPort()); | |
730 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request); | |
731 | |
732 if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0)) | |
733 return NPT_FAILURE; | |
734 | |
735 const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request); | |
736 const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request); | |
737 const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request); | |
738 NPT_CHECK_POINTER_SEVERE(nts); | |
739 NPT_CHECK_POINTER_SEVERE(nt); | |
740 NPT_CHECK_POINTER_SEVERE(usn); | |
741 | |
742 NPT_String uuid; | |
743 // if we get an advertisement other than uuid | |
744 // verify it's formatted properly | |
745 if (*usn != *nt) { | |
746 char tmp_uuid[200]; | |
747 char tmp_nt[200]; | |
748 int ret; | |
749 //FIXME: no sscanf! | |
750 ret = sscanf(((const char*)*usn)+5, "%[^::]::%s", | |
751 tmp_uuid, | |
752 tmp_nt); | |
753 if (ret != 2) | |
754 return NPT_FAILURE; | |
755 | |
756 if (nt->Compare(tmp_nt, true)) | |
757 return NPT_FAILURE; | |
758 | |
759 uuid = tmp_uuid; | |
760 } else { | |
761 uuid = ((const char*)*usn)+5; | |
762 } | |
763 | |
764 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { | |
765 NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid); | |
766 return NPT_SUCCESS; | |
767 } | |
768 | |
769 // if it's a byebye, remove the device and return right away | |
770 if (nts->Compare("ssdp:byebye", true) == 0) { | |
771 NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid); | |
772 | |
773 PLT_DeviceDataReference data; | |
774 if (NPT_SUCCEEDED(FindDevice(uuid, data))) { | |
775 NPT_AutoLock lock_devices(m_Devices); | |
776 RemoveDevice(data); | |
777 } | |
778 | |
779 return NPT_SUCCESS; | |
780 } | |
781 | |
782 return ProcessSsdpMessage(&request, context, uuid); | |
783 } | |
784 | |
785 return NPT_FAILURE; | |
786 } | |
787 | |
788 /*---------------------------------------------------------------------- | |
789 | PLT_CtrlPoint::RemoveDevice | |
790 +---------------------------------------------------------------------*/ | |
791 NPT_Result | |
792 PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data) | |
793 { | |
794 NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID()); | |
795 | |
796 /* recursively remove embedded devices */ | |
797 NPT_Array<PLT_DeviceDataReference> embedded_devices = | |
798 data->GetEmbeddedDevices(); | |
799 for(NPT_Cardinal i=0;i>embedded_devices.GetItemCount();i++) { | |
800 RemoveDevice(embedded_devices[i]); | |
801 } | |
802 | |
803 /* remove from list */ | |
804 m_Devices.Remove(data); | |
805 | |
806 /* unsubscribe from services */ | |
807 data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this)); | |
808 | |
809 /* notify listeners */ | |
810 { | |
811 NPT_AutoLock lock(m_ListenerList); | |
812 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data)); | |
813 } | |
814 | |
815 return NPT_SUCCESS; | |
816 } | |
817 | |
818 /*---------------------------------------------------------------------- | |
819 | PLT_CtrlPoint::ProcessSsdpMessage | |
820 +---------------------------------------------------------------------*/ | |
821 NPT_Result | |
822 PLT_CtrlPoint::ProcessSsdpMessage(NPT_HttpMessage* message, | |
823 const NPT_HttpRequestContext& context, | |
824 NPT_String& uuid) | |
825 { | |
826 NPT_COMPILER_UNUSED(context); | |
827 NPT_CHECK_POINTER_SEVERE(message); | |
828 | |
829 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS; | |
830 | |
831 const NPT_String* location = PLT_UPnPMessageHelper::GetLocation(*message); | |
832 NPT_CHECK_POINTER_SEVERE(location); | |
833 | |
834 // be nice and assume a default lease time if not found | |
835 NPT_Timeout leasetime; | |
836 if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(*message, leasetime))) { | |
837 leasetime = 1800; | |
838 } | |
839 | |
840 return InspectDevice(*location, uuid, leasetime); | |
841 } | |
842 | |
843 /*---------------------------------------------------------------------- | |
844 | PLT_CtrlPoint::InspectDevice | |
845 +---------------------------------------------------------------------*/ | |
846 NPT_Result | |
847 PLT_CtrlPoint::InspectDevice(const char* location, | |
848 const char* uuid, | |
849 NPT_Timeout leasetime) | |
850 { | |
851 NPT_HttpUrl url(location); | |
852 if (!url.IsValid()) return NPT_FAILURE; | |
853 | |
854 // is it a new device? | |
855 PLT_DeviceDataReference data; | |
856 if (NPT_FAILED(FindDevice(uuid, data))) { | |
857 NPT_AutoLock lock(m_Devices); | |
858 | |
859 NPT_LOG_INFO_2("New device \"%s\" detected @ %s", uuid, location); | |
860 | |
861 data = new PLT_DeviceData(url, uuid, NPT_TimeInterval(leasetime, 0)); | |
862 m_Devices.Add(data); | |
863 | |
864 // Start a task to retrieve the description | |
865 PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask( | |
866 url, | |
867 this, | |
868 data); | |
869 // Add a delay, some devices need it (aka Rhapsody) | |
870 NPT_TimeInterval delay(0.2f); | |
871 m_TaskManager.StartTask(task, &delay); | |
872 | |
873 return NPT_SUCCESS; | |
874 } | |
875 | |
876 // in case we missed the byebye and the device description has changed (ip or port) | |
877 // reset base and assumes device is the same (same number of services and SCPDs) | |
878 // FIXME: The right way is to remove the device and rescan it though | |
879 PLT_DeviceReadyIterator device_tester; | |
880 if (NPT_SUCCEEDED(device_tester(data)) && | |
881 data->GetDescriptionUrl().Compare(location, true)) { | |
882 NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s", | |
883 (const char*)data->GetFriendlyName(), | |
884 location); | |
885 data->SetURLBase(url); | |
886 } | |
887 | |
888 // renew expiration time | |
889 data->SetLeaseTime(NPT_TimeInterval(leasetime, 0)); | |
890 NPT_LOG_FINE_1("Device (%s) expiration time renewed..", (const char*)uuid); | |
891 return NPT_SUCCESS; | |
892 } | |
893 | |
894 /*---------------------------------------------------------------------- | |
895 | PLT_CtrlPoint::ProcessGetDescriptionResponse | |
896 +---------------------------------------------------------------------*/ | |
897 NPT_Result | |
898 PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result res, | |
899 const NPT_HttpRequestContext& context, | |
900 NPT_HttpResponse* response, | |
901 PLT_DeviceDataReference& device) | |
902 { | |
903 NPT_LOG_INFO_2("Received device description for %s (result = %d)", | |
904 (const char*)device->GetUUID(), | |
905 res); | |
906 | |
907 NPT_CHECK_FATAL(res); | |
908 NPT_CHECK_POINTER_FATAL(response); | |
909 | |
910 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); | |
911 | |
912 // make sure we have seen this device before | |
913 PLT_DeviceDataReference root_device; | |
914 res = FindDevice(device->GetUUID(), root_device); | |
915 NPT_CHECK_WARNING(res); | |
916 | |
917 // get body | |
918 NPT_String desc; | |
919 res = PLT_HttpHelper::GetBody(*response, desc); | |
920 NPT_CHECK_LABEL_SEVERE(res, bad_response); | |
921 | |
922 // set the device description | |
923 res = root_device->SetDescription(desc, context.GetLocalAddress().GetIpAddress()); | |
924 NPT_CHECK_LABEL_SEVERE(res, bad_response); | |
925 | |
926 NPT_LOG_INFO_2("Device \"%s\" is now known as \"%s\"", | |
927 (const char*)device->GetUUID(), | |
928 (const char*)device->GetFriendlyName()); | |
929 | |
930 // add embedded devices to list of devices | |
931 // and fetch their services scpd | |
932 for (NPT_Cardinal i = 0; | |
933 i<root_device->m_EmbeddedDevices.GetItemCount(); | |
934 i++) { | |
935 | |
936 PLT_DeviceDataReference embedded_device = root_device->m_EmbeddedDevices[i]; | |
937 PLT_DeviceDataReference data; | |
938 if (NPT_FAILED(FindDevice(embedded_device->GetUUID(), data))) { | |
939 NPT_AutoLock lock(m_Devices); | |
940 m_Devices.Add(embedded_device); | |
941 } | |
942 | |
943 res = embedded_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator( | |
944 &m_TaskManager, | |
945 this, | |
946 embedded_device)); | |
947 NPT_CHECK_LABEL_SEVERE(res, bad_response); | |
948 } | |
949 | |
950 // Get SCPD of root device services now | |
951 res = root_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator( | |
952 &m_TaskManager, | |
953 this, | |
954 root_device)); | |
955 NPT_CHECK_LABEL_SEVERE(res, bad_response); | |
956 | |
957 return NPT_SUCCESS; | |
958 | |
959 bad_response: | |
960 NPT_LOG_SEVERE_2("Bad Description response for device \"%s\": %s", | |
961 (const char*)device->GetUUID(), | |
962 (const char*)desc); | |
963 | |
964 if (!root_device.IsNull()) { | |
965 NPT_AutoLock lock(m_Devices); | |
966 RemoveDevice(root_device); | |
967 } | |
968 return res; | |
969 } | |
970 | |
971 /*---------------------------------------------------------------------- | |
972 | PLT_CtrlPoint::ProcessGetSCPDResponse | |
973 +---------------------------------------------------------------------*/ | |
974 NPT_Result | |
975 PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result res, | |
976 NPT_HttpRequest* request, | |
977 NPT_HttpResponse* response, | |
978 PLT_DeviceDataReference& device) | |
979 { | |
980 PLT_DeviceReadyIterator device_tester; | |
981 PLT_Service* service = NULL; | |
982 | |
983 NPT_LOG_INFO_2("Received SCPD response for %s (result = %d)", | |
984 (const char*)device->GetUUID(), | |
985 res); | |
986 | |
987 NPT_CHECK_FATAL(res); | |
988 NPT_CHECK_POINTER_FATAL(request); | |
989 NPT_CHECK_POINTER_FATAL(response); | |
990 | |
991 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); | |
992 | |
993 PLT_DeviceDataReference data; | |
994 NPT_CHECK_WARNING(FindDevice(device->GetUUID(), data)); | |
995 | |
996 // get body | |
997 NPT_String scpd; | |
998 res = PLT_HttpHelper::GetBody(*response, scpd); | |
999 NPT_CHECK_LABEL_FATAL(res, bad_response); | |
1000 | |
1001 // look for the service based on the SCPD uri | |
1002 res = data->FindServiceByDescriptionURI(request->GetUrl().GetPath(), service); | |
1003 NPT_CHECK_LABEL_FATAL(res, bad_response); | |
1004 | |
1005 { | |
1006 // lock using listener list before testing | |
1007 // to make sure an scpd is not getting set while we test | |
1008 NPT_AutoLock lock(m_ListenerList); | |
1009 | |
1010 // set the service scpd | |
1011 res = service->SetSCPDXML(scpd); | |
1012 NPT_CHECK_LABEL_FATAL(res, bad_response); | |
1013 | |
1014 if (NPT_SUCCEEDED(device_tester(data))) { | |
1015 // notify that the device is ready to use | |
1016 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data)); | |
1017 } | |
1018 | |
1019 // if device is not root, notify listeners now if parent is ready | |
1020 if (!data->GetParentUUID().IsEmpty()) { | |
1021 PLT_DeviceDataReference parent; | |
1022 NPT_CHECK_WARNING(FindDevice(data->GetParentUUID(), parent)); | |
1023 | |
1024 // lock using listener list before testing | |
1025 // to make sure an scpd is not getting set while we test | |
1026 if (NPT_SUCCEEDED(device_tester(parent))) { | |
1027 // notify that the root device is ready to use | |
1028 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(parent)); | |
1029 } | |
1030 } | |
1031 } | |
1032 | |
1033 return NPT_SUCCESS; | |
1034 | |
1035 bad_response: | |
1036 NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s", | |
1037 (const char*)device->GetFriendlyName(), | |
1038 (const char*)scpd); | |
1039 | |
1040 if (!data.IsNull()) { | |
1041 NPT_AutoLock lock(m_Devices); | |
1042 RemoveDevice(data); | |
1043 } | |
1044 return res; | |
1045 } | |
1046 | |
1047 /*---------------------------------------------------------------------- | |
1048 | PLT_CtrlPoint::Subscribe | |
1049 +---------------------------------------------------------------------*/ | |
1050 NPT_Result | |
1051 PLT_CtrlPoint::Subscribe(PLT_Service* service, bool cancel, void* userdata) | |
1052 { | |
1053 NPT_AutoLock lock(m_Subscribers); | |
1054 | |
1055 if (!service->IsSubscribable()) return NPT_FAILURE; | |
1056 | |
1057 // look for the host and port of the device | |
1058 PLT_DeviceData* device = service->GetDevice(); | |
1059 | |
1060 // get the relative event subscription url | |
1061 // if URL starts with '/', it's not to be appended to base URL | |
1062 NPT_String event_sub_url = service->GetEventSubURL(); | |
1063 if (!event_sub_url.StartsWith("/")) { | |
1064 event_sub_url = device->GetURLBase().GetPath() + event_sub_url; | |
1065 } | |
1066 | |
1067 NPT_HttpUrl url(device->GetURLBase().GetHost(), | |
1068 device->GetURLBase().GetPort(), | |
1069 event_sub_url); | |
1070 | |
1071 // look for the subscriber with that service to decide if it's a renewal or not | |
1072 PLT_EventSubscriber* sub = NULL; | |
1073 NPT_ContainerFind(m_Subscribers, | |
1074 PLT_EventSubscriberFinderByService(service), | |
1075 sub); | |
1076 | |
1077 // create the request | |
1078 NPT_HttpRequest* request = NULL; | |
1079 | |
1080 if (cancel == false) { | |
1081 // renewal? | |
1082 if (sub) { | |
1083 NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"", | |
1084 (const char*)sub->GetSID(), | |
1085 (const char*)service->GetServiceID(), | |
1086 (const char*)device->GetFriendlyName()); | |
1087 | |
1088 // create the request | |
1089 request = new NPT_HttpRequest(url, "SUBSCRIBE"); | |
1090 | |
1091 PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID()); | |
1092 PLT_UPnPMessageHelper::SetTimeOut(*request, 1800); | |
1093 } else { | |
1094 NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"", | |
1095 (const char*)service->GetServiceID(), | |
1096 (const char*)service->GetDevice()->GetFriendlyName()); | |
1097 | |
1098 // prepare the callback url | |
1099 NPT_String uuid = device->GetUUID(); | |
1100 NPT_String service_id = service->GetServiceID(); | |
1101 NPT_String callback_uri = "/" + uuid + "/" + service_id; | |
1102 | |
1103 // create the request | |
1104 request = new NPT_HttpRequest(url, "SUBSCRIBE"); | |
1105 // specify callback url using ip of interface used when | |
1106 // retrieving device description | |
1107 NPT_HttpUrl callbackUrl(device->m_LocalIfaceIp.ToString(), | |
1108 m_EventHttpServer->GetPort(), | |
1109 callback_uri); | |
1110 | |
1111 // set the required headers for a new subscription | |
1112 PLT_UPnPMessageHelper::SetNT(*request, "upnp:event"); | |
1113 PLT_UPnPMessageHelper::SetCallbacks(*request, | |
1114 "<" + callbackUrl.ToString() + ">"); | |
1115 PLT_UPnPMessageHelper::SetTimeOut(*request, 1800); | |
1116 } | |
1117 } else { | |
1118 NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"", | |
1119 (const char*)(sub?sub->GetSID().GetChars():"unknown"), | |
1120 (const char*)service->GetServiceID(), | |
1121 (const char*)service->GetDevice()->GetFriendlyName()); | |
1122 | |
1123 // cancellation | |
1124 if (!sub)return NPT_FAILURE; | |
1125 | |
1126 // create the request | |
1127 request = new NPT_HttpRequest(url, "UNSUBSCRIBE"); | |
1128 PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID()); | |
1129 | |
1130 // remove from list now | |
1131 m_Subscribers.Remove(sub, true); | |
1132 delete sub; | |
1133 } | |
1134 | |
1135 NPT_CHECK_POINTER_FATAL(request); | |
1136 | |
1137 // Prepare the request | |
1138 // create a task to post the request | |
1139 PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask( | |
1140 request, | |
1141 this, | |
1142 service, | |
1143 userdata); | |
1144 m_TaskManager.StartTask(task); | |
1145 | |
1146 return NPT_SUCCESS; | |
1147 } | |
1148 | |
1149 /*---------------------------------------------------------------------- | |
1150 | PLT_CtrlPoint::ProcessSubscribeResponse | |
1151 +---------------------------------------------------------------------*/ | |
1152 NPT_Result | |
1153 PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result res, | |
1154 NPT_HttpResponse* response, | |
1155 PLT_Service* service, | |
1156 void* /* userdata */) | |
1157 { | |
1158 const NPT_String* sid = NULL; | |
1159 NPT_Int32 timeout; | |
1160 PLT_EventSubscriber* sub = NULL; | |
1161 | |
1162 NPT_AutoLock lock(m_Subscribers); | |
1163 | |
1164 NPT_LOG_INFO_2("Received subscription response for service \"%s\" (result = %d)", | |
1165 (const char*)service->GetServiceID(), | |
1166 res); | |
1167 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); | |
1168 | |
1169 // if there's a failure or it's a response to a cancellation | |
1170 // we get out | |
1171 if (NPT_FAILED(res) || response == NULL || response->GetStatusCode() != 200) { | |
1172 NPT_CHECK_LABEL_SEVERE(NPT_FAILED(res)?res:NPT_FAILURE, failure); | |
1173 } | |
1174 | |
1175 if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) || | |
1176 NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, timeout))) { | |
1177 NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure); | |
1178 } | |
1179 | |
1180 // look for the subscriber with that sid | |
1181 if (NPT_FAILED(NPT_ContainerFind(m_Subscribers, | |
1182 PLT_EventSubscriberFinderBySID(*sid), | |
1183 sub))) { | |
1184 NPT_LOG_INFO_3("Creating new subscriber \"%s\" for service \"%s\" of device \"%s\"", | |
1185 (const char*)*sid, | |
1186 (const char*)service->GetServiceID(), | |
1187 (const char*)service->GetDevice()->GetFriendlyName()); | |
1188 | |
1189 sub = new PLT_EventSubscriber(&m_TaskManager, service, *sid); | |
1190 m_Subscribers.Add(sub); | |
1191 } | |
1192 | |
1193 sub->SetTimeout(timeout); | |
1194 return NPT_SUCCESS; | |
1195 | |
1196 failure: | |
1197 NPT_LOG_SEVERE_3("(un)subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"", | |
1198 (const char*)(sid?*sid:"?"), | |
1199 (const char*)service->GetServiceID(), | |
1200 (const char*)service->GetDevice()->GetFriendlyName()); | |
1201 | |
1202 // in case it was a renewal look for the subscriber with that service and remove it from the list | |
1203 if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, | |
1204 PLT_EventSubscriberFinderByService(service), | |
1205 sub))) { | |
1206 m_Subscribers.Remove(sub); | |
1207 delete sub; | |
1208 } | |
1209 | |
1210 return NPT_FAILURE; | |
1211 } | |
1212 | |
1213 /*---------------------------------------------------------------------- | |
1214 | PLT_CtrlPoint::InvokeAction | |
1215 +---------------------------------------------------------------------*/ | |
1216 NPT_Result | |
1217 PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action, | |
1218 void* userdata) | |
1219 { | |
1220 PLT_Service* service = action->GetActionDesc()->GetService(); | |
1221 PLT_DeviceData* device = service->GetDevice(); | |
1222 | |
1223 // look for the service control url | |
1224 NPT_String control_url = service->GetControlURL(); | |
1225 | |
1226 // if URL starts with a "/", it's not to be appended to base URL | |
1227 if (!control_url.StartsWith("/")) { | |
1228 control_url = device->GetURLBase().GetPath() + control_url; | |
1229 } | |
1230 | |
1231 // create the request | |
1232 // FIXME: hack use HTTP/1.0 for now because of WMC that returning 100 Continue when using HTTP/1.1 | |
1233 // and this screws up the http processing right now | |
1234 NPT_HttpUrl url(device->GetURLBase().GetHost(), device->GetURLBase().GetPort(), control_url); | |
1235 NPT_HttpRequest* request = new NPT_HttpRequest(url, "POST", NPT_HTTP_PROTOCOL_1_0); | |
1236 | |
1237 // create a memory stream for our request body | |
1238 NPT_MemoryStreamReference stream(new NPT_MemoryStream); | |
1239 action->FormatSoapRequest(*stream); | |
1240 | |
1241 // set the request body | |
1242 NPT_InputStreamReference input = stream; | |
1243 PLT_HttpHelper::SetBody(*request, input); | |
1244 | |
1245 PLT_HttpHelper::SetContentType(*request, "text/xml; charset=\"utf-8\""); | |
1246 NPT_String service_type = service->GetServiceType(); | |
1247 NPT_String action_name = action->GetActionDesc()->GetName(); | |
1248 request->GetHeaders().SetHeader("SOAPAction", "\"" + service_type + "#" + action_name + "\""); | |
1249 | |
1250 // create a task to post the request | |
1251 PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask( | |
1252 request, | |
1253 this, | |
1254 action, | |
1255 userdata); | |
1256 | |
1257 // queue the request | |
1258 m_TaskManager.StartTask(task); | |
1259 | |
1260 return NPT_SUCCESS; | |
1261 } | |
1262 | |
1263 /*---------------------------------------------------------------------- | |
1264 | PLT_CtrlPoint::ProcessActionResponse | |
1265 +---------------------------------------------------------------------*/ | |
1266 NPT_Result | |
1267 PLT_CtrlPoint::ProcessActionResponse(NPT_Result res, | |
1268 NPT_HttpResponse* response, | |
1269 PLT_ActionReference& action, | |
1270 void* userdata) | |
1271 { | |
1272 NPT_String service_type; | |
1273 NPT_String str; | |
1274 NPT_XmlElementNode* xml = NULL; | |
1275 NPT_String name; | |
1276 NPT_String soap_action_name; | |
1277 NPT_XmlElementNode* soap_action_response; | |
1278 NPT_XmlElementNode* soap_body; | |
1279 NPT_XmlElementNode* fault; | |
1280 const NPT_String* attr = NULL; | |
1281 PLT_ActionDesc* action_desc = action->GetActionDesc(); | |
1282 | |
1283 // reset the error code and desc | |
1284 action->SetError(0, ""); | |
1285 | |
1286 // check context validity | |
1287 if (NPT_FAILED(res) || response == NULL) { | |
1288 goto failure; | |
1289 } | |
1290 | |
1291 NPT_LOG_FINE("Received Action Response:"); | |
1292 PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response); | |
1293 | |
1294 NPT_LOG_FINER("Reading/Parsing Action Response Body..."); | |
1295 if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) { | |
1296 goto failure; | |
1297 } | |
1298 | |
1299 NPT_LOG_FINER("Analyzing Action Response Body..."); | |
1300 | |
1301 // read envelope | |
1302 if (xml->GetTag().Compare("Envelope", true)) | |
1303 goto failure; | |
1304 | |
1305 // check namespace | |
1306 if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/")) | |
1307 goto failure; | |
1308 | |
1309 // check encoding | |
1310 attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/"); | |
1311 if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/")) | |
1312 goto failure; | |
1313 | |
1314 // read action | |
1315 soap_body = PLT_XmlHelper::GetChild(xml, "Body"); | |
1316 if (soap_body == NULL) | |
1317 goto failure; | |
1318 | |
1319 // check if an error occurred | |
1320 fault = PLT_XmlHelper::GetChild(soap_body, "Fault"); | |
1321 if (fault != NULL) { | |
1322 // we have an error | |
1323 ParseFault(action, fault); | |
1324 goto failure; | |
1325 } | |
1326 | |
1327 if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response))) | |
1328 goto failure; | |
1329 | |
1330 // verify action name is identical to SOAPACTION header | |
1331 if (soap_action_response->GetTag().Compare(action_desc->GetName() + "Response", true)) | |
1332 goto failure; | |
1333 | |
1334 // verify namespace | |
1335 if (!soap_action_response->GetNamespace() || | |
1336 soap_action_response->GetNamespace()->Compare(action_desc->GetService()->GetServiceType())) | |
1337 goto failure; | |
1338 | |
1339 // read all the arguments if any | |
1340 for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action_response->GetChildren().GetFirstItem(); args; args++) { | |
1341 NPT_XmlElementNode* child = (*args)->AsElementNode(); | |
1342 if (!child) continue; | |
1343 | |
1344 action->SetArgumentValue(child->GetTag(), child->GetText()?*child->GetText():""); | |
1345 if (NPT_FAILED(res)) goto failure; | |
1346 } | |
1347 | |
1348 // create a buffer for our response body and call the service | |
1349 res = action->VerifyArguments(false); | |
1350 if (NPT_FAILED(res)) goto failure; | |
1351 | |
1352 goto cleanup; | |
1353 | |
1354 failure: | |
1355 // override res with failure if necessary | |
1356 if (NPT_SUCCEEDED(res)) res = NPT_ERROR_INVALID_FORMAT; | |
1357 // fallthrough | |
1358 | |
1359 cleanup: | |
1360 { | |
1361 NPT_AutoLock lock(m_ListenerList); | |
1362 m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata)); | |
1363 } | |
1364 | |
1365 delete xml; | |
1366 return res; | |
1367 } | |
1368 | |
1369 /*---------------------------------------------------------------------- | |
1370 | PLT_CtrlPoint::ParseFault | |
1371 +---------------------------------------------------------------------*/ | |
1372 NPT_Result | |
1373 PLT_CtrlPoint::ParseFault(PLT_ActionReference& action, | |
1374 NPT_XmlElementNode* fault) | |
1375 { | |
1376 NPT_XmlElementNode* detail = fault->GetChild("detail"); | |
1377 if (detail == NULL) return NPT_FAILURE; | |
1378 | |
1379 NPT_XmlElementNode *upnp_error, *error_code, *error_desc; | |
1380 upnp_error = detail->GetChild("upnp_error"); | |
1381 if (upnp_error == NULL) return NPT_FAILURE; | |
1382 | |
1383 error_code = upnp_error->GetChild("errorCode"); | |
1384 error_desc = upnp_error->GetChild("errorDescription"); | |
1385 NPT_Int32 code = 501; | |
1386 NPT_String desc; | |
1387 if (error_code && error_code->GetText()) { | |
1388 NPT_String value = *error_code->GetText(); | |
1389 value.ToInteger(code); | |
1390 } | |
1391 if (error_desc && error_desc->GetText()) { | |
1392 desc = *error_desc->GetText(); | |
1393 } | |
1394 action->SetError(code, desc); | |
1395 return NPT_SUCCESS; | |
1396 } |