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