/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "proxy_group.h"
#include "config.h"
#include "dbconfig.h"
#include "dbsync.h"
#include "user_macro.h"
#include "zbxalgo.h"
#include "zbxcacheconfig.h"
#include "zbxcommon.h"
#include "zbxdb.h"
#include "zbxnum.h"
#include "zbxpgservice.h"
#include "zbxtime.h"
#include "zbxversion.h"

ZBX_PTR_VECTOR_IMPL(pg_proxy_ptr, zbx_pg_proxy_t *)
ZBX_PTR_VECTOR_IMPL(pg_group_ptr, zbx_pg_group_t *)
ZBX_VECTOR_IMPL(pg_host, zbx_pg_host_t)
ZBX_VECTOR_IMPL(pg_host_ref_ptr, zbx_pg_host_ref_t *)

static zbx_hash_t	pg_host_ref_hash(const void *d)
{
	const zbx_pg_host_ref_t	*host = (const zbx_pg_host_ref_t *)d;

	return ZBX_DEFAULT_UINT64_HASH_FUNC(&host->host->hostid);
}

static int	pg_host_ref_compare(const void *d1, const void *d2)
{
	const zbx_pg_host_ref_t	*h1 = (const zbx_pg_host_ref_t *)d1;
	const zbx_pg_host_ref_t	*h2 = (const zbx_pg_host_ref_t *)d2;

	ZBX_RETURN_IF_NOT_EQUAL(h1->host->hostid, h2->host->hostid);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: sync proxy groups with configuration cache                        *
 *                                                                            *
 * Parameters: sync     - [IN] db synchronization data                        *
 *             revision - [IN] current sync revision                          *
 *                                                                            *
 * Comments: The result contains the following fields:                        *
 *           0 - proxy_groupid                                                *
 *           1 - failover_delay                                               *
 *           2 - min_online                                                   *
 *                                                                            *
 ******************************************************************************/
void	dc_sync_proxy_group(zbx_dbsync_t *sync, zbx_uint64_t revision)
{
	char			**row;
	zbx_uint64_t		rowid;
	unsigned char		tag;
	zbx_dc_proxy_group_t	*pg;
	int			ret;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_dcsync_sync_start(sync, dbconfig_used_size());

	while (SUCCEED == (ret = zbx_dbsync_next(sync, &rowid, &row, &tag)))
	{
		/* removed rows will be always added at the end */
		if (ZBX_DBSYNC_ROW_REMOVE == tag)
			break;

		zbx_uint64_t	proxy_groupid;
		int		found;

		ZBX_STR2UINT64(proxy_groupid, row[0]);

		pg = (zbx_dc_proxy_group_t *)DCfind_id(&get_dc_config()->proxy_groups, proxy_groupid,
				sizeof(zbx_dc_proxy_group_t), &found);

		dc_strpool_replace(found, &pg->failover_delay, row[1]);
		dc_strpool_replace(found, &pg->min_online, row[2]);
		dc_strpool_replace(found, &pg->name, row[3]);

		pg->revision = revision;
	}

	for (; SUCCEED == ret; ret = zbx_dbsync_next(sync, &rowid, &row, &tag))
	{
		if (NULL == (pg = (zbx_dc_proxy_group_t *)zbx_hashset_search(&get_dc_config()->proxy_groups, &rowid)))
			continue;

		dc_strpool_release(pg->failover_delay);
		dc_strpool_release(pg->min_online);
		dc_strpool_release(pg->name);

		zbx_hashset_remove_direct(&get_dc_config()->proxy_groups, pg);
	}

	if (0 != sync->add_num + sync->update_num + sync->remove_num)
		get_dc_config()->revision.proxy_group = revision;

	zbx_dcsync_sync_end(sync, dbconfig_used_size());

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: update local proxy group cache                                    *
 *                                                                            *
 * Parameter: groups   - [IN/OUT] local proxy group cache                     *
 *            revision - [IN/OUT] local proxy group cache revision            *
 *                                                                            *
 * Return value: SUCCEED - local cache was updated                            *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_dc_fetch_proxy_groups(zbx_hashset_t *groups, zbx_uint64_t *revision)
{
	int		ret = FAIL;
	zbx_uint64_t	old_revision = *revision;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (*revision >= get_dc_config()->revision.proxy_group)
		goto out;

	zbx_hashset_iter_t	iter;
	zbx_dc_proxy_group_t	*dc_group;
	zbx_pg_group_t		*group;

	zbx_hashset_iter_reset(groups, &iter);
	while (NULL != (group = (zbx_pg_group_t *)zbx_hashset_iter_next(&iter)))
		group->flags = ZBX_PG_GROUP_FLAGS_NONE;

	RDLOCK_CACHE;

	*revision = get_dc_config()->revision.proxy_group;

	zbx_hashset_iter_reset(&get_dc_config()->proxy_groups, &iter);
	while (NULL != (dc_group = (zbx_dc_proxy_group_t *)zbx_hashset_iter_next(&iter)))
	{
		if (NULL == (group = (zbx_pg_group_t *)zbx_hashset_search(groups, &dc_group->proxy_groupid)))
		{
			zbx_pg_group_t	group_local = {.proxy_groupid = dc_group->proxy_groupid};

			group = (zbx_pg_group_t *)zbx_hashset_insert(groups, &group_local, sizeof(group_local));
			zbx_vector_pg_proxy_ptr_create(&group->proxies);
			zbx_hashset_create(&group->hostids, 0, ZBX_DEFAULT_UINT64_HASH_FUNC,
					ZBX_DEFAULT_UINT64_COMPARE_FUNC);
			zbx_vector_uint64_create(&group->unassigned_hostids);
			group->flags = ZBX_PG_GROUP_SYNC_ADDED;
		}
		else
			group->flags = ZBX_PG_GROUP_SYNC_MODIFIED;

		group->sync_revision = *revision;

		if (dc_group->revision > group->revision)
		{
			group->revision = dc_group->revision;

			if (NULL == group->name || 0 != strcmp(group->name, dc_group->name))
				group->name = zbx_strdup(group->name, dc_group->name);

			if (NULL == group->failover_delay ||
					0 != strcmp(group->failover_delay, dc_group->failover_delay))
			{
				group->failover_delay = zbx_strdup(group->failover_delay, dc_group->failover_delay);
			}

			if (NULL == group->min_online || 0 != strcmp(group->min_online, dc_group->min_online))
				group->min_online = zbx_strdup(group->min_online, dc_group->min_online);
		}
	}

	UNLOCK_CACHE;

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s revision:" ZBX_FS_UI64 "->" ZBX_FS_UI64, __func__,
			zbx_result_string(ret), old_revision, *revision);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: update local proxy cache                                          *
 *                                                                            *
 * Parameter: groups   - [IN] local proxy group cache                         *
 *            proxies  - [IN/OUT] local proxy cache                           *
 *            revision - [IN/OUT] local proxy cache revision                  *
 *            flags    - [IN] ZBX_PG_PROXY_FETCH_FORCE - force configuration  *
 *                            update ignoring revision checks                 *
 *                            ZBX_PG_PROXY_FETCH_REVISION - update by proxy   *
 *                            configuration revision                          *
 *            proxy_reloc - [OUT] proxy relocation data                       *
 *                                                                            *
 * Return value: SUCCEED - local cache was updated                            *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_dc_fetch_proxies(zbx_hashset_t *groups, zbx_hashset_t *proxies, zbx_uint64_t *revision, int flags,
		zbx_vector_objmove_t *proxy_reloc)
{
	int		ret = FAIL;
	zbx_uint64_t	old_revision = *revision;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (ZBX_PG_PROXY_FETCH_REVISION == flags && *revision >= get_dc_config()->revision.proxy)
		goto out;

	zbx_hashset_iter_t	iter;
	ZBX_DC_PROXY		*dc_proxy;
	zbx_pg_proxy_t		*proxy;

	RDLOCK_CACHE;

	*revision = get_dc_config()->revision.proxy;

	zbx_hashset_iter_reset(&get_dc_config()->proxies, &iter);

	while (NULL != (dc_proxy = (ZBX_DC_PROXY *)zbx_hashset_iter_next(&iter)))
	{
		if (0 != dc_proxy->proxy_groupid && NULL == zbx_hashset_search(groups, &dc_proxy->proxy_groupid))
			continue;

		proxy = (zbx_pg_proxy_t *)zbx_hashset_search(proxies, &dc_proxy->proxyid);

		if (NULL != proxy)
		{
			if (NULL != proxy->group && proxy->group->proxy_groupid != dc_proxy->proxy_groupid)
			{
				zbx_objmove_t	reloc = {
						.objid = proxy->proxyid,
						.srcid = proxy->group->proxy_groupid,
						.dstid = dc_proxy->proxy_groupid
					};

				zbx_vector_objmove_append_ptr(proxy_reloc, &reloc);
			}
		}
		else
		{
			zbx_pg_proxy_t	proxy_local = {.proxyid = dc_proxy->proxyid};

			proxy = (zbx_pg_proxy_t *)zbx_hashset_insert(proxies, &proxy_local, sizeof(proxy_local));
			proxy->group = NULL;

			zbx_hashset_create(&proxy->hosts, 0, pg_host_ref_hash, pg_host_ref_compare);
			zbx_vector_pg_host_create(&proxy->deleted_group_hosts);

			zbx_objmove_t	reloc = {
					.objid = proxy->proxyid,
					.srcid = 0,
					.dstid = dc_proxy->proxy_groupid,
				};

			zbx_vector_objmove_append_ptr(proxy_reloc, &reloc);
		}

		proxy->lastaccess = dc_proxy->lastaccess;
		proxy->version = ZBX_COMPONENT_VERSION_WITHOUT_PATCH(dc_proxy->version_int);
		proxy->revision = *revision;

		if (NULL == proxy->name || 0 != strcmp(proxy->name, dc_proxy->name))
			proxy->name = zbx_strdup(proxy->name, dc_proxy->name);
	}

	UNLOCK_CACHE;

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s revision:" ZBX_FS_UI64 "->" ZBX_FS_UI64, __func__,
			zbx_result_string(ret), old_revision, *revision);
	return ret;
}

static void	dc_register_host_proxy(zbx_dc_host_proxy_t *hp)
{
	zbx_dc_host_proxy_index_t	*hpi, hpi_local = {.host = hp->host};

	if (NULL == (hpi = (zbx_dc_host_proxy_index_t *)zbx_hashset_search(&get_dc_config()->host_proxy_index, &hpi_local)))
	{
		hpi = (zbx_dc_host_proxy_index_t *)zbx_hashset_insert(&get_dc_config()->host_proxy_index, &hpi_local,
				sizeof(hpi_local));
		dc_strpool_acquire(hpi->host);
	}

	hpi->host_proxy = hp;
}

static void	dc_deregister_host_proxy(zbx_dc_host_proxy_t *hp)
{
	zbx_dc_host_proxy_index_t	*hpi, hpi_local = {.host = hp->host};

	if (NULL != (hpi = (zbx_dc_host_proxy_index_t *)zbx_hashset_search(&get_dc_config()->host_proxy_index,
			&hpi_local)))
	{
		dc_strpool_release(hpi->host);
		zbx_hashset_remove_direct(&get_dc_config()->host_proxy_index, hpi);
	}
}

void	dc_update_host_proxy(const char *host_old, const char *host_new)
{
	zbx_dc_host_proxy_index_t	*hpi, hpi_local = {.host = host_old};

	if (NULL != (hpi = (zbx_dc_host_proxy_index_t *)zbx_hashset_search(&get_dc_config()->host_proxy_index,
			&hpi_local)))
	{
		dc_strpool_release(hpi->host);
		zbx_hashset_remove_direct(&get_dc_config()->host_proxy_index, hpi);

		hpi_local.host_proxy = hpi->host_proxy;
		hpi_local.host = dc_strpool_intern(host_new);
		zbx_hashset_insert(&get_dc_config()->host_proxy_index, &hpi_local, sizeof(hpi_local));
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: sync host proxy links with configuration cache                    *
 *                                                                            *
 * Parameters: sync     - [IN] db synchronization data                        *
 *                                                                            *
 * Comments: The result contains the following fields:                        *
 *           0 - hostproxyid                                                  *
 *           1 - hostid                                                       *
 *           2 - host                                                         *
 *           3 - proxyid                                                      *
 *           4 - revision                                                     *
 *           5 - host.host (NULL on proxies)                                  *
 *                                                                            *
 ******************************************************************************/
void	dc_sync_host_proxy(zbx_dbsync_t *sync, zbx_uint64_t revision)
{
	char				**row;
	zbx_uint64_t			rowid;
	unsigned char			tag;
	zbx_dc_host_proxy_t		*hp;
	int				ret;
	ZBX_DC_HOST			*dc_host;
	zbx_hashset_t			psk_owners;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	zbx_dcsync_sync_start(sync, dbconfig_used_size());

	/* Encryption data in host_proxy table is stored and synced only on proxy. */
	/* The identify conflicts have been already checked by server, so they can */
	/* be skipped by using separate psk_owner registry.                        */
	zbx_hashset_create(&psk_owners, 0, ZBX_DEFAULT_PTR_HASH_FUNC, ZBX_DEFAULT_PTR_COMPARE_FUNC);

	while (SUCCEED == (ret = zbx_dbsync_next(sync, &rowid, &row, &tag)))
	{
		/* removed rows will be always added at the end */
		if (ZBX_DBSYNC_ROW_REMOVE == tag)
			break;

		zbx_uint64_t	hostproxyid;
		int		found;

		ZBX_STR2UINT64(hostproxyid, row[0]);

		hp = (zbx_dc_host_proxy_t *)DCfind_id(&get_dc_config()->host_proxy, hostproxyid,
				sizeof(zbx_dc_host_proxy_t), &found);

		ZBX_DBROW2UINT64(hp->hostid, row[1]);
		ZBX_STR2UINT64(hp->proxyid, row[3]);
		ZBX_STR2UINT64(hp->revision, row[4]);

		if (SUCCEED != zbx_db_is_null(row[5]))	/* server */
		{
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
			if (0 == found)
			{
				hp->tls_issuer = NULL;
				hp->tls_subject = NULL;
				hp->tls_dc_psk = NULL;
			}
#endif

			dc_strpool_replace(found, &hp->host, row[5]);

			if (NULL != (dc_host = (ZBX_DC_HOST *)zbx_hashset_search(&get_dc_config()->hosts, &hp->hostid)))
			{
				if (0 != dc_host->proxy_groupid)
				{
					if (0 != dc_host->proxyid)
						dc_host_deregister_proxy(dc_host, dc_host->proxyid, revision);

					dc_host_register_proxy(dc_host, hp->proxyid, revision);
					dc_host->proxyid = hp->proxyid;
					dc_host->revision = revision;
				}
			}
		}
		else	/* proxy */
		{
			dc_strpool_replace(found, &hp->host, row[2]);

			ZBX_STR2UCHAR(hp->tls_accept, row[6]);
#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
			dc_strpool_replace(found, &hp->tls_issuer, row[7]);
			dc_strpool_replace(found, &hp->tls_subject, row[8]);
			hp->tls_dc_psk = dc_psk_sync(row[9], row[10], hp->host, found, &psk_owners, hp->tls_dc_psk);
#endif
		}

		dc_register_host_proxy(hp);
	}

	for (; SUCCEED == ret; ret = zbx_dbsync_next(sync, &rowid, &row, &tag))
	{
		if (NULL == (hp = (zbx_dc_host_proxy_t *)zbx_hashset_search(&get_dc_config()->host_proxy, &rowid)))
			continue;

		if (NULL != (dc_host = (ZBX_DC_HOST *)zbx_hashset_search(&get_dc_config()->hosts, &hp->hostid)))
		{
			if (0 != dc_host->proxy_groupid)
			{
				dc_host_deregister_proxy(dc_host, hp->proxyid, revision);
				dc_host->proxyid = 0;
				dc_host->revision = revision;
			}
		}

		dc_deregister_host_proxy(hp);

		dc_strpool_release(hp->host);

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
		if (NULL != hp->tls_issuer)	/* proxy */
		{
			dc_strpool_release(hp->tls_issuer);
			dc_strpool_release(hp->tls_subject);
			dc_psk_unlink(hp->tls_dc_psk);
		}
#endif
		zbx_hashset_remove_direct(&get_dc_config()->host_proxy, hp);
	}

	zbx_hashset_destroy(&psk_owners);

	zbx_dcsync_sync_end(sync, dbconfig_used_size());

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: get redirection information for the host                          *
 *                                                                            *
 * Parameters: host     - [IN] host name                                      *
 *             attr     - [IN] connection attributes                          *
 *             redirect - [OUT] redirection information                       *
 *                                                                            *
 * Return value: SUCCEED - host must be redirected to the returned address    *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	dc_get_host_redirect(const char *host, const zbx_tls_conn_attr_t *attr, zbx_comms_redirect_t *redirect)
{
	zbx_dc_host_proxy_index_t	*hpi, hpi_local = {.host = host};
	ZBX_DC_PROXY			*proxy;

	if (NULL == (hpi = (zbx_dc_host_proxy_index_t *)zbx_hashset_search(&get_dc_config()->host_proxy_index,
			&hpi_local)))
	{
		return FAIL;
	}

	if (NULL == (proxy = (ZBX_DC_PROXY *)zbx_hashset_search(&get_dc_config()->proxies, &hpi->host_proxy->proxyid)))
		return FAIL;

	if (NULL != get_dc_config()->proxy_hostname && 0 == strcmp(proxy->name, get_dc_config()->proxy_hostname))
	{
		int	now;

		now = (int)time(NULL);

		/* check if proxy is not offline and the client redirect should be reset */
		if (now - get_dc_config()->proxy_lastonline < get_dc_config()->proxy_failover_delay ||
				now - hpi->lastreset < get_dc_config()->proxy_failover_delay)
		{
			return FAIL;
		}

		hpi->lastreset = now;
		redirect->reset = ZBX_REDIRECT_RESET;

		return SUCCEED;
	}

	const char	*local_port = proxy->local_port;

	if ('{' == *local_port)
	{
		um_cache_resolve_const(get_dc_config()->um_cache, NULL, 0, proxy->local_port, ZBX_MACRO_ENV_NONSECURE,
				&local_port);
	}

	if ('\0' != *local_port)
		zbx_snprintf(redirect->address, sizeof(redirect->address), "%s:%s", proxy->local_address, local_port);
	else
		zbx_strlcpy(redirect->address, proxy->local_address, sizeof(redirect->address));


	unsigned char	tls_accept;

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	const char	*tls_issuer;
	const char	*tls_subject;
	ZBX_DC_PSK	*dc_psk = NULL;
#endif

	if (NULL != get_dc_config()->proxy_hostname)
	{
		/* on proxy encryption information is taken from host_proxy redirect mapping */
		tls_accept = hpi->host_proxy->tls_accept;

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
		tls_issuer = hpi->host_proxy->tls_issuer;
		tls_subject = hpi->host_proxy->tls_subject;
		dc_psk = hpi->host_proxy->tls_dc_psk;
#endif
	}
	else
	{
		ZBX_DC_HOST	*dc_host;

		/* on server encryption information is taken from hosts, block redirection for unknown hosts */
		if (NULL == (dc_host = DCfind_host(host)))
			return FAIL;

		tls_accept = dc_host->tls_accept;

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
		tls_issuer = dc_host->tls_issuer;
		tls_subject = dc_host->tls_subject;
		dc_psk = dc_host->tls_dc_psk;
#endif
	}

	if (0 == ((unsigned int)tls_accept & attr->connection_type))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot perform host \"%s\" redirection: connection of type \"%s\""
				" is not allowed",
				host, zbx_tcp_connection_type_name(attr->connection_type));

		return FAIL;
	}

#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL)
	const char	*msg;

	if (FAIL == zbx_tls_validate_attr(attr, tls_issuer, tls_subject,
			NULL == dc_psk ? NULL : dc_psk->tls_psk_identity, &msg))
	{

		zabbix_log(LOG_LEVEL_DEBUG, "cannot perform host \"%s\" redirection: %s", host, msg);
		return FAIL;
	}
#endif

	redirect->revision = hpi->host_proxy->revision;
	redirect->reset = ZBX_REDIRECT_NONE;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: set proxy failover delay in configuration cache                   *
 *                                                                            *
 ******************************************************************************/
void	dc_update_proxy_failover_delay(void)
{
	if (NULL != get_dc_config()->proxy_failover_delay_raw)
	{
		const char	*ptr = get_dc_config()->proxy_failover_delay_raw;

		if ('{' == *ptr)
			um_cache_resolve_const(get_dc_config()->um_cache, NULL, 0, ptr, ZBX_MACRO_ENV_NONSECURE, &ptr);

		if (FAIL == zbx_is_time_suffix(ptr, &get_dc_config()->proxy_failover_delay, ZBX_LENGTH_UNLIMITED))
			get_dc_config()->proxy_failover_delay = ZBX_PG_DEFAULT_FAILOVER_DELAY;

		dc_strpool_release(get_dc_config()->proxy_failover_delay_raw);
		get_dc_config()->proxy_failover_delay_raw = NULL;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: set proxy failover delay in configuration cache                   *
 *                                                                            *
 ******************************************************************************/
void	zbx_dc_set_proxy_failover_delay(const char *failover_delay)
{
	WRLOCK_CACHE;

	int	found = (NULL != get_dc_config()->proxy_failover_delay_raw);

	/* failover delay can be updated only by one process at time, */
	/* so it can be checked without locking before update        */
	if (0 == found || 0 != strcmp(get_dc_config()->proxy_failover_delay_raw, failover_delay))
		dc_strpool_replace(found, &get_dc_config()->proxy_failover_delay_raw, failover_delay);

	UNLOCK_CACHE;
}

/******************************************************************************
 *                                                                            *
 * Purpose: set proxy last online timestamp in configuration cache            *
 *                                                                            *
 ******************************************************************************/
void	zbx_dc_set_proxy_lastonline(int lastonline)
{
	WRLOCK_CACHE;
	get_dc_config()->proxy_lastonline = lastonline;
	UNLOCK_CACHE;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get proxy group revision                                          *
 *                                                                            *
 ******************************************************************************/
zbx_uint64_t	zbx_dc_get_proxy_group_revision(zbx_uint64_t proxy_groupid)
{
	zbx_uint64_t	revision;

	RDLOCK_CACHE;
	zbx_dc_proxy_group_t	*pg;

	if (NULL != (pg = (zbx_dc_proxy_group_t *)zbx_hashset_search(&get_dc_config()->proxy_groups, &proxy_groupid)))
		revision = pg->revision;
	else
		revision = 0;

	UNLOCK_CACHE;

	return revision;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get proxy group by proxy id                                       *
 *                                                                            *
 ******************************************************************************/
zbx_uint64_t	zbx_dc_get_proxy_groupid(zbx_uint64_t proxyid)
{
	zbx_uint64_t	proxy_groupid = 0;
	ZBX_DC_PROXY	*proxy;

	if (0 != proxyid)
	{
		RDLOCK_CACHE;

		if (NULL != (proxy = (ZBX_DC_PROXY *)zbx_hashset_search(&get_dc_config()->proxies, &proxyid)))
			proxy_groupid = proxy->proxy_groupid;

		UNLOCK_CACHE;
	}

	return proxy_groupid;
}