Update gssdp to 0.14.3 (e2f1b0d)
[profile/ivi/GSSDP.git] / libgssdp / gssdp-resource-group.c
1 /* 
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library 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.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gssdp-resource-group
24  * @short_description: Class for controlling resource announcement.
25  *
26  * A #GSSDPResourceGroup is a group of SSDP resources whose availability can
27  * be controlled as one. This is useful when one needs to announce a single
28  * service as multiple SSDP resources (UPnP does this for example).
29  */
30
31 #include <config.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <time.h>
35
36 #include <libsoup/soup.h>
37
38 #include "gssdp-resource-group.h"
39 #include "gssdp-resource-browser.h"
40 #include "gssdp-client-private.h"
41 #include "gssdp-protocol.h"
42
43 G_DEFINE_TYPE (GSSDPResourceGroup,
44                gssdp_resource_group,
45                G_TYPE_OBJECT);
46
47 #define DEFAULT_MAN_HEADER "\"ssdp:discover\""
48
49 struct _GSSDPResourceGroupPrivate {
50         GSSDPClient *client;
51
52         guint        max_age;
53
54         gboolean     available;
55
56         GList       *resources;
57
58         gulong       message_received_id;
59
60         GSource     *timeout_src;
61
62         guint        last_resource_id;
63         
64         guint        message_delay;
65         GQueue      *message_queue;
66         GSource     *message_src;
67 };
68
69 enum {
70         PROP_0,
71         PROP_CLIENT,
72         PROP_MAX_AGE,
73         PROP_AVAILABLE,
74         PROP_MESSAGE_DELAY
75 };
76
77 typedef struct {
78         GSSDPResourceGroup *resource_group;
79
80         GRegex              *target_regex;
81         char                *target;
82         char                *usn;
83         GList               *locations;
84
85         GList               *responses;
86
87         guint                id;
88
89         guint                version;
90
91         gboolean             initial_byebye_sent;
92 } Resource;
93
94 typedef struct {
95         char     *dest_ip;
96         gushort   dest_port;
97         char     *target;
98         Resource *resource;
99
100         GSource  *timeout_src;
101 } DiscoveryResponse;
102
103 #define DEFAULT_MESSAGE_DELAY 120
104 #define DEFAULT_ANNOUNCEMENT_SET_SIZE 3
105 #define VERSION_PATTERN "[0-9]+$"
106
107 /* Function prototypes */
108 static void
109 gssdp_resource_group_set_client (GSSDPResourceGroup *resource_group,
110                                  GSSDPClient        *client);
111 static gboolean
112 resource_group_timeout          (gpointer            user_data);
113 static void
114 message_received_cb             (GSSDPClient        *client,
115                                  const char         *from_ip,
116                                  gushort             from_port,
117                                  _GSSDPMessageType   type,
118                                  SoupMessageHeaders *headers,
119                                  gpointer            user_data);
120 static void
121 resource_alive                  (Resource           *resource);
122 static void
123 resource_byebye                 (Resource           *resource);
124 static void
125 resource_free                   (Resource           *resource);
126 static gboolean
127 discovery_response_timeout      (gpointer            user_data);
128 static void
129 discovery_response_free         (DiscoveryResponse  *response);
130 static gboolean
131 process_queue                   (gpointer            data);
132 static char *
133 get_version_for_target          (char *target);
134 static GRegex *
135 create_target_regex             (const char         *target,
136                                  guint              *version,
137                                  GError            **error);
138 static void
139 send_initial_resource_byebye    (Resource          *resource);
140
141 static void
142 gssdp_resource_group_init (GSSDPResourceGroup *resource_group)
143 {
144         resource_group->priv = G_TYPE_INSTANCE_GET_PRIVATE
145                                         (resource_group,
146                                          GSSDP_TYPE_RESOURCE_GROUP,
147                                          GSSDPResourceGroupPrivate);
148
149         resource_group->priv->max_age = SSDP_DEFAULT_MAX_AGE;
150         resource_group->priv->message_delay = DEFAULT_MESSAGE_DELAY;
151
152         resource_group->priv->message_queue = g_queue_new ();
153 }
154
155 static void
156 gssdp_resource_group_get_property (GObject    *object,
157                                    guint       property_id,
158                                    GValue     *value,
159                                    GParamSpec *pspec)
160 {
161         GSSDPResourceGroup *resource_group;
162
163         resource_group = GSSDP_RESOURCE_GROUP (object);
164
165         switch (property_id) {
166         case PROP_CLIENT:
167                 g_value_set_object
168                         (value,
169                          gssdp_resource_group_get_client (resource_group));
170                 break;
171         case PROP_MAX_AGE:
172                 g_value_set_uint
173                         (value,
174                          gssdp_resource_group_get_max_age (resource_group));
175                 break;
176         case PROP_AVAILABLE:
177                 g_value_set_boolean
178                         (value,
179                          gssdp_resource_group_get_available (resource_group));
180                 break;
181         case PROP_MESSAGE_DELAY:
182                 g_value_set_uint
183                         (value,
184                          gssdp_resource_group_get_message_delay 
185                                 (resource_group));
186                 break;
187         default:
188                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
189                 break;
190         }
191 }
192
193 static void
194 gssdp_resource_group_set_property (GObject      *object,
195                                    guint         property_id,
196                                    const GValue *value,
197                                    GParamSpec   *pspec)
198 {
199         GSSDPResourceGroup *resource_group;
200
201         resource_group = GSSDP_RESOURCE_GROUP (object);
202
203         switch (property_id) {
204         case PROP_CLIENT:
205                 gssdp_resource_group_set_client (resource_group,
206                                                  g_value_get_object (value));
207                 break;
208         case PROP_MAX_AGE:
209                 gssdp_resource_group_set_max_age (resource_group,
210                                                   g_value_get_long (value));
211                 break;
212         case PROP_AVAILABLE:
213                 gssdp_resource_group_set_available
214                         (resource_group, g_value_get_boolean (value));
215                 break;
216         case PROP_MESSAGE_DELAY:
217                 gssdp_resource_group_set_message_delay
218                         (resource_group, g_value_get_uint (value));
219                 break;
220         default:
221                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
222                 break;
223         }
224 }
225
226 static void
227 gssdp_resource_group_dispose (GObject *object)
228 {
229         GSSDPResourceGroup *resource_group;
230         GSSDPResourceGroupPrivate *priv;
231
232         resource_group = GSSDP_RESOURCE_GROUP (object);
233         priv = resource_group->priv;
234
235         while (priv->resources) {
236                 resource_free (priv->resources->data);
237                 priv->resources =
238                         g_list_delete_link (priv->resources,
239                                             priv->resources);
240         }
241
242         if (priv->message_queue) {
243                 /* send messages without usual delay */
244                 while (!g_queue_is_empty (priv->message_queue)) {
245                         if (priv->available)
246                                 process_queue (resource_group);
247                         else
248                                 g_free (g_queue_pop_head
249                                         (priv->message_queue));
250                 }
251
252                 g_queue_free (priv->message_queue);
253                 priv->message_queue = NULL;
254         }
255
256         if (priv->message_src) {
257                 g_source_destroy (priv->message_src);
258                 priv->message_src = NULL;
259         }
260
261         if (priv->timeout_src) {
262                 g_source_destroy (priv->timeout_src);
263                 priv->timeout_src = NULL;
264         }
265
266         if (priv->client) {
267                 if (g_signal_handler_is_connected
268                         (priv->client,
269                          priv->message_received_id)) {
270                         g_signal_handler_disconnect
271                                 (priv->client,
272                                  priv->message_received_id);
273                 }
274                                                    
275                 g_object_unref (priv->client);
276                 priv->client = NULL;
277         }
278
279         G_OBJECT_CLASS (gssdp_resource_group_parent_class)->dispose (object);
280 }
281
282 static void
283 gssdp_resource_group_class_init (GSSDPResourceGroupClass *klass)
284 {
285         GObjectClass *object_class;
286
287         object_class = G_OBJECT_CLASS (klass);
288
289         object_class->set_property = gssdp_resource_group_set_property;
290         object_class->get_property = gssdp_resource_group_get_property;
291         object_class->dispose      = gssdp_resource_group_dispose;
292
293         g_type_class_add_private (klass, sizeof (GSSDPResourceGroupPrivate));
294
295         /**
296          * GSSDPResourceGroup:client:
297          *
298          * The #GSSDPClient to use.
299          **/
300         g_object_class_install_property
301                 (object_class,
302                  PROP_CLIENT,
303                  g_param_spec_object
304                          ("client",
305                           "Client",
306                           "The associated client.",
307                           GSSDP_TYPE_CLIENT,
308                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
309                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
310                           G_PARAM_STATIC_BLURB));
311
312         /**
313          * GSSDPResourceGroup:max-age:
314          *
315          * The number of seconds our advertisements are valid.
316          **/
317         g_object_class_install_property
318                 (object_class,
319                  PROP_MAX_AGE,
320                  g_param_spec_uint
321                          ("max-age",
322                           "Max age",
323                           "The number of seconds advertisements are valid.",
324                           0,
325                           G_MAXUINT,
326                           SSDP_DEFAULT_MAX_AGE,
327                           G_PARAM_READWRITE |
328                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
329                           G_PARAM_STATIC_BLURB));
330
331         /**
332          * GSSDPResourceGroup:available:
333          *
334          * Whether this group of resources is available or not.
335          **/
336         g_object_class_install_property
337                 (object_class,
338                  PROP_AVAILABLE,
339                  g_param_spec_boolean
340                          ("available",
341                           "Available",
342                           "Whether this group of resources is available or "
343                           "not.",
344                           FALSE,
345                           G_PARAM_READWRITE |
346                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
347                           G_PARAM_STATIC_BLURB));
348
349         /**
350          * GSSDPResourceGroup:message-delay:
351          *
352          * The minimum number of milliseconds between SSDP messages.
353          * The default is 120 based on DLNA specification.
354          **/
355         g_object_class_install_property
356                 (object_class,
357                  PROP_MESSAGE_DELAY,
358                  g_param_spec_uint
359                          ("message-delay",
360                           "Message delay",
361                           "The minimum number of milliseconds between SSDP "
362                           "messages.",
363                           0,
364                           G_MAXUINT,
365                           DEFAULT_MESSAGE_DELAY,
366                           G_PARAM_READWRITE |
367                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
368                           G_PARAM_STATIC_BLURB));
369 }
370
371 /**
372  * gssdp_resource_group_new:
373  * @client: The #GSSDPClient to associate with
374  *
375  * Return value: A new #GSSDPResourceGroup object.
376  **/
377 GSSDPResourceGroup *
378 gssdp_resource_group_new (GSSDPClient *client)
379 {
380         return g_object_new (GSSDP_TYPE_RESOURCE_GROUP,
381                              "client", client,
382                              NULL);
383 }
384
385 /*
386  * Sets the #GSSDPClient @resource_group is associated with @client
387  */
388 static void
389 gssdp_resource_group_set_client (GSSDPResourceGroup *resource_group,
390                                  GSSDPClient        *client)
391 {
392         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
393         g_return_if_fail (GSSDP_IS_CLIENT (client));
394
395         resource_group->priv->client = g_object_ref (client);
396
397         resource_group->priv->message_received_id =
398                 g_signal_connect_object (resource_group->priv->client,
399                                          "message-received",
400                                          G_CALLBACK (message_received_cb),
401                                          resource_group,
402                                          0);
403
404         g_object_notify (G_OBJECT (resource_group), "client");
405 }
406
407 /**
408  * gssdp_resource_group_get_client:
409  * @resource_group: A #GSSDPResourceGroup
410  *
411  * Returns: (transfer none): The #GSSDPClient @resource_group is associated with.
412  **/
413 GSSDPClient *
414 gssdp_resource_group_get_client (GSSDPResourceGroup *resource_group)
415 {
416         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), NULL);
417
418         return resource_group->priv->client;
419 }
420
421 /**
422  * gssdp_resource_group_set_max_age:
423  * @resource_group: A #GSSDPResourceGroup
424  * @max_age: The number of seconds advertisements are valid
425  *
426  * Sets the number of seconds advertisements are valid to @max_age.
427  **/
428 void
429 gssdp_resource_group_set_max_age (GSSDPResourceGroup *resource_group,
430                                   guint               max_age)
431 {
432         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
433
434         if (resource_group->priv->max_age == max_age)
435                 return;
436
437         resource_group->priv->max_age = max_age;
438         
439         g_object_notify (G_OBJECT (resource_group), "max-age");
440 }
441
442 /**
443  * gssdp_resource_group_get_max_age:
444  * @resource_group: A #GSSDPResourceGroup
445  *
446  * Return value: The number of seconds advertisements are valid.
447  **/
448 guint
449 gssdp_resource_group_get_max_age (GSSDPResourceGroup *resource_group)
450 {
451         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
452
453         return resource_group->priv->max_age;
454 }
455
456 /**
457  * gssdp_resource_group_set_message_delay:
458  * @resource_group: A #GSSDPResourceGroup
459  * @message_delay: The message delay in ms.
460  *
461  * Sets the minimum time between each SSDP message.
462  **/
463 void
464 gssdp_resource_group_set_message_delay (GSSDPResourceGroup *resource_group,
465                                         guint               message_delay)
466 {
467         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
468
469         if (resource_group->priv->message_delay == message_delay)
470                 return;
471
472         resource_group->priv->message_delay = message_delay;
473         
474         g_object_notify (G_OBJECT (resource_group), "message-delay");
475 }
476
477 /**
478  * gssdp_resource_group_get_message_delay:
479  * @resource_group: A #GSSDPResourceGroup
480  *
481  * Return value: the minimum time between each SSDP message in ms.
482  **/
483 guint
484 gssdp_resource_group_get_message_delay (GSSDPResourceGroup *resource_group)
485 {
486         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
487
488         return resource_group->priv->message_delay;
489 }
490
491 static void
492 send_initial_resource_byebye (Resource *resource)
493 {
494         if (!resource->initial_byebye_sent) {
495                 /* Unannounce before first announce. This is
496                    done to minimize the possibility of
497                    control points thinking that this is just
498                    a reannouncement. */
499                 resource_byebye (resource);
500
501                 resource->initial_byebye_sent = TRUE;
502         }
503 }
504
505 static void
506 send_announcement_set (GList *resources, GFunc message_function)
507 {
508         guint8 i;
509
510         for (i = 0; i < DEFAULT_ANNOUNCEMENT_SET_SIZE; i++) {
511                 g_list_foreach (resources, message_function, NULL);
512         }
513 }
514
515 /**
516  * gssdp_resource_group_set_available:
517  * @resource_group: A #GSSDPResourceGroup
518  * @available: TRUE if @resource_group should be available (advertised)
519  *
520  * Sets @resource_group<!-- -->s availability to @available. Changing
521  * @resource_group<!-- -->s availability causes it to announce its new state
522  * to listening SSDP clients.
523  **/
524 void
525 gssdp_resource_group_set_available (GSSDPResourceGroup *resource_group,
526                                     gboolean            available)
527 {
528         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
529
530         if (resource_group->priv->available == available)
531                 return;
532
533         resource_group->priv->available = available;
534
535         if (available) {
536                 int timeout;
537
538                 /* We want to re-announce at least 3 times before the resource
539                  * group expires to cope with the unrelialble nature of UDP.
540                  *
541                  * Read the paragraphs about 'CACHE-CONTROL' on pages 21-22 of
542                  * UPnP Device Architecture Document v1.1 for further details.
543                  * */
544                 timeout = resource_group->priv->max_age;
545                 if (G_LIKELY (timeout > 6))
546                         timeout = (timeout / 3) - 1;
547
548                 /* Add re-announcement timer */
549                 resource_group->priv->timeout_src =
550                         g_timeout_source_new_seconds (timeout);
551                 g_source_set_callback (resource_group->priv->timeout_src,
552                                        resource_group_timeout,
553                                        resource_group, NULL);
554
555                 g_source_attach (resource_group->priv->timeout_src,
556                                  g_main_context_get_thread_default ());
557
558                 g_source_unref (resource_group->priv->timeout_src);
559
560                 /* Make sure initial byebyes are sent grouped before initial
561                  * alives */
562                 send_announcement_set (resource_group->priv->resources,
563                                        (GFunc) send_initial_resource_byebye);
564
565                 send_announcement_set (resource_group->priv->resources,
566                                        (GFunc) resource_alive);
567         } else {
568                 /* Unannounce all resources */
569                 send_announcement_set (resource_group->priv->resources,
570                                        (GFunc) resource_byebye);
571
572                 /* Remove re-announcement timer */
573                 g_source_destroy (resource_group->priv->timeout_src);
574                 resource_group->priv->timeout_src = NULL;
575         }
576         
577         g_object_notify (G_OBJECT (resource_group), "available");
578 }
579
580 /**
581  * gssdp_resource_group_get_available:
582  * @resource_group: A #GSSDPResourceGroup
583  *
584  * Return value: TRUE if @resource_group is available (advertised).
585  **/
586 gboolean
587 gssdp_resource_group_get_available (GSSDPResourceGroup *resource_group)
588 {
589         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), FALSE);
590
591         return resource_group->priv->available;
592 }
593
594 /**
595  * gssdp_resource_group_add_resource:
596  * @resource_group: An @GSSDPResourceGroup
597  * @target: The resource's target
598  * @usn: The resource's USN
599  * @locations: (element-type utf8): A #GList of the resource's locations
600  *
601  * Adds a resource with target @target, USN @usn, and locations @locations
602  * to @resource_group.
603  *
604  * Return value: The ID of the added resource.
605  **/
606 guint
607 gssdp_resource_group_add_resource (GSSDPResourceGroup *resource_group,
608                                    const char         *target,
609                                    const char         *usn,
610                                    GList              *locations)
611 {
612         Resource *resource;
613         GList *l;
614         GError *error;
615
616         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
617         g_return_val_if_fail (target != NULL, 0);
618         g_return_val_if_fail (usn != NULL, 0);
619         g_return_val_if_fail (locations != NULL, 0);
620
621         resource = g_slice_new0 (Resource);
622
623         resource->resource_group = resource_group;
624
625         resource->target = g_strdup (target);
626         resource->usn    = g_strdup (usn);
627
628         error = NULL;
629         resource->target_regex = create_target_regex (target, &resource->version, &error);
630         if (error) {
631                 g_warning ("Error compiling regular expression for '%s': %s",
632                            target,
633                            error->message);
634
635                 g_error_free (error);
636                 resource_free (resource);
637
638                 return 0;
639         }
640
641         resource->initial_byebye_sent = FALSE;
642
643         for (l = locations; l; l = l->next) {
644                 resource->locations = g_list_append (resource->locations,
645                                                     g_strdup (l->data));
646         }
647
648         resource_group->priv->resources =
649                 g_list_prepend (resource_group->priv->resources, resource);
650
651         resource->id = ++resource_group->priv->last_resource_id;
652
653         if (resource_group->priv->available)
654                 resource_alive (resource);
655
656         return resource->id;
657 }
658
659 /**
660  * gssdp_resource_group_add_resource_simple:
661  * @resource_group: An @GSSDPResourceGroup
662  * @target: The resource's target
663  * @usn: The resource's USN
664  * @location: The resource's location
665  *
666  * Adds a resource with target @target, USN @usn, and location @location
667  * to @resource_group.
668  *
669  * Return value: The ID of the added resource.
670  **/
671 guint
672 gssdp_resource_group_add_resource_simple (GSSDPResourceGroup *resource_group,
673                                           const char         *target,
674                                           const char         *usn,
675                                           const char         *location)
676 {
677         GList *locations = NULL;
678         guint  resource_id;
679
680         locations = g_list_append (locations, (gpointer) location);
681         resource_id = gssdp_resource_group_add_resource (resource_group, target, usn, locations);
682
683         g_list_free (locations);
684
685         return resource_id;
686 }
687
688 /**
689  * gssdp_resource_group_remove_resource:
690  * @resource_group: An @GSSDPResourceGroup
691  * @resource_id: The ID of the resource to remove
692  *
693  * Removes the resource with ID @resource_id from @resource_group.
694  **/
695 void
696 gssdp_resource_group_remove_resource (GSSDPResourceGroup *resource_group,
697                                       guint               resource_id)
698 {
699         GList *l;
700
701         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
702         g_return_if_fail (resource_id > 0);
703
704         for (l = resource_group->priv->resources; l; l = l->next) {
705                 Resource *resource;
706
707                 resource = l->data;
708
709                 if (resource->id == resource_id) {
710                         resource_group->priv->resources = 
711                                 g_list_remove (resource_group->priv->resources,
712                                                resource);
713                         
714                         resource_free (resource);
715
716                         return;
717                 }
718         }
719 }
720
721 /*
722  * Called to re-announce all resources periodically
723  */
724 static gboolean
725 resource_group_timeout (gpointer user_data)
726 {
727         GSSDPResourceGroup *resource_group;
728
729         resource_group = GSSDP_RESOURCE_GROUP (user_data);
730
731         send_announcement_set (resource_group->priv->resources,
732                                (GFunc) resource_alive);
733
734         return TRUE;
735 }
736
737 /*
738  * Received a message
739  */
740 static void
741 message_received_cb (G_GNUC_UNUSED GSSDPClient *client,
742                      const char                *from_ip,
743                      gushort                    from_port,
744                      _GSSDPMessageType          type,
745                      SoupMessageHeaders        *headers,
746                      gpointer                   user_data)
747 {
748         GSSDPResourceGroup *resource_group;
749         const char *target, *mx_str, *version_str, *man;
750         gboolean want_all;
751         int mx, version;
752         GList *l;
753
754         resource_group = GSSDP_RESOURCE_GROUP (user_data);
755
756         /* Only process if we are available */
757         if (!resource_group->priv->available)
758                 return;
759
760         /* We only handle discovery requests */
761         if (type != _GSSDP_DISCOVERY_REQUEST)
762                 return;
763
764         /* Extract target */
765         target = soup_message_headers_get_one (headers, "ST");
766         if (!target) {
767                 g_warning ("Discovery request did not have an ST header");
768
769                 return;
770         }
771
772         /* Is this the "ssdp:all" target? */
773         want_all = (strcmp (target, GSSDP_ALL_RESOURCES) == 0);
774
775         /* Extract MX */
776         mx_str = soup_message_headers_get_one (headers, "MX");
777         if (!mx_str || atoi (mx_str) <= 0) {
778                 g_warning ("Discovery request did not have a valid MX header");
779
780                 return;
781         }
782
783         man = soup_message_headers_get_one (headers, "MAN");
784         if (!man || strcmp (man, DEFAULT_MAN_HEADER) != 0) {
785                 g_warning ("Discovery request did not have a valid MAN header");
786
787                 return;
788         }
789
790         mx = atoi (mx_str);
791
792         /* Extract version */
793         version_str = get_version_for_target ((char *) target);
794         if (version_str != NULL)
795                 version = atoi (version_str);
796         else
797                 version = 0;
798
799         /* Find matching resource */
800         for (l = resource_group->priv->resources; l; l = l->next) {
801                 Resource *resource;
802
803                 resource = l->data;
804
805                 if (want_all ||
806                     (g_regex_match (resource->target_regex,
807                                     target,
808                                     0,
809                                     NULL) &&
810                      (guint) version <= resource->version)) {
811                         /* Match. */
812                         guint timeout;
813                         DiscoveryResponse *response;
814
815                         /* Get a random timeout from the interval [0, mx] */
816                         timeout = g_random_int_range (0, mx * 1000);
817
818                         /* Prepare response */
819                         response = g_slice_new (DiscoveryResponse);
820                         
821                         response->dest_ip   = g_strdup (from_ip);
822                         response->dest_port = from_port;
823                         response->resource  = resource;
824
825                         if (want_all)
826                                 response->target = g_strdup (resource->target);
827                         else
828                                 response->target = g_strdup (target);
829
830                         /* Add timeout */
831                         response->timeout_src = g_timeout_source_new (timeout);
832                         g_source_set_callback (response->timeout_src,
833                                                discovery_response_timeout,
834                                                response, NULL);
835
836                         g_source_attach (response->timeout_src,
837                                          g_main_context_get_thread_default ());
838
839                         g_source_unref (response->timeout_src);
840                         
841                         /* Add to resource */
842                         resource->responses =
843                                 g_list_prepend (resource->responses, response);
844                 }
845         }
846 }
847
848 /*
849  * Construct the AL (Alternative Locations) header for @resource
850  */
851 static char *
852 construct_al (Resource *resource)
853 {
854        if (resource->locations->next) {
855                 GString *al_string;
856                 GList *l;
857
858                 al_string = g_string_new ("AL: ");
859
860                 for (l = resource->locations->next; l; l = l->next) {
861                         g_string_append_c (al_string, '<');
862                         g_string_append (al_string, l->data);
863                         g_string_append_c (al_string, '>');
864                 }
865
866                 g_string_append (al_string, "\r\n");
867
868                 return g_string_free (al_string, FALSE);
869         } else
870                 return NULL; 
871 }
872
873 static char *
874 construct_usn (const char *usn,
875                const char *response_target,
876                const char *resource_target)
877 {
878         const char *needle;
879         char *prefix;
880         char *st;
881
882         needle = strstr (usn, resource_target);
883         if (needle == NULL)
884                 return g_strdup (usn);
885
886         prefix = g_strndup (usn, needle - usn);
887         st = g_strconcat (prefix, response_target, NULL);
888
889         g_free (prefix);
890
891         return st;
892 }
893
894 /*
895  * Send a discovery response
896  */
897 static gboolean
898 discovery_response_timeout (gpointer user_data)
899 {
900         DiscoveryResponse *response;
901         GSSDPClient *client;
902         SoupDate *date;
903         char *al, *date_str, *message;
904         guint max_age;
905         char *usn;
906
907         response = user_data;
908
909         /* Send message */
910         client = response->resource->resource_group->priv->client;
911
912         max_age = response->resource->resource_group->priv->max_age;
913
914         al = construct_al (response->resource);
915         usn = construct_usn (response->resource->usn,
916                              response->target,
917                              response->resource->target);
918         date = soup_date_new_from_now (0);
919         date_str = soup_date_to_string (date, SOUP_DATE_HTTP);
920         soup_date_free (date);
921
922         message = g_strdup_printf (SSDP_DISCOVERY_RESPONSE,
923                                    (char *) response->resource->locations->data,
924                                    al ? al : "",
925                                    usn,
926                                    gssdp_client_get_server_id (client),
927                                    max_age,
928                                    response->target,
929                                    date_str);
930
931         _gssdp_client_send_message (client,
932                                     response->dest_ip,
933                                     response->dest_port,
934                                     message,
935                                     _GSSDP_DISCOVERY_RESPONSE);
936
937         g_free (message);
938         g_free (date_str);
939         g_free (al);
940         g_free (usn);
941
942         discovery_response_free (response);
943
944         return FALSE;
945 }
946
947 /*
948  * Free a DiscoveryResponse structure and its contained data
949  */
950 static void
951 discovery_response_free (DiscoveryResponse *response)
952 {
953         response->resource->responses =
954                 g_list_remove (response->resource->responses, response);
955
956         g_source_destroy (response->timeout_src);
957         
958         g_free (response->dest_ip);
959         g_free (response->target);
960
961         g_slice_free (DiscoveryResponse, response);
962 }
963
964 /*
965  * Send the next queued message, if any
966  */
967 static gboolean
968 process_queue (gpointer data)
969 {
970         GSSDPResourceGroup *resource_group;
971
972         resource_group = GSSDP_RESOURCE_GROUP (data);
973
974         if (g_queue_is_empty (resource_group->priv->message_queue)) {
975                 /* this is the timeout after last message in queue */
976                 resource_group->priv->message_src = NULL;
977
978                 return FALSE;
979         } else {
980                 GSSDPClient *client;
981                 char *message;
982
983                 client = resource_group->priv->client;
984                 message = g_queue_pop_head
985                         (resource_group->priv->message_queue);
986
987                 _gssdp_client_send_message (client,
988                                             NULL,
989                                             0,
990                                             message,
991                                             _GSSDP_DISCOVERY_RESPONSE);
992                 g_free (message);
993
994                 return TRUE;
995         }
996 }
997
998 /*
999  * Add a message to sending queue
1000  * 
1001  * Do not free @message.
1002  */
1003 static void
1004 queue_message (GSSDPResourceGroup *resource_group,
1005                char               *message)
1006 {
1007         g_queue_push_tail (resource_group->priv->message_queue, 
1008                            message);
1009
1010         if (resource_group->priv->message_src == NULL) {
1011                 /* nothing in the queue: process message immediately 
1012                    and add a timeout for (possible) next message */
1013                 process_queue (resource_group);
1014                 resource_group->priv->message_src = g_timeout_source_new (
1015                     resource_group->priv->message_delay);
1016                 g_source_set_callback (resource_group->priv->message_src,
1017                     process_queue, resource_group, NULL);
1018                 g_source_attach (resource_group->priv->message_src,
1019                                  g_main_context_get_thread_default ());
1020                 g_source_unref (resource_group->priv->message_src);
1021         }
1022 }
1023
1024 /*
1025  * Send ssdp:alive message for @resource
1026  */
1027 static void
1028 resource_alive (Resource *resource)
1029 {
1030         GSSDPClient *client;
1031         guint max_age;
1032         char *al, *message;
1033
1034         /* Send initial byebye if not sent already */
1035         send_initial_resource_byebye (resource);
1036
1037         /* Send message */
1038         client = resource->resource_group->priv->client;
1039
1040         max_age = resource->resource_group->priv->max_age;
1041
1042         al = construct_al (resource);
1043
1044         message = g_strdup_printf (SSDP_ALIVE_MESSAGE,
1045                                    max_age,
1046                                    (char *) resource->locations->data,
1047                                    al ? al : "",
1048                                    gssdp_client_get_server_id (client),
1049                                    resource->target,
1050                                    resource->usn);
1051
1052         queue_message (resource->resource_group, message);
1053
1054         g_free (al);
1055 }
1056
1057 /*
1058  * Send ssdp:byebye message for @resource
1059  */
1060 static void
1061 resource_byebye (Resource *resource)
1062 {
1063         char *message;
1064
1065         /* Queue message */
1066         message = g_strdup_printf (SSDP_BYEBYE_MESSAGE,
1067                                    resource->target,
1068                                    resource->usn);
1069
1070         queue_message (resource->resource_group, message);
1071 }
1072
1073 /*
1074  * Free a Resource structure and its contained data
1075  */
1076 static void
1077 resource_free (Resource *resource)
1078 {
1079         while (resource->responses)
1080                 discovery_response_free (resource->responses->data);
1081
1082         if (resource->resource_group->priv->available)
1083                 resource_byebye (resource);
1084
1085         g_free (resource->usn);
1086         g_free (resource->target);
1087
1088         if (resource->target_regex)
1089                 g_regex_unref (resource->target_regex);
1090
1091         while (resource->locations) {
1092                 g_free (resource->locations->data);
1093                 resource->locations = g_list_delete_link (resource->locations,
1094                                                          resource->locations);
1095         }
1096
1097         g_slice_free (Resource, resource);
1098 }
1099
1100 /* Gets you the pointer to the version part in the target string */
1101 static char *
1102 get_version_for_target (char *target)
1103 {
1104         char *version;
1105
1106         if (strncmp (target, "urn:", 4) != 0) {
1107                 /* target is not a URN so no version. */
1108                 return NULL;
1109         }
1110
1111         version = g_strrstr (target, ":") + 1;
1112         if (version == NULL ||
1113             !g_regex_match_simple (VERSION_PATTERN, version, 0, 0))
1114                 return NULL;
1115
1116         return version;
1117 }
1118
1119 static GRegex *
1120 create_target_regex (const char *target, guint *version, GError **error)
1121 {
1122         GRegex *regex;
1123         char *pattern;
1124         char *version_str;
1125
1126         *version = 0;
1127         /* Make sure we have enough room for version pattern */
1128         pattern = g_strndup (target,
1129                              strlen (target) + strlen (VERSION_PATTERN));
1130
1131         version_str = get_version_for_target (pattern);
1132         if (version_str != NULL) {
1133                 *version = atoi (version_str);
1134                 strcpy (version_str, VERSION_PATTERN);
1135         }
1136
1137         regex = g_regex_new (pattern, 0, 0, error);
1138
1139         g_free (pattern);
1140
1141         return regex;
1142 }
1143