View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.protocol.async;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.List;
21  import java.util.Random;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  
25  import org.fourthline.cling.UpnpService;
26  import org.fourthline.cling.model.DiscoveryOptions;
27  import org.fourthline.cling.model.Location;
28  import org.fourthline.cling.model.NetworkAddress;
29  import org.fourthline.cling.model.message.IncomingDatagramMessage;
30  import org.fourthline.cling.model.message.UpnpRequest;
31  import org.fourthline.cling.model.message.discovery.IncomingSearchRequest;
32  import org.fourthline.cling.model.message.discovery.OutgoingSearchResponse;
33  import org.fourthline.cling.model.message.discovery.OutgoingSearchResponseDeviceType;
34  import org.fourthline.cling.model.message.discovery.OutgoingSearchResponseRootDevice;
35  import org.fourthline.cling.model.message.discovery.OutgoingSearchResponseServiceType;
36  import org.fourthline.cling.model.message.discovery.OutgoingSearchResponseUDN;
37  import org.fourthline.cling.model.message.header.DeviceTypeHeader;
38  import org.fourthline.cling.model.message.header.MXHeader;
39  import org.fourthline.cling.model.message.header.RootDeviceHeader;
40  import org.fourthline.cling.model.message.header.STAllHeader;
41  import org.fourthline.cling.model.message.header.ServiceTypeHeader;
42  import org.fourthline.cling.model.message.header.UDNHeader;
43  import org.fourthline.cling.model.message.header.UpnpHeader;
44  import org.fourthline.cling.model.meta.Device;
45  import org.fourthline.cling.model.meta.LocalDevice;
46  import org.fourthline.cling.model.types.DeviceType;
47  import org.fourthline.cling.model.types.ServiceType;
48  import org.fourthline.cling.model.types.UDN;
49  import org.fourthline.cling.protocol.ReceivingAsync;
50  import org.fourthline.cling.transport.RouterException;
51  
52  /**
53   * Handles reception of search requests, responds for local registered devices.
54   * <p>
55   * Waits a random time between 0 and the requested <em>MX</em> (maximum 120 seconds)
56   * before executing. Only waits if there are actually any registered local devices.
57   * </p>
58   * <p>
59   * Extracts the <em>search target</em>, builds and sends the dozens of messages
60   * required by the UPnP specification, depending on the search target and what
61   * local devices and services are found in the {@link org.fourthline.cling.registry.Registry}.
62   * </p>
63   *
64   * @author Christian Bauer
65   */
66  public class ReceivingSearch extends ReceivingAsync<IncomingSearchRequest> {
67  
68      final private static Logger log = Logger.getLogger(ReceivingSearch.class.getName());
69  
70      private static final boolean LOG_ENABLED = log.isLoggable(Level.FINE);
71  
72      final protected Random randomGenerator = new Random();
73  
74      public ReceivingSearch(UpnpService upnpService, IncomingDatagramMessage<UpnpRequest> inputMessage) {
75          super(upnpService, new IncomingSearchRequest(inputMessage));
76      }
77  
78      protected void execute() throws RouterException {
79          if (getUpnpService().getRouter() == null) {
80              // TODO: http://mailinglists.945824.n3.nabble.com/rare-NPE-on-start-tp3078213p3142767.html
81              log.fine("Router hasn't completed initialization, ignoring received search message");
82              return;
83          }
84  
85          if (!getInputMessage().isMANSSDPDiscover()) {
86              log.fine("Invalid search request, no or invalid MAN ssdp:discover header: " + getInputMessage());
87              return;
88          }
89  
90          UpnpHeader searchTarget = getInputMessage().getSearchTarget();
91  
92          if (searchTarget == null) {
93              log.fine("Invalid search request, did not contain ST header: " + getInputMessage());
94              return;
95          }
96  
97          List<NetworkAddress> activeStreamServers =
98              getUpnpService().getRouter().getActiveStreamServers(getInputMessage().getLocalAddress());
99          if (activeStreamServers.size() == 0) {
100             log.fine("Aborting search response, no active stream servers found (network disabled?)");
101             return;
102         }
103 
104         for (NetworkAddress activeStreamServer : activeStreamServers) {
105             sendResponses(searchTarget, activeStreamServer);
106         }
107     }
108 
109     @Override
110     protected boolean waitBeforeExecution() throws InterruptedException {
111 
112         Integer mx = getInputMessage().getMX();
113 
114         if (mx == null) {
115             log.fine("Invalid search request, did not contain MX header: " + getInputMessage());
116             return false;
117         }
118 
119         // Spec says we should assume "less" if it's 120 or more
120         // From the spec, MX should be "greater than or equal to 1"
121         // Prevent negative MX to make nextInt() throw IllegalArgumentException below
122         if (mx > 120 || mx <= 0) mx = MXHeader.DEFAULT_VALUE;
123 
124         // Only wait if there is something to wait for
125         if (getUpnpService().getRegistry().getLocalDevices().size() > 0) {
126             int sleepTime = randomGenerator.nextInt(mx * 1000);
127             log.fine("Sleeping " + sleepTime + " milliseconds to avoid flooding with search responses");
128             Thread.sleep(sleepTime);
129         }
130 
131         return true;
132     }
133     
134     protected void sendResponses(UpnpHeader searchTarget, NetworkAddress activeStreamServer) throws RouterException {
135         if (searchTarget instanceof STAllHeader) {
136 
137             sendSearchResponseAll(activeStreamServer);
138 
139         } else if (searchTarget instanceof RootDeviceHeader) {
140 
141             sendSearchResponseRootDevices(activeStreamServer);
142 
143         } else if (searchTarget instanceof UDNHeader) {
144 
145             sendSearchResponseUDN((UDN) searchTarget.getValue(), activeStreamServer);
146 
147         } else if (searchTarget instanceof DeviceTypeHeader) {
148 
149             sendSearchResponseDeviceType((DeviceType) searchTarget.getValue(), activeStreamServer);
150 
151         } else if (searchTarget instanceof ServiceTypeHeader) {
152 
153             sendSearchResponseServiceType((ServiceType) searchTarget.getValue(), activeStreamServer);
154 
155         } else {
156             log.warning("Non-implemented search request target: " + searchTarget.getClass());
157         }
158     }
159 
160     protected void sendSearchResponseAll(NetworkAddress activeStreamServer) throws RouterException {
161         if (LOG_ENABLED) {
162             log.fine("Responding to 'all' search with advertisement messages for all local devices");
163         }
164         for (LocalDevice localDevice : getUpnpService().getRegistry().getLocalDevices()) {
165 
166             if (isAdvertisementDisabled(localDevice))
167                 continue;
168 
169             // We are re-using the regular notification messages here but override the NT with the ST header
170             if (LOG_ENABLED) {
171                 log.finer("Sending root device messages: " + localDevice);
172             }
173             List<OutgoingSearchResponse> rootDeviceMsgs =
174                     createDeviceMessages(localDevice, activeStreamServer);
175             for (OutgoingSearchResponse upnpMessage : rootDeviceMsgs) {
176                 getUpnpService().getRouter().send(upnpMessage);
177             }
178 
179             if (localDevice.hasEmbeddedDevices()) {
180                 for (LocalDevice embeddedDevice : localDevice.findEmbeddedDevices()) {
181                     if (LOG_ENABLED) {
182                         log.finer("Sending embedded device messages: " + embeddedDevice);
183                     }
184                     List<OutgoingSearchResponse> embeddedDeviceMsgs =
185                             createDeviceMessages(embeddedDevice, activeStreamServer);
186                     for (OutgoingSearchResponse upnpMessage : embeddedDeviceMsgs) {
187                         getUpnpService().getRouter().send(upnpMessage);
188                     }
189                 }
190             }
191 
192             List<OutgoingSearchResponse> serviceTypeMsgs =
193                     createServiceTypeMessages(localDevice, activeStreamServer);
194             if (serviceTypeMsgs.size() > 0) {
195                 if (LOG_ENABLED) {
196                     log.finer("Sending service type messages");
197                 }
198                 for (OutgoingSearchResponse upnpMessage : serviceTypeMsgs) {
199                     getUpnpService().getRouter().send(upnpMessage);
200                 }
201             }
202 
203         }
204     }
205 
206     protected List<OutgoingSearchResponse> createDeviceMessages(LocalDevice device,
207                                                                 NetworkAddress activeStreamServer) {
208         List<OutgoingSearchResponse> msgs = new ArrayList<>();
209 
210         // See the tables in UDA 1.0 section 1.1.2
211 
212         if (device.isRoot()) {
213             msgs.add(
214                     new OutgoingSearchResponseRootDevice(
215                             getInputMessage(),
216                             getDescriptorLocation(activeStreamServer, device),
217                             device
218                     )
219             );
220         }
221 
222         msgs.add(
223                 new OutgoingSearchResponseUDN(
224                         getInputMessage(),
225                         getDescriptorLocation(activeStreamServer, device),
226                         device
227                 )
228         );
229 
230         msgs.add(
231                 new OutgoingSearchResponseDeviceType(
232                         getInputMessage(),
233                         getDescriptorLocation(activeStreamServer, device),
234                         device
235                 )
236         );
237 
238         for (OutgoingSearchResponse msg : msgs) {
239             prepareOutgoingSearchResponse(msg);
240         }
241 
242         return msgs;
243     }
244 
245     protected List<OutgoingSearchResponse> createServiceTypeMessages(LocalDevice device,
246                                                                      NetworkAddress activeStreamServer) {
247         List<OutgoingSearchResponse> msgs = new ArrayList<>();
248         for (ServiceType serviceType : device.findServiceTypes()) {
249             OutgoingSearchResponse message =
250                 new OutgoingSearchResponseServiceType(
251                         getInputMessage(),
252                         getDescriptorLocation(activeStreamServer, device),
253                         device,
254                         serviceType
255                 );
256             prepareOutgoingSearchResponse(message);
257             msgs.add(message);
258         }
259         return msgs;
260     }
261 
262     protected void sendSearchResponseRootDevices(NetworkAddress activeStreamServer) throws RouterException {
263         log.fine("Responding to root device search with advertisement messages for all local root devices");
264         for (LocalDevice device : getUpnpService().getRegistry().getLocalDevices()) {
265 
266             if (isAdvertisementDisabled(device))
267                 continue;
268 
269             OutgoingSearchResponse message =
270                 new OutgoingSearchResponseRootDevice(
271                         getInputMessage(),
272                         getDescriptorLocation(activeStreamServer, device),
273                         device
274                 );
275             prepareOutgoingSearchResponse(message);
276             getUpnpService().getRouter().send(message);
277         }
278     }
279 
280     protected void sendSearchResponseUDN(UDN udn, NetworkAddress activeStreamServer) throws RouterException {
281         Device device = getUpnpService().getRegistry().getDevice(udn, false);
282         if (device != null && device instanceof LocalDevice) {
283 
284             if (isAdvertisementDisabled((LocalDevice)device))
285                 return;
286 
287             log.fine("Responding to UDN device search: " + udn);
288             OutgoingSearchResponse message =
289                 new OutgoingSearchResponseUDN(
290                         getInputMessage(),
291                         getDescriptorLocation(activeStreamServer, (LocalDevice) device),
292                         (LocalDevice) device
293                 );
294             prepareOutgoingSearchResponse(message);
295             getUpnpService().getRouter().send(message);
296         }
297     }
298 
299     protected void sendSearchResponseDeviceType(DeviceType deviceType, NetworkAddress activeStreamServer) throws RouterException{
300         log.fine("Responding to device type search: " + deviceType);
301         Collection<Device> devices = getUpnpService().getRegistry().getDevices(deviceType);
302         for (Device device : devices) {
303             if (device instanceof LocalDevice) {
304 
305                 if (isAdvertisementDisabled((LocalDevice)device))
306                     continue;
307 
308                 log.finer("Sending matching device type search result for: " + device);
309                 OutgoingSearchResponse message =
310                     new OutgoingSearchResponseDeviceType(
311                             getInputMessage(),
312                             getDescriptorLocation(activeStreamServer, (LocalDevice) device),
313                             (LocalDevice) device
314                     );
315                 prepareOutgoingSearchResponse(message);
316                 getUpnpService().getRouter().send(message);
317             }
318         }
319     }
320 
321     protected void sendSearchResponseServiceType(ServiceType serviceType, NetworkAddress activeStreamServer) throws RouterException {
322         log.fine("Responding to service type search: " + serviceType);
323         Collection<Device> devices = getUpnpService().getRegistry().getDevices(serviceType);
324         for (Device device : devices) {
325             if (device instanceof LocalDevice) {
326 
327                 if (isAdvertisementDisabled((LocalDevice)device))
328                     continue;
329 
330                 log.finer("Sending matching service type search result: " + device);
331                 OutgoingSearchResponse message =
332                     new OutgoingSearchResponseServiceType(
333                             getInputMessage(),
334                             getDescriptorLocation(activeStreamServer, (LocalDevice) device),
335                             (LocalDevice) device,
336                             serviceType
337                     );
338                 prepareOutgoingSearchResponse(message);
339                 getUpnpService().getRouter().send(message);
340             }
341         }
342     }
343 
344     protected Location getDescriptorLocation(NetworkAddress activeStreamServer, LocalDevice device) {
345         return new Location(
346                 activeStreamServer,
347                 getUpnpService().getConfiguration().getNamespace().getDescriptorPathString(device)
348         );
349     }
350 
351     protected boolean isAdvertisementDisabled(LocalDevice device) {
352         DiscoveryOptions options =
353             getUpnpService().getRegistry().getDiscoveryOptions(device.getIdentity().getUdn());
354         return options != null && !options.isAdvertised();
355     }
356 
357     /**
358      * Override this to edit the outgoing message, e.g. by adding headers.
359      */
360     protected void prepareOutgoingSearchResponse(OutgoingSearchResponse message) {
361     }
362 
363 }