Coverage Report

Created: 2025-06-13 06:21

/src/glib/gio/gsrvtarget.c
Line
Count
Source (jump to first uncovered line)
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
 * SPDX-License-Identifier: LGPL-2.1-or-later
8
 *
9
 * This library is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU Lesser General Public
11
 * License as published by the Free Software Foundation; either
12
 * version 2.1 of the License, or (at your option) any later version.
13
 *
14
 * This library is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General
20
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
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
 * GSrvTarget:
35
 *
36
 * A single target host/port that a network service is running on.
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 [method@Gio.Resolver.lookup_service] or
47
 * [method@Gio.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 [class@Gio.NetworkService]’s
50
 * [iface@Gio.SocketConnectable] 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
G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
63
                     g_srv_target_copy, g_srv_target_free)
64
65
/**
66
 * g_srv_target_new:
67
 * @hostname: the host that the service is running on
68
 * @port: the port that the service is running on
69
 * @priority: the target's priority
70
 * @weight: the target's weight
71
 *
72
 * Creates a new #GSrvTarget with the given parameters.
73
 *
74
 * You should not need to use this; normally #GSrvTargets are
75
 * created by #GResolver.
76
 *
77
 * Returns: a new #GSrvTarget.
78
 *
79
 * Since: 2.22
80
 */
81
GSrvTarget *
82
g_srv_target_new (const gchar *hostname,
83
                  guint16      port,
84
                  guint16      priority,
85
                  guint16      weight)
86
0
{
87
0
  GSrvTarget *target = g_slice_new0 (GSrvTarget);
88
89
0
  target->hostname = g_strdup (hostname);
90
0
  target->port = port;
91
0
  target->priority = priority;
92
0
  target->weight = weight;
93
94
0
  return target;
95
0
}
96
97
/**
98
 * g_srv_target_copy:
99
 * @target: a #GSrvTarget
100
 *
101
 * Copies @target
102
 *
103
 * Returns: a copy of @target
104
 *
105
 * Since: 2.22
106
 */
107
GSrvTarget *
108
g_srv_target_copy (GSrvTarget *target)
109
0
{
110
0
  return g_srv_target_new (target->hostname, target->port,
111
0
                           target->priority, target->weight);
112
0
}
113
114
/**
115
 * g_srv_target_free:
116
 * @target: a #GSrvTarget
117
 *
118
 * Frees @target
119
 *
120
 * Since: 2.22
121
 */
122
void
123
g_srv_target_free (GSrvTarget *target)
124
0
{
125
0
  g_free (target->hostname);
126
0
  g_slice_free (GSrvTarget, target);
127
0
}
128
129
/**
130
 * g_srv_target_get_hostname:
131
 * @target: a #GSrvTarget
132
 *
133
 * Gets @target's hostname (in ASCII form; if you are going to present
134
 * this to the user, you should use g_hostname_is_ascii_encoded() to
135
 * check if it contains encoded Unicode segments, and use
136
 * g_hostname_to_unicode() to convert it if it does.)
137
 *
138
 * Returns: @target's hostname
139
 *
140
 * Since: 2.22
141
 */
142
const gchar *
143
g_srv_target_get_hostname (GSrvTarget *target)
144
0
{
145
0
  return target->hostname;
146
0
}
147
148
/**
149
 * g_srv_target_get_port:
150
 * @target: a #GSrvTarget
151
 *
152
 * Gets @target's port
153
 *
154
 * Returns: @target's port
155
 *
156
 * Since: 2.22
157
 */
158
guint16
159
g_srv_target_get_port (GSrvTarget *target)
160
0
{
161
0
  return target->port;
162
0
}
163
164
/**
165
 * g_srv_target_get_priority:
166
 * @target: a #GSrvTarget
167
 *
168
 * Gets @target's priority. You should not need to look at this;
169
 * #GResolver already sorts the targets according to the algorithm in
170
 * RFC 2782.
171
 *
172
 * Returns: @target's priority
173
 *
174
 * Since: 2.22
175
 */
176
guint16
177
g_srv_target_get_priority (GSrvTarget *target)
178
0
{
179
0
  return target->priority;
180
0
}
181
182
/**
183
 * g_srv_target_get_weight:
184
 * @target: a #GSrvTarget
185
 *
186
 * Gets @target's weight. You should not need to look at this;
187
 * #GResolver already sorts the targets according to the algorithm in
188
 * RFC 2782.
189
 *
190
 * Returns: @target's weight
191
 *
192
 * Since: 2.22
193
 */
194
guint16
195
g_srv_target_get_weight (GSrvTarget *target)
196
0
{
197
0
  return target->weight;
198
0
}
199
200
static gint
201
compare_target (gconstpointer a, gconstpointer b)
202
0
{
203
0
  GSrvTarget *ta = (GSrvTarget *)a;
204
0
  GSrvTarget *tb = (GSrvTarget *)b;
205
206
0
  if (ta->priority == tb->priority)
207
0
    {
208
      /* Arrange targets of the same priority "in any order, except
209
       * that all those with weight 0 are placed at the beginning of
210
       * the list"
211
       */
212
0
      return ta->weight - tb->weight;
213
0
    }
214
0
  else
215
0
    return ta->priority - tb->priority;
216
0
}
217
218
/**
219
 * g_srv_target_list_sort: (skip)
220
 * @targets: a #GList of #GSrvTarget
221
 *
222
 * Sorts @targets in place according to the algorithm in RFC 2782.
223
 *
224
 * Returns: (transfer full): the head of the sorted list.
225
 *
226
 * Since: 2.22
227
 */
228
GList *
229
g_srv_target_list_sort (GList *targets)
230
0
{
231
0
  gint sum, num, val, priority, weight;
232
0
  GList *t, *out, *tail;
233
0
  GSrvTarget *target;
234
235
0
  if (!targets)
236
0
    return NULL;
237
238
0
  if (!targets->next)
239
0
    {
240
0
      target = targets->data;
241
0
      if (!strcmp (target->hostname, "."))
242
0
        {
243
          /* 'A Target of "." means that the service is decidedly not
244
           * available at this domain.'
245
           */
246
0
          g_srv_target_free (target);
247
0
          g_list_free (targets);
248
0
          return NULL;
249
0
        }
250
0
    }
251
252
  /* Sort input list by priority, and put the 0-weight targets first
253
   * in each priority group. Initialize output list to %NULL.
254
   */
255
0
  targets = g_list_sort (targets, compare_target);
256
0
  out = tail = NULL;
257
258
  /* For each group of targets with the same priority, remove them
259
   * from @targets and append them to @out in a valid order.
260
   */
261
0
  while (targets)
262
0
    {
263
0
      priority = ((GSrvTarget *)targets->data)->priority;
264
265
      /* Count the number of targets at this priority level, and
266
       * compute the sum of their weights.
267
       */
268
0
      sum = num = 0;
269
0
      for (t = targets; t; t = t->next)
270
0
        {
271
0
          target = (GSrvTarget *)t->data;
272
0
          if (target->priority != priority)
273
0
            break;
274
0
          sum += target->weight;
275
0
          num++;
276
0
        }
277
278
      /* While there are still targets at this priority level... */
279
0
      while (num)
280
0
        {
281
          /* Randomly select from the targets at this priority level,
282
           * giving precedence to the ones with higher weight,
283
           * according to the rules from RFC 2782.
284
           */
285
0
          val = g_random_int_range (0, sum + 1);
286
0
          for (t = targets; ; t = t->next)
287
0
            {
288
0
              g_assert (t != NULL && t->data != NULL);
289
0
              weight = ((GSrvTarget *)t->data)->weight;
290
0
              if (weight >= val)
291
0
                break;
292
0
              val -= weight;
293
0
            }
294
295
0
          targets = g_list_remove_link (targets, t);
296
297
0
          if (!out)
298
0
            out = t;
299
0
          else
300
0
            tail->next = t;
301
0
          tail = t;
302
303
0
          sum -= weight;
304
0
          num--;
305
0
        }
306
0
    }
307
308
0
  return out;
309
0
}