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