1 /*****************************************************************
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
7 | http://www.plutinosoft.com
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.
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
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.
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
33 ****************************************************************/
35 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
39 #include "PltDatagramStream.h"
40 #include "PltDeviceHost.h"
43 #include "PltVersion.h"
45 NPT_SET_LOCAL_LOGGER("platinum.core.ssdp")
47 /*----------------------------------------------------------------------
48 | PLT_SsdpSender::SendSsdp
49 +---------------------------------------------------------------------*/
51 PLT_SsdpSender::SendSsdp(NPT_HttpRequest
& request
,
54 NPT_UdpSocket
& socket
,
56 const NPT_SocketAddress
* addr
/* = NULL */)
58 NPT_CHECK_SEVERE(FormatPacket(request
, usn
, target
, socket
, notify
));
61 NPT_String prefix
= NPT_String::Format("Sending SSDP %s packet for %s",
62 (const char*)request
.GetMethod(),
64 PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER
, prefix
, &request
);
66 // use a memory stream to write all the data
67 NPT_MemoryStream stream
;
68 NPT_Result res
= request
.Emit(stream
);
71 // copy stream into a data packet and send it
74 if (size
!= (NPT_Size
)size
) NPT_CHECK(NPT_ERROR_OUT_OF_RANGE
);
76 NPT_DataBuffer
packet(stream
.GetData(), (NPT_Size
)size
);
77 NPT_CHECK_WARNING(socket
.Send(packet
, addr
));
81 /*----------------------------------------------------------------------
82 | PLT_SsdpSender::SendSsdp
83 +---------------------------------------------------------------------*/
85 PLT_SsdpSender::SendSsdp(NPT_HttpResponse
& response
,
88 NPT_UdpSocket
& socket
,
90 const NPT_SocketAddress
* addr
/* = NULL */)
92 NPT_CHECK_SEVERE(FormatPacket(response
, usn
, target
, socket
, notify
));
95 NPT_String prefix
= NPT_String::Format("Sending SSDP Response:");
96 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER
, prefix
, &response
);
98 // use a memory stream to write all the data
99 NPT_MemoryStream stream
;
100 NPT_Result res
= response
.Emit(stream
);
101 if (NPT_FAILED(res
)) return res
;
103 // copy stream into a data packet and send it
105 stream
.GetSize(size
);
106 if (size
!= (NPT_Size
)size
) NPT_CHECK(NPT_ERROR_OUT_OF_RANGE
);
108 NPT_DataBuffer
packet(stream
.GetData(), (NPT_Size
)size
);
109 NPT_CHECK_WARNING(socket
.Send(packet
, addr
));
113 /*----------------------------------------------------------------------
114 | PLT_SsdpSender::FormatPacket
115 +---------------------------------------------------------------------*/
117 PLT_SsdpSender::FormatPacket(NPT_HttpMessage
& message
,
120 NPT_UdpSocket
& socket
,
123 NPT_COMPILER_UNUSED(socket
);
125 PLT_UPnPMessageHelper::SetUSN(message
, usn
);
127 PLT_UPnPMessageHelper::SetNT(message
, target
);
129 PLT_UPnPMessageHelper::SetST(message
, target
);
130 PLT_UPnPMessageHelper::SetDate(message
);
132 //PLT_HttpHelper::SetContentLength(message, 0);
137 /*----------------------------------------------------------------------
138 | PLT_SsdpDeviceSearchResponseInterfaceIterator class
139 +---------------------------------------------------------------------*/
141 PLT_SsdpDeviceSearchResponseInterfaceIterator::operator()(NPT_NetworkInterface
*& net_if
) const
143 const NPT_SocketAddress
* remote_addr
= &m_RemoteAddr
;
145 NPT_List
<NPT_NetworkInterfaceAddress
>::Iterator niaddr
=
146 net_if
->GetAddresses().GetFirstItem();
147 if (!niaddr
) return NPT_SUCCESS
;
149 // don't try to bind on port 1900 or connect will fail later
150 NPT_UdpSocket
socket(NPT_SOCKET_FLAG_CANCELLABLE
);
151 //socket.Bind(NPT_SocketAddress(NPT_IpAddress::Any, 1900), true);
153 // by connecting, the kernel chooses which interface to use to route to the remote
154 // this is the IP we should use in our Location URL header
155 NPT_CHECK_WARNING(socket
.Connect(m_RemoteAddr
, 5000));
157 socket
.GetInfo(info
);
159 // did we successfully connect and found out which interface is used?
160 if (info
.local_address
.GetIpAddress().AsLong()) {
161 // check that the interface the kernel chose matches the interface
162 // we wanted to send on
163 if ((*niaddr
).GetPrimaryAddress().AsLong() != info
.local_address
.GetIpAddress().AsLong()) {
167 // socket already connected, so we don't need to specify where to go
171 NPT_HttpResponse
response(200, "OK", NPT_HTTP_PROTOCOL_1_1
);
172 PLT_UPnPMessageHelper::SetLocation(response
, m_Device
->GetDescriptionUrl(info
.local_address
.GetIpAddress().ToString()));
173 PLT_UPnPMessageHelper::SetLeaseTime(response
, m_Device
->GetLeaseTime());
174 PLT_UPnPMessageHelper::SetServer(response
, PLT_HTTP_DEFAULT_SERVER
, false);
175 response
.GetHeaders().SetHeader("EXT", "");
177 // process search response twice to be DLNA compliant
178 #if defined(PLATINUM_UPNP_SPECS_STRICT)
180 //NPT_UdpSocket socket;
181 NPT_CHECK_SEVERE(m_Device
->SendSsdpSearchResponse(response
, socket
, m_ST
, remote_addr
));
183 NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY_GROUP
));
186 //NPT_UdpSocket socket;
187 NPT_CHECK_SEVERE(m_Device
->SendSsdpSearchResponse(response
, socket
, m_ST
, remote_addr
));
193 /*----------------------------------------------------------------------
194 | PLT_SsdpDeviceSearchResponseTask::DoRun()
195 +---------------------------------------------------------------------*/
197 PLT_SsdpDeviceSearchResponseTask::DoRun()
199 NPT_List
<NPT_NetworkInterface
*> if_list
;
200 NPT_CHECK_LABEL_WARNING(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list
, true),
203 if_list
.Apply(PLT_SsdpDeviceSearchResponseInterfaceIterator(
207 if_list
.Apply(NPT_ObjectDeleter
<NPT_NetworkInterface
>());
213 /*----------------------------------------------------------------------
214 | PLT_SsdpAnnounceInterfaceIterator class
215 +---------------------------------------------------------------------*/
217 PLT_SsdpAnnounceInterfaceIterator::operator()(NPT_NetworkInterface
*& net_if
) const
219 // don't use this interface address if it's not broadcast capable
220 if (m_Broadcast
&& !(net_if
->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST
)) {
224 NPT_List
<NPT_NetworkInterfaceAddress
>::Iterator niaddr
=
225 net_if
->GetAddresses().GetFirstItem();
226 if (!niaddr
) return NPT_FAILURE
;
228 // Remove disconnected interfaces
229 NPT_IpAddress addr
= (*niaddr
).GetPrimaryAddress();
230 if (!addr
.ToString().Compare("0.0.0.0")) return NPT_FAILURE
;
233 !(net_if
->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST
) &&
234 !(net_if
->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_LOOPBACK
)) {
235 NPT_LOG_INFO_2("Not a valid interface: %s (flags: %d)",
236 (const char*)addr
.ToString(), net_if
->GetFlags());
241 NPT_UdpMulticastSocket
multicast_socket(NPT_SOCKET_FLAG_CANCELLABLE
);
242 NPT_UdpSocket
broadcast_socket(NPT_SOCKET_FLAG_CANCELLABLE
);
243 NPT_UdpSocket
* socket
;
246 url
= NPT_HttpUrl((*niaddr
).GetBroadcastAddress().ToString(), 1900, "*");
247 socket
= &broadcast_socket
;
249 url
= NPT_HttpUrl("239.255.255.250", 1900, "*");
250 NPT_CHECK_SEVERE(multicast_socket
.SetInterface(addr
));
251 socket
= &multicast_socket
;
252 multicast_socket
.SetTimeToLive(PLT_Constants::GetInstance().GetAnnounceMulticastTimeToLive());
255 NPT_HttpRequest
req(url
, "NOTIFY", NPT_HTTP_PROTOCOL_1_1
);
256 PLT_HttpHelper::SetHost(req
, "239.255.255.250:1900");
258 // Location header valid only for ssdp:alive or ssdp:update messages
259 if (m_Type
!= PLT_ANNOUNCETYPE_BYEBYE
) {
260 PLT_UPnPMessageHelper::SetLocation(req
, m_Device
->GetDescriptionUrl(addr
.ToString()));
263 NPT_CHECK_SEVERE(m_Device
->Announce(req
, *socket
, m_Type
));
265 #if defined(PLATINUM_UPNP_SPECS_STRICT)
266 // delay alive only as we don't want to delay when stopping
267 if (m_Type
!= PLT_ANNOUNCETYPE_BYEBYE
) {
268 NPT_System::Sleep(NPT_TimeInterval(PLT_DLNA_SSDP_DELAY_GROUP
));
271 NPT_CHECK_SEVERE(m_Device
->Announce(req
, *socket
, m_Type
));
277 /*----------------------------------------------------------------------
278 | PLT_SsdpDeviceAnnounceUnicastTask::DoRun
279 +---------------------------------------------------------------------*/
281 PLT_SsdpDeviceAnnounceTask::DoRun()
283 NPT_List
<NPT_NetworkInterface
*> if_list
;
286 NPT_CHECK_LABEL_FATAL(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list
, false),
289 // if we're announcing our arrival, sends a byebye first (NMPR compliance)
290 if (m_IsByeByeFirst
== true) {
291 m_IsByeByeFirst
= false;
293 if (m_ExtraBroadcast
) {
294 if_list
.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device
, PLT_ANNOUNCETYPE_BYEBYE
, true));
298 if_list
.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device
, PLT_ANNOUNCETYPE_BYEBYE
, false));
300 // schedule to announce alive in 200 ms
301 if (IsAborting(200)) break;
304 if (m_ExtraBroadcast
) {
305 if_list
.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device
, PLT_ANNOUNCETYPE_ALIVE
, true));
309 if_list
.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device
, PLT_ANNOUNCETYPE_ALIVE
, false));
313 if_list
.Apply(NPT_ObjectDeleter
<NPT_NetworkInterface
>());
316 if (IsAborting((NPT_Timeout
)m_Repeat
.ToMillis())) break;
320 /*----------------------------------------------------------------------
321 | PLT_SsdpListenTask::GetInputStream
322 +---------------------------------------------------------------------*/
324 PLT_SsdpListenTask::GetInputStream(NPT_InputStreamReference
& stream
)
326 if (!m_Datagram
.IsNull()) {
330 NPT_InputStreamReference input_stream
;
331 NPT_Result res
= m_Socket
->GetInputStream(input_stream
);
332 if (NPT_FAILED(res
)) {
335 // for datagrams, we can't simply read from the socket directly
336 // we need to read datagram into a buffer
337 m_Datagram
= new PLT_InputDatagramStream((NPT_UdpSocket
*)m_Socket
);
343 /*----------------------------------------------------------------------
344 | PLT_SsdpListenTask::GetInfo
345 +---------------------------------------------------------------------*/
347 PLT_SsdpListenTask::DoAbort()
349 PLT_HttpServerSocketTask::DoAbort();
352 /*----------------------------------------------------------------------
353 | PLT_SsdpListenTask::GetInfo
354 +---------------------------------------------------------------------*/
356 PLT_SsdpListenTask::GetInfo(NPT_SocketInfo
& info
)
358 if (m_Datagram
.IsNull()) return NPT_FAILURE
;
359 return m_Datagram
->GetInfo(info
);
362 /*----------------------------------------------------------------------
363 | PLT_SsdpListenTask::SetupResponse
364 +---------------------------------------------------------------------*/
366 PLT_SsdpListenTask::SetupResponse(NPT_HttpRequest
& request
,
367 const NPT_HttpRequestContext
& context
,
368 NPT_HttpResponse
& response
)
370 NPT_COMPILER_UNUSED(response
);
372 NPT_AutoLock
lock(m_Mutex
);
373 m_Listeners
.Apply(PLT_SsdpPacketListenerIterator(request
, context
));
375 // return error since we don't have anything to respond
376 // as we use a separate task to respond with ssdp
377 return NPT_ERROR_TERMINATED
;
380 /*----------------------------------------------------------------------
381 | PLT_SsdpSearchTask::PLT_SsdpSearchTask
382 +---------------------------------------------------------------------*/
383 PLT_SsdpSearchTask::PLT_SsdpSearchTask(NPT_UdpSocket
* socket
,
384 PLT_SsdpSearchResponseListener
* listener
,
385 NPT_HttpRequest
* request
,
386 NPT_TimeInterval frequency
) :
387 m_Listener(listener
),
389 m_Frequency(frequency
?frequency
:NPT_TimeInterval(30.)),
390 m_Repeat(frequency
.ToSeconds()!=0),
393 m_Socket
->SetReadTimeout((NPT_Timeout
)m_Frequency
.ToMillis());
394 m_Socket
->SetWriteTimeout(10000);
397 /*----------------------------------------------------------------------
398 | PLT_SsdpSearchTask::~PLT_SsdpSearchTask
399 +---------------------------------------------------------------------*/
400 PLT_SsdpSearchTask::~PLT_SsdpSearchTask()
406 /*----------------------------------------------------------------------
407 | PLT_SsdpSearchTask::DoAbort
408 +---------------------------------------------------------------------*/
410 PLT_SsdpSearchTask::DoAbort()
415 /*----------------------------------------------------------------------
416 | PLT_SsdpSearchTask::DoRun
417 +---------------------------------------------------------------------*/
419 PLT_SsdpSearchTask::DoRun()
421 NPT_HttpResponse
* response
= NULL
;
422 NPT_Timeout timeout
= 30000;
423 NPT_HttpRequestContext context
;
426 // get the address of the server
427 NPT_IpAddress server_address
;
428 NPT_CHECK_LABEL_SEVERE(server_address
.ResolveName(
429 m_Request
->GetUrl().GetHost(),
432 NPT_SocketAddress
address(server_address
,
433 m_Request
->GetUrl().GetPort());
435 // send 2 requests in a row
436 NPT_OutputStreamReference
output_stream(
437 new PLT_OutputDatagramStream(m_Socket
,
440 NPT_CHECK_LABEL_SEVERE(NPT_HttpClient::WriteRequest(
441 *output_stream
.AsPointer(),
445 NPT_CHECK_LABEL_SEVERE(NPT_HttpClient::WriteRequest(
446 *output_stream
.AsPointer(),
450 output_stream
= NULL
;
452 // keep track of when we sent the request
453 NPT_TimeStamp last_send
;
454 NPT_System::GetCurrentTimeStamp(last_send
);
456 while (!IsAborting(0)) {
458 PLT_InputDatagramStreamReference
input_stream(
459 new PLT_InputDatagramStream(m_Socket
));
461 NPT_InputStreamReference stream
= input_stream
;
462 NPT_Result res
= NPT_HttpClient::ReadResponse(
467 // callback to process response
468 if (NPT_SUCCEEDED(res
)) {
471 input_stream
->GetInfo(info
);
473 context
.SetLocalAddress(info
.local_address
);
474 context
.SetRemoteAddress(info
.remote_address
);
477 ProcessResponse(NPT_SUCCESS
, *m_Request
, context
, response
);
480 } else if (res
!= NPT_ERROR_TIMEOUT
) {
481 NPT_LOG_WARNING_1("PLT_SsdpSearchTask got an error (%d) waiting for response", res
);
485 NPT_System::Sleep(NPT_TimeInterval(.15f
));
490 // check if it's time to resend request
492 NPT_System::GetCurrentTimeStamp(now
);
493 if (now
>= last_send
+ m_Frequency
)
496 } while (!IsAborting(0) && m_Repeat
);
502 /*----------------------------------------------------------------------
503 | PLT_CtrlPointGetDescriptionTask::ProcessResponse
504 +---------------------------------------------------------------------*/
506 PLT_SsdpSearchTask::ProcessResponse(NPT_Result res
,
507 const NPT_HttpRequest
& request
,
508 const NPT_HttpRequestContext
& context
,
509 NPT_HttpResponse
* response
)
511 NPT_COMPILER_UNUSED(request
);
512 return m_Listener
->ProcessSsdpSearchResponse(res
, context
, response
);