glib/gio/gsrvtarget.c
Philip Withnall a1ff120a98
gsrvtarget: Silence false positive NULL pointer dereference
scan-build thinks there could be a `NULL` pointer dereference of
`t->data` here. It’s wrong, so add an assertion to try and help it
understand the control flow.

The loop is exited as soon as a target is found whose weight is greater
than or equal to a random value between 0 and the sum of all the weights
in the set of remaining targets in the loop. By definition, the last
target in the loop always satisfies this condition, so a target will
always be chosen, and hence `t` will never be `NULL` within the loop.

`t->data` will never be `NULL` by construction of the target list.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Helps: #1767
2024-04-25 23:57:41 +01:00

310 lines
7.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2008 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <glib.h>
#include "glibintl.h"
#include "gsrvtarget.h"
#include <stdlib.h>
#include <string.h>
/**
* GSrvTarget:
*
* A single target host/port that a network service is running on.
*
* SRV (service) records are used by some network protocols to provide
* service-specific aliasing and load-balancing. For example, XMPP
* (Jabber) uses SRV records to locate the XMPP server for a domain;
* rather than connecting directly to example.com or assuming a
* specific server hostname like xmpp.example.com, an XMPP client
* would look up the `xmpp-client` SRV record for example.com, and
* then connect to whatever host was pointed to by that record.
*
* You can use [method@Gio.Resolver.lookup_service] or
* [method@Gio.Resolver.lookup_service_async] to find the `GSrvTarget`s
* for a given service. However, if you are simply planning to connect
* to the remote service, you can use [class@Gio.NetworkService]s
* [iface@Gio.SocketConnectable] interface and not need to worry about
* `GSrvTarget` at all.
*/
struct _GSrvTarget {
gchar *hostname;
guint16 port;
guint16 priority;
guint16 weight;
};
G_DEFINE_BOXED_TYPE (GSrvTarget, g_srv_target,
g_srv_target_copy, g_srv_target_free)
/**
* g_srv_target_new:
* @hostname: the host that the service is running on
* @port: the port that the service is running on
* @priority: the target's priority
* @weight: the target's weight
*
* Creates a new #GSrvTarget with the given parameters.
*
* You should not need to use this; normally #GSrvTargets are
* created by #GResolver.
*
* Returns: a new #GSrvTarget.
*
* Since: 2.22
*/
GSrvTarget *
g_srv_target_new (const gchar *hostname,
guint16 port,
guint16 priority,
guint16 weight)
{
GSrvTarget *target = g_slice_new0 (GSrvTarget);
target->hostname = g_strdup (hostname);
target->port = port;
target->priority = priority;
target->weight = weight;
return target;
}
/**
* g_srv_target_copy:
* @target: a #GSrvTarget
*
* Copies @target
*
* Returns: a copy of @target
*
* Since: 2.22
*/
GSrvTarget *
g_srv_target_copy (GSrvTarget *target)
{
return g_srv_target_new (target->hostname, target->port,
target->priority, target->weight);
}
/**
* g_srv_target_free:
* @target: a #GSrvTarget
*
* Frees @target
*
* Since: 2.22
*/
void
g_srv_target_free (GSrvTarget *target)
{
g_free (target->hostname);
g_slice_free (GSrvTarget, target);
}
/**
* g_srv_target_get_hostname:
* @target: a #GSrvTarget
*
* Gets @target's hostname (in ASCII form; if you are going to present
* this to the user, you should use g_hostname_is_ascii_encoded() to
* check if it contains encoded Unicode segments, and use
* g_hostname_to_unicode() to convert it if it does.)
*
* Returns: @target's hostname
*
* Since: 2.22
*/
const gchar *
g_srv_target_get_hostname (GSrvTarget *target)
{
return target->hostname;
}
/**
* g_srv_target_get_port:
* @target: a #GSrvTarget
*
* Gets @target's port
*
* Returns: @target's port
*
* Since: 2.22
*/
guint16
g_srv_target_get_port (GSrvTarget *target)
{
return target->port;
}
/**
* g_srv_target_get_priority:
* @target: a #GSrvTarget
*
* Gets @target's priority. You should not need to look at this;
* #GResolver already sorts the targets according to the algorithm in
* RFC 2782.
*
* Returns: @target's priority
*
* Since: 2.22
*/
guint16
g_srv_target_get_priority (GSrvTarget *target)
{
return target->priority;
}
/**
* g_srv_target_get_weight:
* @target: a #GSrvTarget
*
* Gets @target's weight. You should not need to look at this;
* #GResolver already sorts the targets according to the algorithm in
* RFC 2782.
*
* Returns: @target's weight
*
* Since: 2.22
*/
guint16
g_srv_target_get_weight (GSrvTarget *target)
{
return target->weight;
}
static gint
compare_target (gconstpointer a, gconstpointer b)
{
GSrvTarget *ta = (GSrvTarget *)a;
GSrvTarget *tb = (GSrvTarget *)b;
if (ta->priority == tb->priority)
{
/* Arrange targets of the same priority "in any order, except
* that all those with weight 0 are placed at the beginning of
* the list"
*/
return ta->weight - tb->weight;
}
else
return ta->priority - tb->priority;
}
/**
* g_srv_target_list_sort: (skip)
* @targets: a #GList of #GSrvTarget
*
* Sorts @targets in place according to the algorithm in RFC 2782.
*
* Returns: (transfer full): the head of the sorted list.
*
* Since: 2.22
*/
GList *
g_srv_target_list_sort (GList *targets)
{
gint sum, num, val, priority, weight;
GList *t, *out, *tail;
GSrvTarget *target;
if (!targets)
return NULL;
if (!targets->next)
{
target = targets->data;
if (!strcmp (target->hostname, "."))
{
/* 'A Target of "." means that the service is decidedly not
* available at this domain.'
*/
g_srv_target_free (target);
g_list_free (targets);
return NULL;
}
}
/* Sort input list by priority, and put the 0-weight targets first
* in each priority group. Initialize output list to %NULL.
*/
targets = g_list_sort (targets, compare_target);
out = tail = NULL;
/* For each group of targets with the same priority, remove them
* from @targets and append them to @out in a valid order.
*/
while (targets)
{
priority = ((GSrvTarget *)targets->data)->priority;
/* Count the number of targets at this priority level, and
* compute the sum of their weights.
*/
sum = num = 0;
for (t = targets; t; t = t->next)
{
target = (GSrvTarget *)t->data;
if (target->priority != priority)
break;
sum += target->weight;
num++;
}
/* While there are still targets at this priority level... */
while (num)
{
/* Randomly select from the targets at this priority level,
* giving precedence to the ones with higher weight,
* according to the rules from RFC 2782.
*/
val = g_random_int_range (0, sum + 1);
for (t = targets; ; t = t->next)
{
g_assert (t != NULL && t->data != NULL);
weight = ((GSrvTarget *)t->data)->weight;
if (weight >= val)
break;
val -= weight;
}
targets = g_list_remove_link (targets, t);
if (!out)
out = t;
else
tail->next = t;
tail = t;
sum -= weight;
num--;
}
}
return out;
}