21c0575043ec47b9bcfe35b1bdce9941530e9140
[platform/upstream/glib.git] / gio / gsrvtarget.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /* GIO - GLib Input, Output and Streaming Library
4  *
5  * Copyright (C) 2008 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General
18  * Public License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24 #include <glib.h>
25 #include "glibintl.h"
26
27 #include "gsrvtarget.h"
28
29 #include <stdlib.h>
30 #include <string.h>
31
32
33 /**
34  * SECTION:gsrvtarget
35  * @short_description: DNS SRV record target
36  * @include: gio/gio.h
37  *
38  * SRV (service) records are used by some network protocols to provide
39  * service-specific aliasing and load-balancing. For example, XMPP
40  * (Jabber) uses SRV records to locate the XMPP server for a domain;
41  * rather than connecting directly to "example.com" or assuming a
42  * specific server hostname like "xmpp.example.com", an XMPP client
43  * would look up the "xmpp-client" SRV record for "example.com", and
44  * then connect to whatever host was pointed to by that record.
45  *
46  * You can use g_resolver_lookup_service() or
47  * g_resolver_lookup_service_async() to find the #GSrvTarget<!-- -->s
48  * for a given service. However, if you are simply planning to connect
49  * to the remote service, you can use #GNetworkService's
50  * #GSocketConnectable interface and not need to worry about
51  * #GSrvTarget at all.
52  */
53
54 struct _GSrvTarget {
55   gchar   *hostname;
56   guint16  port;
57
58   guint16  priority;
59   guint16  weight;
60 };
61
62 /**
63  * GSrvTarget:
64  *
65  * A single target host/port that a network service is running on.
66  */
67
68 G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
69                      g_srv_target_copy, g_srv_target_free)
70
71 /**
72  * g_srv_target_new:
73  * @hostname: the host that the service is running on
74  * @port: the port that the service is running on
75  * @priority: the target's priority
76  * @weight: the target's weight
77  *
78  * Creates a new #GSrvTarget with the given parameters.
79  *
80  * You should not need to use this; normally #GSrvTarget<!-- -->s are
81  * created by #GResolver.
82  *
83  * Return value: a new #GSrvTarget.
84  *
85  * Since: 2.22
86  */
87 GSrvTarget *
88 g_srv_target_new (const gchar *hostname,
89                   guint16      port,
90                   guint16      priority,
91                   guint16      weight)
92 {
93   GSrvTarget *target = g_slice_new0 (GSrvTarget);
94
95   target->hostname = g_strdup (hostname);
96   target->port = port;
97   target->priority = priority;
98   target->weight = weight;
99
100   return target;
101 }
102
103 /**
104  * g_srv_target_copy:
105  * @target: a #GSrvTarget
106  *
107  * Copies @target
108  *
109  * Return value: a copy of @target
110  *
111  * Since: 2.22
112  */
113 GSrvTarget *
114 g_srv_target_copy (GSrvTarget *target)
115 {
116   return g_srv_target_new (target->hostname, target->port,
117                            target->priority, target->weight);
118 }
119
120 /**
121  * g_srv_target_free:
122  * @target: a #GSrvTarget
123  *
124  * Frees @target
125  *
126  * Since: 2.22
127  */
128 void
129 g_srv_target_free (GSrvTarget *target)
130 {
131   g_free (target->hostname);
132   g_slice_free (GSrvTarget, target);
133 }
134
135 /**
136  * g_srv_target_get_hostname:
137  * @target: a #GSrvTarget
138  *
139  * Gets @target's hostname (in ASCII form; if you are going to present
140  * this to the user, you should use g_hostname_is_ascii_encoded() to
141  * check if it contains encoded Unicode segments, and use
142  * g_hostname_to_unicode() to convert it if it does.)
143  *
144  * Return value: @target's hostname
145  *
146  * Since: 2.22
147  */
148 const gchar *
149 g_srv_target_get_hostname (GSrvTarget *target)
150 {
151   return target->hostname;
152 }
153
154 /**
155  * g_srv_target_get_port:
156  * @target: a #GSrvTarget
157  *
158  * Gets @target's port
159  *
160  * Return value: @target's port
161  *
162  * Since: 2.22
163  */
164 guint16
165 g_srv_target_get_port (GSrvTarget *target)
166 {
167   return target->port;
168 }
169
170 /**
171  * g_srv_target_get_priority:
172  * @target: a #GSrvTarget
173  *
174  * Gets @target's priority. You should not need to look at this;
175  * #GResolver already sorts the targets according to the algorithm in
176  * RFC 2782.
177  *
178  * Return value: @target's priority
179  *
180  * Since: 2.22
181  */
182 guint16
183 g_srv_target_get_priority (GSrvTarget *target)
184 {
185   return target->priority;
186 }
187
188 /**
189  * g_srv_target_get_weight:
190  * @target: a #GSrvTarget
191  *
192  * Gets @target's weight. You should not need to look at this;
193  * #GResolver already sorts the targets according to the algorithm in
194  * RFC 2782.
195  *
196  * Return value: @target's weight
197  *
198  * Since: 2.22
199  */
200 guint16
201 g_srv_target_get_weight (GSrvTarget *target)
202 {
203   return target->weight;
204 }
205
206 gint
207 compare_target (gconstpointer a, gconstpointer b)
208 {
209   GSrvTarget *ta = (GSrvTarget *)a;
210   GSrvTarget *tb = (GSrvTarget *)b;
211
212   if (ta->priority == tb->priority)
213     {
214       /* Arrange targets of the same priority "in any order, except
215        * that all those with weight 0 are placed at the beginning of
216        * the list"
217        */
218       return ta->weight - tb->weight;
219     }
220   else
221     return ta->priority - tb->priority;
222 }
223
224 /**
225  * g_srv_target_list_sort: (skip)
226  * @targets: a #GList of #GSrvTarget
227  *
228  * Sorts @targets in place according to the algorithm in RFC 2782.
229  *
230  * Return value: (transfer full): the head of the sorted list.
231  *
232  * Since: 2.22
233  */
234 GList *
235 g_srv_target_list_sort (GList *targets)
236 {
237   gint sum, num, val, priority, weight;
238   GList *t, *out, *tail;
239   GSrvTarget *target;
240
241   if (!targets)
242     return NULL;
243
244   if (!targets->next)
245     {
246       target = targets->data;
247       if (!strcmp (target->hostname, "."))
248         {
249           /* 'A Target of "." means that the service is decidedly not
250            * available at this domain.'
251            */
252           g_srv_target_free (target);
253           g_list_free (targets);
254           return NULL;
255         }
256     }
257
258   /* Sort input list by priority, and put the 0-weight targets first
259    * in each priority group. Initialize output list to %NULL.
260    */
261   targets = g_list_sort (targets, compare_target);
262   out = tail = NULL;
263
264   /* For each group of targets with the same priority, remove them
265    * from @targets and append them to @out in a valid order.
266    */
267   while (targets)
268     {
269       priority = ((GSrvTarget *)targets->data)->priority;
270
271       /* Count the number of targets at this priority level, and
272        * compute the sum of their weights.
273        */
274       sum = num = 0;
275       for (t = targets; t; t = t->next)
276         {
277           target = (GSrvTarget *)t->data;
278           if (target->priority != priority)
279             break;
280           sum += target->weight;
281           num++;
282         }
283
284       /* While there are still targets at this priority level... */
285       while (num)
286         {
287           /* Randomly select from the targets at this priority level,
288            * giving precedence to the ones with higher weight,
289            * according to the rules from RFC 2782.
290            */
291           val = g_random_int_range (0, sum + 1);
292           for (t = targets; ; t = t->next)
293             {
294               weight = ((GSrvTarget *)t->data)->weight;
295               if (weight >= val)
296                 break;
297               val -= weight;
298             }
299
300           targets = g_list_remove_link (targets, t);
301
302           if (!out)
303             out = t;
304           else
305             tail->next = t;
306           tail = t;
307
308           sum -= weight;
309           num--;
310         }
311     }
312
313   return out;
314 }