/*
** Copyright (C) 2001-2026 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 "zbxdiscovery.h"

#include "zbxserialize.h"
#include "zbx_discoverer_constants.h"
#include "zbxalgo.h"
#include "zbxcacheconfig.h"
#include "zbxipcservice.h"
#include "zbxjson.h"
#include "zbxstats.h"
#include "zbxip.h"

#define DISCOVERER_INITIALIZED_YES	1
#define ZBX_DISCOVERER_IPRANGE_LIMIT	(1 << 16)

static int	discoverer_initialized = 0;

ZBX_VECTOR_IMPL(iprange, zbx_iprange_t)

void	zbx_discoverer_init(void)
{
	discoverer_initialized = DISCOVERER_INITIALIZED_YES;
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees discovery check                                             *
 *                                                                            *
 ******************************************************************************/
void	zbx_discovery_dcheck_free(zbx_dc_dcheck_t *dcheck)
{
	zbx_free(dcheck->key_);
	zbx_free(dcheck->ports);

	if (SVC_SNMPv1 == dcheck->type || SVC_SNMPv2c == dcheck->type || SVC_SNMPv3 == dcheck->type)
	{
		zbx_free(dcheck->snmp_community);
		zbx_free(dcheck->snmpv3_securityname);
		zbx_free(dcheck->snmpv3_authpassphrase);
		zbx_free(dcheck->snmpv3_privpassphrase);
		zbx_free(dcheck->snmpv3_contextname);
	}

	zbx_free(dcheck);
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees discovery rule                                              *
 *                                                                            *
 ******************************************************************************/
void	zbx_discovery_drule_free(zbx_dc_drule_t *drule)
{
	zbx_free(drule->delay_str);
	zbx_free(drule->iprange);
	zbx_free(drule->name);

	zbx_vector_dc_dcheck_ptr_clear_ext(&drule->dchecks, zbx_discovery_dcheck_free);
	zbx_vector_dc_dcheck_ptr_destroy(&drule->dchecks);

	zbx_free(drule);
}

/******************************************************************************
 *                                                                            *
 * Purpose: sends command to discovery manager                                *
 *                                                                            *
 * Parameters: code     - [IN] message code                                   *
 *             data     - [IN] message data                                   *
 *             size     - [IN] message data size                              *
 *             response - [OUT] response message (can be NULL if response is  *
 *                              not requested)                                *
 *                                                                            *
 ******************************************************************************/
static void	discovery_send(zbx_uint32_t code, unsigned char *data, zbx_uint32_t size,
		zbx_ipc_message_t *response)
{
	char			*error = NULL;
	static zbx_ipc_socket_t	socket = {0};

	/* each process has a permanent connection to discovery manager */
	if (0 == socket.fd && FAIL == zbx_ipc_socket_open(&socket, ZBX_IPC_SERVICE_DISCOVERER, SEC_PER_MIN,
			&error))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot connect to discoverer service: %s", error);
		exit(EXIT_FAILURE);
	}

	if (FAIL == zbx_ipc_socket_write(&socket, code, data, size))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot send data to discoverer service");
		exit(EXIT_FAILURE);
	}

	if (NULL != response && FAIL == zbx_ipc_socket_read(&socket, response))
	{
		zabbix_log(LOG_LEVEL_CRIT, "cannot receive data from discoverer service");
		exit(EXIT_FAILURE);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets queue size (enqueued checks count) of discovery manager      *
 *                                                                            *
 * Parameters: size  - [OUT] enqueued item count                              *
 *             error - [OUT] error message                                    *
 *                                                                            *
 * Return value: SUCCEED - queue size retrieved                               *
 *               FAIL    - discovery manager is not initialized               *
 *                                                                            *
 ******************************************************************************/
int	zbx_discovery_get_queue_size(zbx_uint64_t *size, char **error)
{
	zbx_ipc_message_t	message;

	if (DISCOVERER_INITIALIZED_YES != discoverer_initialized)
	{
		if (NULL != error)
		{
			*error = zbx_strdup(NULL, "discoverer is not initialized: please check \"StartDiscoverers\""
					" configuration parameter");
		}

		return FAIL;
	}

	zbx_ipc_message_init(&message);
	discovery_send(ZBX_IPC_DISCOVERER_QUEUE, NULL, 0, &message);
	memcpy(size, message.data, sizeof(zbx_uint64_t));
	zbx_ipc_message_clean(&message);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: unpacks worker usage statistics                                   *
 *                                                                            *
 * Parameters: usage - [OUT] worker usage statistics                          *
 *             count - [OUT]                                                  *
 *             data  - [IN] input data                                        *
 *                                                                            *
 ******************************************************************************/
static void	discovery_unpack_usage_stats(zbx_vector_dbl_t *usage, int *count, const unsigned char *data)
{
	const unsigned char	*offset = data;
	int			usage_num;

	offset += zbx_deserialize_value(offset, &usage_num);
	zbx_vector_dbl_reserve(usage, (size_t)usage_num);

	for (int i = 0; i < usage_num; i++)
	{
		double	busy;

		offset += zbx_deserialize_value(offset, &busy);
		zbx_vector_dbl_append(usage, busy);
	}

	(void)zbx_deserialize_value(offset, count);
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets discovery manager diagnostic statistics                      *
 *                                                                            *
 ******************************************************************************/
int	zbx_get_usage_stats_discovery(zbx_vector_dbl_t *usage, int *count, char **error)
{
	unsigned char	*result;

	if (DISCOVERER_INITIALIZED_YES != discoverer_initialized)
	{
		*error = zbx_strdup(NULL, "discoverer is not initialized: please check \"StartDiscoverers\""
				" configuration parameter");

		return FAIL;
	}

	if (SUCCEED != zbx_ipc_async_exchange(ZBX_IPC_SERVICE_DISCOVERER, ZBX_IPC_DISCOVERER_USAGE_STATS,
			SEC_PER_MIN, NULL, 0, &result, error))
	{
		return FAIL;
	}

	discovery_unpack_usage_stats(usage, count, result);
	zbx_free(result);

	return SUCCEED;
}

#undef DISCOVERER_INITIALIZED_YES

/******************************************************************************
 *                                                                            *
 * Purpose: Packs diagnostic statistics data into a single buffer that can be *
 *          used in IPC.                                                      *
 * Parameters: data    - [OUT] memory buffer for packed data                  *
 *             usage   - [IN] worker usage statistics                         *
 *             count   - [IN]                                                 *
 *                                                                            *
 ******************************************************************************/
zbx_uint32_t	zbx_discovery_pack_usage_stats(unsigned char **data, const zbx_vector_dbl_t *usage, int count)
{
	unsigned char	*ptr;
	zbx_uint32_t	data_len;

	data_len = (zbx_uint32_t)((unsigned int)usage->values_num * sizeof(double) + sizeof(int) + sizeof(int));

	ptr = *data = (unsigned char *)zbx_malloc(NULL, data_len);

	ptr += zbx_serialize_value(ptr, usage->values_num);

	for (int i = 0; i < usage->values_num; i++)
		ptr += zbx_serialize_value(ptr, usage->values[i]);

	(void)zbx_serialize_value(ptr, count);

	return data_len;
}

void zbx_discovery_stats_ext_get_data(struct zbx_json *json, const void *arg)
{
	zbx_uint64_t	size;

	ZBX_UNUSED(arg);

	/* zabbix[discovery_queue] */
	if (SUCCEED == zbx_discovery_get_queue_size(&size, NULL))
		zbx_json_adduint64(json, "discovery_queue", size);
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets discovery worker usage statistics                            *
 *                                                                            *
 ******************************************************************************/
void	zbx_discovery_stats_procinfo(zbx_process_info_t *info)
{
	zbx_vector_dbl_t	usage;
	char			*error = NULL;

	zbx_vector_dbl_create(&usage);

	memset(info, 0, sizeof(zbx_process_info_t));

	if (SUCCEED != zbx_get_usage_stats_discovery(&usage, &info->count, &error))
	{
		zabbix_log(LOG_LEVEL_WARNING, "cannot get discovery usage statistics: %s", error);
		zbx_free(error);
		goto out;
	}

	if (0 == usage.values_num)
		goto out;

	info->busy_min = info->busy_max = info->busy_avg = usage.values[0];

	for (int i = 1; i < usage.values_num; i++)
	{
		if (usage.values[i] < info->busy_min)
			info->busy_min = usage.values[i];

		if (usage.values[i] > info->busy_max)
			info->busy_max = usage.values[i];

		info->busy_avg += usage.values[i];
	}

	info->busy_avg /= (double)usage.values_num;

	info->idle_min = 100.0 - info->busy_min;
	info->idle_max = 100.0 - info->busy_max;
	info->idle_avg = 100.0 - info->busy_avg;
	info->count = usage.values_num;
out:
	zbx_vector_dbl_destroy(&usage);
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses IP ranges from drule                                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_discovery_process_drule_iprange(const zbx_dc_drule_t *drule, zbx_vector_iprange_t *ipranges,
		char *err, int sz_err)
{
	char	*comma, *start = drule->iprange;
	int	i, ret = SUCCEED;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() rule:'%s' range:'%s'", __func__, drule->name, drule->iprange);

	zbx_vector_iprange_clear(ipranges);

	/* i = 1 to guarantee at least 1 iprange */
	for (i = 1; NULL != (start = strchr(start, ',')); i++, start++);

	zbx_vector_iprange_reserve(ipranges, (size_t)i);

	for (start = drule->iprange; '\0' != *start;)
	{
		zbx_iprange_t	ipr;
		int		res, ip_first[ZBX_IPRANGE_GROUPS_V6], z[ZBX_IPRANGE_GROUPS_V6] = {0};

		if (NULL != (comma = strchr(start, ',')))
			*comma = '\0';

		zabbix_log(LOG_LEVEL_DEBUG, "%s() range:'%s'", __func__, start);

		if (SUCCEED == (res = zbx_iprange_parse(&ipr, start)))
			zbx_iprange_first(&ipr, ip_first);

		if (SUCCEED != res || 0 == memcmp(ip_first, z, sizeof(int) *
				(ZBX_IPRANGE_V4 == ipr.type ? ZBX_IPRANGE_GROUPS_V4 : ZBX_IPRANGE_GROUPS_V6)))
		{
			if (NULL != err)
				zbx_snprintf(err, sz_err, "Wrong format of IP range \"%s\"", start);

			ret = FAIL;
			goto out;
		}

		if (ZBX_DISCOVERER_IPRANGE_LIMIT < zbx_iprange_volume(&ipr))
		{
			if (NULL != err)
			{
				zbx_snprintf(err, sz_err, "IP range \"%s\" exceeds %d address limit",
						start, ZBX_DISCOVERER_IPRANGE_LIMIT);
			}

			ret = FAIL;
			goto out;
		}
#ifndef HAVE_IPV6
		if (ZBX_IPRANGE_V6 == ipr.type)
		{
			if (NULL != err)
			{
				zbx_snprintf(err, sz_err,
						"Encountered IP range \"%s\", but IPv6 support not compiled in",
						start);
			}

			ret = FAIL;
			goto out;
		}
#endif
		zbx_vector_iprange_append(ipranges, ipr);

		if (NULL != comma)
		{
			*comma = ',';
			start = comma + 1;
		}
		else
			break;
	}
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() drule:" ZBX_FS_UI64,
			__func__, drule->druleid);

	return ret;
}
