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