/*
** 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 "item_preproc.h"
#include "preproc_snmp.h"
#include "pp_manager.h"

#include "zbxjson.h"
#include "zbxcrypto.h"
#include "zbxstr.h"
#include "zbxvariant.h"

ZBX_VECTOR_IMPL(snmp_walk_to_json_param, zbx_snmp_walk_to_json_param_t)
ZBX_PTR_VECTOR_IMPL(snmp_walk_to_json_output_val, zbx_snmp_walk_json_output_value_t *)
ZBX_PTR_VECTOR_IMPL(snmp_value_pair, zbx_snmp_value_pair_t *)

#define ZBX_PREPROC_SNMP_UTF8_FROM_HEX	1
#define ZBX_PREPROC_SNMP_MAC_FROM_HEX	2
#define ZBX_PREPROC_SNMP_UINT_FROM_BITS	3

#ifdef HAVE_NETSNMP
static char	zbx_snmp_init_done;

static int	preproc_snmp_translate_oid(const char *oid_in, char **oid_out)
{
	char	buffer[MAX_OID_LEN];
	oid	oid_tmp[MAX_OID_LEN];
	size_t	oid_len = MAX_OID_LEN;

	if (0 != get_node(oid_in, oid_tmp, &oid_len))
	{
		snprint_objid(buffer, sizeof(buffer), oid_tmp, oid_len);
		*oid_out = zbx_strdup(NULL, buffer);
		return SUCCEED;
	}

	return FAIL;
}
#endif

static zbx_hash_t	snmp_value_pair_hash_func(const void *d)
{
	const zbx_snmp_value_pair_t	*s = (const zbx_snmp_value_pair_t *)d;

	return ZBX_DEFAULT_STRING_HASH_FUNC(s->oid);
}

static int	snmp_value_pair_compare_func(const void *d1, const void *d2)
{
	const zbx_snmp_value_pair_t	*s1 = (const zbx_snmp_value_pair_t *)d1;
	const zbx_snmp_value_pair_t	*s2 = (const zbx_snmp_value_pair_t *)d2;

	return strcmp(s1->oid, s2->oid);
}

static void	snmp_value_pair_free(zbx_snmp_value_pair_t *p)
{
	zbx_free(p->oid);
	zbx_free(p->value);
	zbx_free(p);
}

static void	snmp_walk_json_output_obj_clear(zbx_snmp_walk_json_output_obj_t *obj)
{
	zbx_free(obj->key);
	zbx_vector_snmp_value_pair_clear_ext(&obj->values, snmp_value_pair_free);
	zbx_vector_snmp_value_pair_destroy(&obj->values);
}

static zbx_hash_t	snmp_walk_json_output_obj_hash_func(const void *d)
{
	const zbx_snmp_walk_json_output_obj_t	*s;
	s = (const zbx_snmp_walk_json_output_obj_t *)d;

	return ZBX_DEFAULT_STRING_HASH_FUNC(s->key);
}

static int	snmp_walk_json_output_obj_compare_func(const void *d1, const void *d2)
{
	const zbx_snmp_walk_json_output_obj_t	*s1 = (const zbx_snmp_walk_json_output_obj_t *)d1;
	const zbx_snmp_walk_json_output_obj_t	*s2 = (const zbx_snmp_walk_json_output_obj_t *)d2;

	return strcmp(s1->key, s2->key);
}

static int	snmp_hex_to_utf8(unsigned char *value, unsigned char *out, int size)
{
	int	len;

	zbx_remove_chars((char *)value, " ");

	if (FAIL == (len = zbx_hex2bin(value, out, size)))
		return FAIL;

	out[len] = '\0';
	zbx_replace_invalid_utf8((char *)out);

	return SUCCEED;
}

static int	snmp_hex_to_utf8_dyn(char *value, char **out)
{
	size_t	len;

	if ('\0' == *value)
		return FAIL;

	len = strlen(value) / 3 + 2;
	*out = (char *)zbx_malloc(NULL, len);

	if (FAIL == snmp_hex_to_utf8((unsigned char *)value, (unsigned char *)*out, (int)len))
	{
		zbx_free(*out);
		return FAIL;
	}

	return SUCCEED;
}

static int	snmp_hex_to_mac(char *out)
{
	while ('\0' != *out)
	{
		if (0 == isxdigit((unsigned char)out[0]) || 0 == isxdigit((unsigned char)out[1]))
			return FAIL;

		if ('\0' == out[2])
			break;

		if (' ' != out[2])
			return FAIL;

		out[2] = ':';
		out += 3;
	}

	return SUCCEED;
}

static int	snmp_hex_to_mac_dyn(const char *value, char **out)
{
	*out = zbx_strdup(NULL, value);

	if (SUCCEED != snmp_hex_to_mac(*out))
	{
		zbx_free(*out);
		return FAIL;
	}

	return SUCCEED;
}

static int	preproc_snmp_walk_to_json_params(const char *params,
		zbx_vector_snmp_walk_to_json_param_t *parsed_params)
{
	char	*token = NULL, *saveptr, *field_name, *params2, *oid_prefix = NULL;
	int	format_flag , idx = 0;

	if (NULL == params || '\0' == *params)
		return FAIL;

	params2 = zbx_strdup(NULL, params);
	token = strtok_r(params2, "\n", &saveptr);

	while (NULL != token)
	{
		if (0 == idx % 3)
		{
			field_name = token;
		}
		else if (2 == idx % 3)
		{
			format_flag = atoi(token);

			zbx_snmp_walk_to_json_param_t	parsed_param;

			parsed_param.field_name = zbx_strdup(NULL, field_name);
			parsed_param.format_flag = format_flag;
			parsed_param.oid_prefix = oid_prefix;
			oid_prefix = NULL;

			zbx_vector_snmp_walk_to_json_param_append(parsed_params, parsed_param);
		}
		else
		{
#ifdef HAVE_NETSNMP
			char	*oid_tr_tmp = NULL;

			if (SUCCEED == preproc_snmp_translate_oid(token, &oid_tr_tmp))
				oid_prefix = oid_tr_tmp;
			else
				oid_prefix = zbx_strdup(NULL, token);
#else
			oid_prefix = zbx_strdup(NULL, token);
#endif
		}

		token = strtok_r(NULL, "\n", &saveptr);
		idx++;
	}

	zbx_free(params2);
	zbx_free(oid_prefix);

	if (0 != idx % 3)
		return FAIL;

	return SUCCEED;
}

static size_t	preproc_snmp_pair_parse_oid(const char *ptr, zbx_snmp_value_pair_t *p)
{
	const char	*start = ptr;
	size_t		len;

	while (1)
	{
		if ('.' != *ptr && 0 == isdigit((unsigned char)*ptr))
			break;
		ptr++;
	}

	if (0 != (len = (size_t)(ptr - start)))
	{
		p->oid = zbx_malloc(NULL, len + 1);
		memcpy(p->oid, start, len);
		p->oid[len] = '\0';
	}

	return len;
}

static size_t	preproc_snmp_parse_type(const char *ptr, char **type)
{
	const char	*start = ptr++;
	size_t		len;

	while (0 != isalnum(*ptr) || '-' == *ptr)
	{
		ptr++;
	}

	len = (size_t)(ptr - start);
	*type = zbx_malloc(NULL, len + 1);
	memcpy(*type, start, len);
	(*type)[len] = '\0';

	return len;
}

static size_t	preproc_snmp_parse_value(const char *ptr, zbx_snmp_value_pair_t *p)
{
	const char	*start = ptr;
	size_t		len;

	if ('"' != *ptr)
	{
		if (NULL == (ptr = strchr(ptr, '\n')))
		{
			len = strlen(start);
		}
		else
		{
			if (ZBX_SNMP_TYPE_HEX == p->type || ZBX_SNMP_TYPE_BITS == p->type)
			{
				while ('.' != *(ptr + 1) && '\0' != *(ptr + 1))
					ptr++;
			}
			else if (ZBX_SNMP_TYPE_STRING == p->type)
			{
				while ('\0' != *(ptr + 1) &&
						('\n' != *ptr || '.' != *(ptr + 1) || 0 == isdigit(*(ptr + 2))))
					ptr++;
			}

			len = (size_t)(ptr - start);
		}

		p->value = zbx_malloc(NULL, len + 1);
		memcpy(p->value, start, len);
		(p->value)[len] = '\0';

		if (ZBX_SNMP_TYPE_HEX == p->type || ZBX_SNMP_TYPE_BITS == p->type)
		{
			zbx_remove_chars(p->value, "\n");
			zbx_rtrim(p->value, " ");
		}

		return len;
	}
	else
	{
		char	*out;
		int	escape = 0;

		ptr++;

		while ('"' != *ptr || 0 != escape)
		{
			if ('\0' == *ptr)
				return 0;

			if (0 == escape)
			{
				if ('\\' == *ptr)
					escape = 1;
			}
			else
			{
				escape = 0;
			}

			ptr++;
		}

		len = (size_t)(++ptr - start);
		out = p->value = zbx_malloc(NULL, len - 1);
		ptr = start + 1;

		while ('"' != *ptr)
		{
			if ('\\' == *ptr)
			{
				ptr++;
			}
			*out++ = *ptr++;
		}
		*out = '\0';

		return len;
	}
}

static int	preproc_snmp_parse_line(const char *data, zbx_snmp_value_pair_t *p, size_t *line_len, char **error)
{
	int		ret = FAIL;
	size_t		len;
	const char	*start = data;
	char		*type = NULL;

	if (NULL == data || '\0' == *data)
		return FAIL;

	if (0 == (len = preproc_snmp_pair_parse_oid(data, p)))
	{
		*error = strdup("invalid OID format");
		return FAIL;
	}

	data += len;
	while (' ' == *data)
		data++;

	if ('=' != *data)
	{
		*error = strdup("invalid value separator following OID");
		goto out;
	}

	data++;
reparse_type:
	while (' ' == *data)
		data++;

	if (0 != isupper((unsigned char)*data))
	{
		len = preproc_snmp_parse_type(data, &type);
		data += len;

		if (':' != *data)
		{
			if (0 == strcmp(type, "No") &&
					0 == strncmp(data, " more variables", ZBX_CONST_STRLEN(" more variables")))
			{
				while ('\n' != *data && '\0' != *data)
					data++;

				goto eol;
			}

			if (0 == strcmp(type, "NULL") && ('\n' == *data || '\0' == *data))
			{
				p->type = ZBX_SNMP_TYPE_UNDEFINED;

				goto eol;
			}

			*error = strdup("invalid value type format");
			goto out;
		}

		data++;
		while (' ' == *data)
			data++;

		if (0 == strcmp(type, "Opaque"))
		{
			zbx_free(type);
			goto reparse_type;
		}
	}

	if (NULL != type)
	{
		if (0 == strcmp(type, "Hex-STRING"))
		{
			p->type = ZBX_SNMP_TYPE_HEX;
		}
		else if (0 == strcmp(type, "BITS"))
		{
			p->type = ZBX_SNMP_TYPE_BITS;
		}
		else if (0 == strcmp(type, "STRING"))
		{
			p->type = ZBX_SNMP_TYPE_STRING;
		}
		else
			p->type = ZBX_SNMP_TYPE_UNDEFINED;
	}
	else
		p->type = ZBX_SNMP_TYPE_UNDEFINED;

	len = preproc_snmp_parse_value(data, p);
	data += len;
eol:
	if ('\0' == *data)
	{
		*line_len = (size_t)(data - start);
		ret = SUCCEED;
		goto out;
	}

	if ('\n' != *data)
	{
		zbx_free(p->value);
		*error = strdup("invalid text following value");
		goto out;
	}

	*line_len = (size_t)(data + 1 - start);
	ret = 0;
out:
	zbx_free(type);

	if (FAIL == ret)
		zbx_free(p->oid);

	return ret;
}

static int	preproc_snmp_walk_to_pairs(zbx_hashset_t *pairs, const char *data, char **error)
{
	size_t			len;
	zbx_snmp_value_pair_t	p;

	memset(&p, 0, sizeof(zbx_snmp_value_pair_t));

	while (FAIL != preproc_snmp_parse_line(data, &p, &len, error))
	{
		if (NULL == p.value)
			p.value = zbx_strdup(NULL, "NULL");

		if (NULL == zbx_hashset_insert(pairs, &p, sizeof(zbx_snmp_value_pair_t)))
		{
			*error = zbx_dsprintf(*error, "duplicate OID detected: %s", p.oid);
			zbx_free(p.oid);
			zbx_free(p.value);
			return FAIL;
		}

		data += len;
		memset(&p, 0, sizeof(zbx_snmp_value_pair_t));
	}

	if (NULL != *error)
		return FAIL;

	return SUCCEED;
}

static void	zbx_vector_snmp_walk_to_json_param_clear_ext(zbx_vector_snmp_walk_to_json_param_t *v)
{
	for (int i = 0; i < v->values_num; i++)
	{
		zbx_free(v->values[i].field_name);
		zbx_free(v->values[i].oid_prefix);
	}

	zbx_vector_snmp_walk_to_json_param_clear(v);
}

static int	preproc_parse_value_from_walk_params(const char *params, char **oid_needle, int *format_flag)
{
	char	*delim_ptr;
	size_t	delim_offset, alloc_offset = 0, alloc_len = 0;

	if (NULL == (delim_ptr = strchr(params, '\n')))
		return FAIL;

	if (0 == (delim_offset = (size_t)(delim_ptr - params)))
		return FAIL;

	zbx_strncpy_alloc(oid_needle, &alloc_offset, &alloc_len, params, delim_offset);
	*format_flag = atoi(params + delim_offset + 1);

	return SUCCEED;
}

static int	preproc_snmp_convert_bits_value(char **value, int format, char **error)
{
#define SNMP_UINT_FROM_BITS_MAX_BYTES	(8 * 2)
#define HEX_CONV(x)			(x > '9' ? x - 'A' + 10 : x - '0')

	if (ZBX_PREPROC_SNMP_UINT_FROM_BITS == format)
	{
		zbx_uint64_t	iout = 0;
		char		*v;
		size_t		len;

		v = *value;
		zbx_remove_chars(v, " ");
		len = strlen(v);

		if (0 != len % 2)
		{
			*error = zbx_dsprintf(NULL, "Cannot convert bit value '%s' to unsigned integer", *value);
			return FAIL;
		}

		if (SNMP_UINT_FROM_BITS_MAX_BYTES < len)
			len = SNMP_UINT_FROM_BITS_MAX_BYTES;

		for (int i = 0; i < (int)len; i += 2)
		{
			char	b1, b2;

			b1 = (char)toupper(v[i]);
			b2 = (char)toupper(v[i + 1]);

			if (0 == isxdigit((unsigned char)b1) || 0 == isxdigit((unsigned char)b2))
			{
				*error = zbx_dsprintf(NULL, "Cannot convert bit value '%s' to unsigned integer",
						*value);

				return FAIL;
			}

			iout += (zbx_uint64_t)(HEX_CONV(b1) * 16 + HEX_CONV(b2)) << (i * 4);
		}

		*value = zbx_dsprintf(*value, ZBX_FS_UI64, iout);
	}

	return SUCCEED;
#undef HEX_CONV
#undef SNMP_UINT_FROM_BITS_MAX_BYTES
}

static int	preproc_snmp_convert_hex_value(char **value, int format, char **error)
{
	char	*out = NULL;

	switch (format)
	{
		case ZBX_PREPROC_SNMP_UTF8_FROM_HEX:
			if (SUCCEED != snmp_hex_to_utf8_dyn(*value, &out))
			{
				*error = zbx_dsprintf(NULL, "Cannot convert hex value '%s' to utf-8 string",
						*value);
				return FAIL;
			}

			break;
		case ZBX_PREPROC_SNMP_MAC_FROM_HEX:
			if (SUCCEED != snmp_hex_to_mac_dyn(*value, &out))
			{
				*error = zbx_dsprintf(NULL, "Cannot convert hex value '%s' to mac address",
						*value);
				return FAIL;
			}
			break;
		default:
			return SUCCEED;
	}

	zbx_free(*value);
	*value = out;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: format SNMP value according to its type and specified format      *
 *                                                                            *
 * Parameters: value  - [IN/OUT] the value to convert. If conversion succeeds *
 *                               the old value is freed and a new one is      *
 *                               allocated                                    *
 *             type   - [IN] the value type returned by SNMP                  *
 *             format - [IN] the value format specified by configuration      *
 *             error  - [OUT] the error message                               *
 *                                                                            *
 * Return value: SUCCEED - the value was converted successfully               *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	preproc_snmp_convert_value(char **value, zbx_snmp_type_t type, int format, char **error)
{
	switch (type)
	{
		case ZBX_SNMP_TYPE_HEX:
			return preproc_snmp_convert_hex_value(value, format, error);
		case ZBX_SNMP_TYPE_BITS:
			return preproc_snmp_convert_bits_value(value, format, error);
		case ZBX_SNMP_TYPE_UNDEFINED:
		default:
			return SUCCEED;
	}
}

static int	preproc_snmp_value_from_walk(const char *data, const char *params, char **output, char **error)
{
	int			ret = FAIL;
	size_t			len, offset;
	zbx_snmp_value_pair_t	p;
	char			*oid_needle = NULL;
	int			format_flag = 0;
#ifdef HAVE_NETSNMP
	char			*oid_tr_tmp = NULL;
	const char		*oid_tr;
#endif
	if (FAIL == preproc_parse_value_from_walk_params(params, &oid_needle, &format_flag))
	{
		*error = zbx_strdup(NULL, "failed to parse params");
		return FAIL;
	}

#ifdef HAVE_NETSNMP
	if (SUCCEED == preproc_snmp_translate_oid(oid_needle, &oid_tr_tmp))
		oid_tr = oid_tr_tmp;
	else
		oid_tr = oid_needle;

	offset = ('.' == oid_tr[0] ? 0 : 1);
#else
	offset = ('.' == oid_needle[0] ? 0 : 1);
#endif
	memset(&p, 0, sizeof(zbx_snmp_value_pair_t));

	while (FAIL != preproc_snmp_parse_line(data, &p, &len, error))
	{
#ifdef HAVE_NETSNMP
		if (0 == strcmp(oid_tr, p.oid + offset))
#else
		if (0 == strcmp(oid_needle, p.oid + offset))
#endif
		{

			if (SUCCEED != preproc_snmp_convert_value(&p.value, p.type, format_flag, error))
			{
				zbx_free(p.oid);
				zbx_free(p.value);
				goto out;
			}

			zbx_free(p.oid);
			*output = (NULL != p.value ? p.value : zbx_strdup(NULL, "NULL"));
			ret = SUCCEED;
			goto out;
		}

		data += len;
		zbx_free(p.oid);
		zbx_free(p.value);
	}

	if (NULL == *error)
		*error = zbx_strdup(NULL, "no data was found");
out:
	zbx_free(oid_needle);
#ifdef HAVE_NETSNMP
	zbx_free(oid_tr_tmp);
#endif
	return ret;
}

static void	snmp_prepend_oid_dot(const char *oid_in, zbx_snmp_value_pair_t *p)
{
	if ('.' != oid_in[0])
		p->oid = zbx_dsprintf(p->oid, ".%s", oid_in);
	else
		p->oid = zbx_strdup(NULL, oid_in);
}

static int	snmp_value_from_cached_walk(zbx_snmp_value_cache_t *cache, const char *params, char **output,
		char **error)
{
	int			ret = FAIL;
	zbx_snmp_value_pair_t	*pair_cached, pair_local = {0};
	char			*oid_needle = NULL;
	int			format_flag = 0;
#ifdef HAVE_NETSNMP
	char			*oid_tr_tmp = NULL;
#endif
	if (FAIL == preproc_parse_value_from_walk_params(params, &oid_needle, &format_flag))
	{
		*error = zbx_strdup(NULL, "failed to parse params");
		return FAIL;
	}

#ifdef HAVE_NETSNMP
	if (SUCCEED == preproc_snmp_translate_oid(oid_needle, &oid_tr_tmp))
		pair_local.oid = oid_tr_tmp;
	else
		snmp_prepend_oid_dot(oid_needle, &pair_local);
#else
	snmp_prepend_oid_dot(oid_needle, &pair_local);
#endif

	if (NULL == (pair_cached = (zbx_snmp_value_pair_t *)zbx_hashset_search(&cache->pairs, &pair_local)))
	{
		*error = zbx_strdup(NULL, "no data was found");
		goto out;
	}

	*output = zbx_strdup(NULL, pair_cached->value);

	if (SUCCEED != preproc_snmp_convert_value(output, pair_cached->type, format_flag, error))
	{
		zbx_free(*output);
		goto out;
	}

	ret = SUCCEED;
out:
	zbx_free(pair_local.oid);
	zbx_free(oid_needle);

	return ret;
}

void	zbx_snmp_value_cache_clear(zbx_snmp_value_cache_t *cache)
{
	zbx_hashset_iter_t		iter;
	zbx_snmp_value_pair_t		*pair;

	zbx_hashset_iter_reset(&cache->pairs, &iter);

	while (NULL != (pair = (zbx_snmp_value_pair_t *)zbx_hashset_iter_next(&iter)))
	{
		zbx_free(pair->oid);
		zbx_free(pair->value);
	}

	zbx_hashset_destroy(&cache->pairs);
}

int	zbx_snmp_value_cache_init(zbx_snmp_value_cache_t *cache, const char *data, char **error)
{
	zbx_hashset_create(&cache->pairs, 100, snmp_value_pair_hash_func,
			snmp_value_pair_compare_func);

	if (FAIL == preproc_snmp_walk_to_pairs(&cache->pairs, data, error))
	{
		zbx_snmp_value_cache_clear(cache);
		return FAIL;
	}

	return SUCCEED;
}

static void	snmp_walk_serialize_json(zbx_hashset_t *grouped_prefixes, char **result)
{
	struct zbx_json			json;
	zbx_hashset_iter_t		iter;
	zbx_snmp_walk_json_output_obj_t	*outobj = NULL;

	zbx_json_initarray(&json, ZBX_JSON_STAT_BUF_LEN);

	zbx_hashset_iter_reset(grouped_prefixes, &iter);

	while (NULL != (outobj = (zbx_snmp_walk_json_output_obj_t *)zbx_hashset_iter_next(&iter)))
	{
		zbx_json_addobject(&json, NULL);
		zbx_json_addstring(&json, "{#SNMPINDEX}", outobj->key, ZBX_JSON_TYPE_STRING);

		for (int k = 0; k < outobj->values.values_num; k++)
		{
			zbx_snmp_value_pair_t	*vv = outobj->values.values[k];

			zbx_json_addstring(&json, vv->oid, vv->value, ZBX_JSON_TYPE_STRING);
		}

		zbx_json_close(&json);
	}

	zbx_json_close(&json);
	*result = zbx_strdup(NULL, json.buffer);
	zbx_json_free(&json);
}

int	item_preproc_snmp_walk_to_value(zbx_pp_cache_t *cache, zbx_variant_t *value, const char *params,
		char **errmsg)
{
	char	*value_out = NULL, *err = NULL;
	int	ret = FAIL;

	if (NULL == params || '\0' == *params)
	{
		*errmsg = zbx_strdup(*errmsg, "parameter should be set");
		return FAIL;
	}

	if (NULL == cache || ZBX_PREPROC_SNMP_WALK_VALUE != cache->type)
	{
		if (FAIL == item_preproc_convert_value(value, ZBX_VARIANT_STR, errmsg))
			return FAIL;

		ret = preproc_snmp_value_from_walk(value->data.str, params, &value_out, &err);
	}
	else
	{
		zbx_snmp_value_cache_t	*snmp_cache;

		if (NULL != cache->error)
		{
			*errmsg = zbx_strdup(NULL, cache->error);
			return FAIL;
		}

		if (NULL == (snmp_cache = (zbx_snmp_value_cache_t *)cache->data))
		{
			if (FAIL == item_preproc_convert_value(value, ZBX_VARIANT_STR, errmsg))
				return FAIL;

			snmp_cache = (zbx_snmp_value_cache_t *)zbx_malloc(NULL, sizeof(zbx_snmp_value_cache_t));

			if (SUCCEED != zbx_snmp_value_cache_init(snmp_cache, value->data.str, &cache->error))
			{
				zbx_free(snmp_cache);
				*errmsg = zbx_strdup(NULL, cache->error);
				return FAIL;
			}

			cache->data = (void *)snmp_cache;
		}

		ret = snmp_value_from_cached_walk(snmp_cache, params, &value_out, &err);
	}

	if (FAIL == ret)
	{
		*errmsg = zbx_dsprintf(*errmsg, "unable to extract value for given OID: %s", err);
		zbx_free(err);
		return FAIL;
	}

	zbx_variant_clear(value);
	zbx_variant_set_str(value, value_out);

	return SUCCEED;
}

int	item_preproc_snmp_get_to_value(zbx_variant_t *value, const char *params, char **errmsg)
{
	char	*err = NULL;
	int	ret = FAIL, format;

	if (FAIL == item_preproc_convert_value(value, ZBX_VARIANT_STR, errmsg))
		return FAIL;

	zbx_remove_chars(value->data.str, "\r\n");
	zbx_rtrim(value->data.str, " ");

	switch ((format = atoi(params)))
	{
		case ZBX_PREPROC_SNMP_UTF8_FROM_HEX:
		case ZBX_PREPROC_SNMP_MAC_FROM_HEX:
			ret = preproc_snmp_convert_hex_value(&value->data.str, format, &err);
			break;
		case ZBX_PREPROC_SNMP_UINT_FROM_BITS:
			ret = preproc_snmp_convert_bits_value(&value->data.str, format, &err);
			break;
		default:
			*errmsg = zbx_dsprintf(*errmsg, "unknown parameter '%s'", params);
			return FAIL;
	}

	if (FAIL == ret)
	{
		*errmsg = zbx_dsprintf(*errmsg, "cannot extract value: %s", err);
		zbx_free(err);
		return FAIL;
	}

	return SUCCEED;
}

int	item_preproc_snmp_walk_to_json(zbx_variant_t *value, const char *params, char **errmsg)
{
	int					ret = SUCCEED;
	char					*result = NULL, *data;
	zbx_hashset_t				grouped_prefixes;
	size_t					len;
	zbx_vector_snmp_walk_to_json_param_t	parsed_params;
	zbx_snmp_value_pair_t			p;

	if (FAIL == item_preproc_convert_value(value, ZBX_VARIANT_STR, errmsg))
		return FAIL;

	zbx_vector_snmp_walk_to_json_param_create(&parsed_params);

	zbx_hashset_create_ext(&grouped_prefixes, 100, snmp_walk_json_output_obj_hash_func,
			snmp_walk_json_output_obj_compare_func, (zbx_clean_func_t)snmp_walk_json_output_obj_clear,
			ZBX_DEFAULT_MEM_MALLOC_FUNC, ZBX_DEFAULT_MEM_REALLOC_FUNC, ZBX_DEFAULT_MEM_FREE_FUNC);

	if (FAIL == preproc_snmp_walk_to_json_params(params, &parsed_params))
	{
		*errmsg = zbx_dsprintf(*errmsg, "failed to parse step parameters");
		ret = FAIL;
		goto out;
	}

	data = value->data.str;
	memset(&p, 0, sizeof(zbx_snmp_value_pair_t));

	while (FAIL != preproc_snmp_parse_line(data, &p, &len, errmsg))
	{
		zbx_snmp_walk_to_json_param_t	param_field;
		size_t				prefix_len;

		for (int i = 0; i < parsed_params.values_num; i++)
		{
			zbx_snmp_walk_json_output_obj_t	*oobj_cached, oobj_local;
			zbx_snmp_value_pair_t		*output_value;

			param_field = parsed_params.values[i];
			prefix_len = strlen(param_field.oid_prefix);

			if ('.' == param_field.oid_prefix[prefix_len - 1])
			{
				param_field.oid_prefix[prefix_len - 1] = '\0';
				prefix_len--;
			}

			if ('.' != param_field.oid_prefix[0])
			{
				if (0 != strncmp(param_field.oid_prefix, p.oid + 1, prefix_len))
					continue;

				prefix_len++;
			}
			else if (0 != strncmp(param_field.oid_prefix, p.oid, prefix_len))
				continue;

			if ('.' != p.oid[prefix_len])
				continue;

			if (SUCCEED != preproc_snmp_convert_value(&p.value, p.type, param_field.format_flag, errmsg))
			{
				zbx_free(p.oid);
				zbx_free(p.value);
				ret = FAIL;
				goto out;
			}

			oobj_local.key = zbx_strdup(NULL, prefix_len + p.oid + 1);
			zbx_rtrim(oobj_local.key, " ");

			output_value = (zbx_snmp_value_pair_t *)zbx_malloc(NULL,
					sizeof(zbx_snmp_value_pair_t));

			output_value->oid = zbx_strdup(NULL, param_field.field_name);
			output_value->value = NULL == p.value ? NULL : zbx_strdup(NULL, p.value);

			if (NULL == (oobj_cached = zbx_hashset_search(&grouped_prefixes, &oobj_local)))
			{
				zbx_vector_snmp_value_pair_create(&oobj_local.values);
				zbx_vector_snmp_value_pair_append(&oobj_local.values, output_value);
				zbx_hashset_insert(&grouped_prefixes, &oobj_local, sizeof(oobj_local));
			}
			else
			{
				zbx_free(oobj_local.key);

				for (int j = 0; j < oobj_cached->values.values_num; j++)
				{
					zbx_snmp_value_pair_t *vp = oobj_cached->values.values[j];

					if (0 == strcmp(vp->oid, output_value->oid))
					{
						snmp_value_pair_free(output_value);
						goto skip;
					}
				}

				zbx_vector_snmp_value_pair_append(&oobj_cached->values, output_value);
			}
		}
skip:
		data += len;
		zbx_free(p.oid);
		zbx_free(p.value);
	}

	if (NULL != *errmsg)
	{
		ret = FAIL;
		goto out;
	}

	if (0 < grouped_prefixes.num_data)
	{
		snmp_walk_serialize_json(&grouped_prefixes, &result);
	}
	else
	{
		*errmsg = zbx_dsprintf(*errmsg, "no data was found");
		ret = FAIL;
	}
out:
	zbx_vector_snmp_walk_to_json_param_clear_ext(&parsed_params);
	zbx_vector_snmp_walk_to_json_param_destroy(&parsed_params);
	zbx_hashset_destroy(&grouped_prefixes);

	if (SUCCEED == ret)
	{
		zbx_variant_clear(value);
		zbx_variant_set_str(value, result);
	}

	return ret;
}

#ifdef HAVE_NETSNMP
/* This function has to be moved to separate SNMP library when such refactoring will be done in future */
static void	zbx_init_snmp(void)
{
	sigset_t	mask, orig_mask;

	if (1 == zbx_snmp_init_done)
		return;

	sigemptyset(&mask);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGUSR2);
	sigaddset(&mask, SIGHUP);
	sigaddset(&mask, SIGQUIT);
	zbx_sigmask(SIG_BLOCK, &mask, &orig_mask);

	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD, 1);
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE, 1);

	init_snmp(preproc_get_progname_cb()());

	netsnmp_init_mib();
	zbx_snmp_init_done = 1;

	zbx_sigmask(SIG_SETMASK, &orig_mask, NULL);
}

void	preproc_init_snmp(void)
{
	zbx_init_snmp();
	netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_PRINT_NUMERIC_OIDS, 1);
	netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT, NETSNMP_OID_OUTPUT_NUMERIC);
}

void	preproc_shutdown_snmp(void)
{
	sigset_t	mask, orig_mask;

	sigemptyset(&mask);
	sigaddset(&mask, SIGTERM);
	sigaddset(&mask, SIGUSR2);
	sigaddset(&mask, SIGHUP);
	sigaddset(&mask, SIGQUIT);
	zbx_sigmask(SIG_BLOCK, &mask, &orig_mask);

	snmp_shutdown(preproc_get_progname_cb()());
	zbx_snmp_init_done = 0;

	zbx_sigmask(SIG_SETMASK, &orig_mask, NULL);
}
#endif