/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "zbxpoller.h"

#include "zbx_availability_constants.h"
#include "zbxavailability.h"
#include "zbxcacheconfig.h"
#include "zbxdbhigh.h"
#include "zbxtime.h"

/******************************************************************************
 *                                                                            *
 * Purpose: write interface availability changes into database                *
 *                                                                            *
 * Parameters: data        - [IN/OUT] the serialized availability data        *
 *             data_alloc  - [IN/OUT] the serialized availability data size   *
 *             data_alloc  - [IN/OUT] the serialized availability data offset *
 *             ia          - [IN] the interface availability data             *
 *                                                                            *
 * Return value: SUCCEED - the availability changes were written into db      *
 *               FAIL    - no changes in availability data were detected      *
 *                                                                            *
 ******************************************************************************/
static int	update_interface_availability(unsigned char **data, size_t *data_alloc, size_t *data_offset,
		const zbx_interface_availability_t *ia)
{
	if (FAIL == zbx_interface_availability_is_set(ia))
		return FAIL;

	zbx_availability_serialize_interface(data, data_alloc, data_offset, ia);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: get interface availability data                                   *
 *                                                                            *
 * Parameters: dc_interface - [IN] the interface                              *
 *             ia           - [OUT] the interface availability data           *
 *                                                                            *
 ******************************************************************************/
static void	interface_get_availability(const zbx_dc_interface_t *dc_interface, zbx_interface_availability_t *ia)
{
	zbx_agent_availability_t	*availability = &ia->agent;

	availability->flags = ZBX_FLAGS_AGENT_STATUS;

	availability->available = dc_interface->available;
	availability->error = zbx_strdup(NULL, dc_interface->error);
	availability->errors_from = dc_interface->errors_from;
	availability->disable_until = dc_interface->disable_until;

	ia->interfaceid = dc_interface->interfaceid;
}

/********************************************************************************
 *                                                                              *
 * Purpose: sets interface availability data                                    *
 *                                                                              *
 * Parameters: dc_interface - [IN/OUT] the interface                            *
 *             ia           - [IN] the interface availability data              *
 *                                                                              *
 *******************************************************************************/
static void	interface_set_availability(zbx_dc_interface_t *dc_interface, const zbx_interface_availability_t *ia)
{
	const zbx_agent_availability_t	*availability = &ia->agent;
	unsigned char			*pavailable;
	int				*perrors_from, *pdisable_until;
	char				*perror;

	pavailable = &dc_interface->available;
	perror = dc_interface->error;
	perrors_from = &dc_interface->errors_from;
	pdisable_until = &dc_interface->disable_until;

	if (0 != (availability->flags & ZBX_FLAGS_AGENT_STATUS_AVAILABLE))
		*pavailable = availability->available;

	if (0 != (availability->flags & ZBX_FLAGS_AGENT_STATUS_ERROR))
		zbx_strlcpy(perror, availability->error, ZBX_INTERFACE_ERROR_LEN_MAX);

	if (0 != (availability->flags & ZBX_FLAGS_AGENT_STATUS_ERRORS_FROM))
		*perrors_from = availability->errors_from;

	if (0 != (availability->flags & ZBX_FLAGS_AGENT_STATUS_DISABLE_UNTIL))
		*pdisable_until = availability->disable_until;
}

static int	interface_availability_by_item_type(unsigned char item_type, unsigned char interface_type)
{
	if ((ITEM_TYPE_ZABBIX == item_type && INTERFACE_TYPE_AGENT == interface_type) ||
			(ITEM_TYPE_SNMP == item_type && INTERFACE_TYPE_SNMP == interface_type) ||
			(ITEM_TYPE_JMX == item_type && INTERFACE_TYPE_JMX == interface_type) ||
			(ITEM_TYPE_IPMI == item_type && INTERFACE_TYPE_IPMI == interface_type))
		return SUCCEED;

	return FAIL;
}

static const char	*item_type_agent_string(zbx_item_type_t item_type)
{
	switch (item_type)
	{
		case ITEM_TYPE_ZABBIX:
			return "Zabbix agent";
		case ITEM_TYPE_SNMP:
			return "SNMP agent";
		case ITEM_TYPE_IPMI:
			return "IPMI agent";
		case ITEM_TYPE_JMX:
			return "JMX agent";
		default:
			return "generic";
	}
}

/********************************************************************************
 *                                                                              *
 * Purpose: activate item interface                                             *
 *                                                                              *
 * Parameters: ts         - [IN] the timestamp                                  *
 *             item       - [IN/OUT] the item                                   *
 *             data       - [IN/OUT] the serialized availability data           *
 *             data_alloc - [IN/OUT] the serialized availability data size      *
 *             data_alloc - [IN/OUT] the serialized availability data offset    *
 *             ts         - [IN] the timestamp                                  *
 *                                                                              *
 *******************************************************************************/
void	zbx_activate_item_interface(zbx_timespec_t *ts, zbx_dc_interface_t *interface, zbx_uint64_t itemid, int type,
		char *host, unsigned char **data, size_t *data_alloc, size_t *data_offset)
{
	zbx_interface_availability_t	in, out;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() interfaceid:" ZBX_FS_UI64 " itemid:" ZBX_FS_UI64 " type:%d",
			__func__, interface->interfaceid, itemid, (int)type);

	zbx_interface_availability_init(&in, interface->interfaceid);
	zbx_interface_availability_init(&out, interface->interfaceid);

	if (FAIL == interface_availability_by_item_type((unsigned char)type, interface->type))
		goto out;

	interface_get_availability(interface, &in);

	if (FAIL == zbx_dc_interface_activate(interface->interfaceid, ts, &in.agent, &out.agent))
		goto out;

	if (FAIL == update_interface_availability(data, data_alloc, data_offset, &out))
		goto out;

	interface_set_availability(interface, &out);

	if (ZBX_INTERFACE_AVAILABLE_TRUE == in.agent.available)
	{
		zabbix_log(LOG_LEVEL_WARNING, "resuming %s checks on host \"%s\": connection restored",
				item_type_agent_string(type), host);
	}
	else
	{
		zabbix_log(LOG_LEVEL_WARNING, "enabling %s checks on host \"%s\": interface became available",
				item_type_agent_string(type), host);
	}
out:
	zbx_interface_availability_clean(&out);
	zbx_interface_availability_clean(&in);

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

/***********************************************************************************
 *                                                                                 *
 * Purpose: deactivate item interface                                              *
 *                                                                                 *
 * Parameters: ts                 - [IN] timestamp                                 *
 *             item               - [IN/OUT] item                                  *
 *             data               - [IN/OUT] serialized availability data          *
 *             data_alloc         - [IN/OUT] serialized availability data size     *
 *             data_alloc         - [IN/OUT] serialized availability data offset   *
 *             ts                 - [IN] timestamp                                 *
 *             unavailable_delay  - [IN]                                           *
 *             unreachable_period - [IN]                                           *
 *             unreachable_delay  - [IN]                                           *
 *             error              - [IN/OUT]                                       *
 *                                                                                 *
 ***********************************************************************************/
void	zbx_deactivate_item_interface(zbx_timespec_t *ts, zbx_dc_interface_t *interface, zbx_uint64_t itemid, int type,
		char *host, char *key_orig, unsigned char **data, size_t *data_alloc, size_t *data_offset,
		int unavailable_delay, int unreachable_period, int unreachable_delay, const char *error)
{
	zbx_interface_availability_t	in, out;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() interfaceid:" ZBX_FS_UI64 " itemid:" ZBX_FS_UI64 " type:%d",
			__func__, interface->interfaceid, itemid, type);

	zbx_interface_availability_init(&in, interface->interfaceid);
	zbx_interface_availability_init(&out, interface->interfaceid);

	if (FAIL == interface_availability_by_item_type((unsigned char)type, interface->type))
		goto out;

	interface_get_availability(interface, &in);

	if (FAIL == zbx_dc_interface_deactivate(interface->interfaceid, ts, unavailable_delay, unreachable_period,
			unreachable_delay, &in.agent, &out.agent, error))
	{
		goto out;
	}

	if (FAIL == update_interface_availability(data, data_alloc, data_offset, &out))
		goto out;

	interface_set_availability(interface, &out);

	if (0 == in.agent.errors_from)
	{
		zabbix_log(LOG_LEVEL_WARNING, "%s item \"%s\" on host \"%s\" failed:"
				" first network error, wait for %d seconds",
				item_type_agent_string(type), key_orig, host,
				out.agent.disable_until - ts->sec);
	}
	else if (ZBX_INTERFACE_AVAILABLE_FALSE != in.agent.available)
	{
		if (ZBX_INTERFACE_AVAILABLE_FALSE != out.agent.available)
		{
			zabbix_log(LOG_LEVEL_WARNING, "%s item \"%s\" on host \"%s\" failed:"
					" another network error, wait for %d seconds",
					item_type_agent_string(type), key_orig, host,
					out.agent.disable_until - ts->sec);
		}
		else
		{
			zabbix_log(LOG_LEVEL_WARNING, "temporarily disabling %s checks on host \"%s\":"
					" interface unavailable",
					item_type_agent_string(type), host);
		}
	}

	zabbix_log(LOG_LEVEL_DEBUG, "%s() errors_from:%d available:%d", __func__,
			out.agent.errors_from, out.agent.available);
out:
	zbx_interface_availability_clean(&out);
	zbx_interface_availability_clean(&in);

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