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