/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@netfilter.org)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <assert.h>				/* assert */
#include <errno.h>				/* errno */
#include <stdio.h>				/* snprintf */
#include <netdb.h>				/* getservbyport */
#include <sys/types.h>				/* inet_ntop */
#include <sys/socket.h>				/* inet_ntop */
#include <arpa/inet.h>				/* inet_ntop */
#include <net/ethernet.h>			/* ETH_ALEN */
#include <net/if.h>				/* IFNAMSIZ */
#include <inttypes.h>				/* PRIx macro */

#include <libipset/debug.h>			/* D() */
#include <libipset/data.h>			/* ipset_data_* */
#include <libipset/icmp.h>			/* icmp_to_name */
#include <libipset/icmpv6.h>			/* icmpv6_to_name */
#include <libipset/parse.h>			/* IPSET_*_SEPARATOR */
#include <libipset/types.h>			/* ipset set types */
#include <libipset/session.h>			/* IPSET_FLAG_ */
#include <libipset/utils.h>			/* UNUSED */
#include <libipset/ipset.h>			/* IPSET_ENV_* */
#include <libipset/print.h>			/* prototypes */

/* Print data (to output buffer). All function must follow snprintf. */

#define SNPRINTF_FAILURE(size, len, offset)			\
do {								\
	if (size < 0 || (unsigned int) size >= len)		\
		return offset + size;				\
	offset += size;						\
	len -= size;						\
} while (0)

/**
 * ipset_print_ether - print ethernet address to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print Ethernet address to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_ether(char *buf, unsigned int len,
		  const struct ipset_data *data, enum ipset_opt opt,
		  uint8_t env UNUSED)
{
	const unsigned char *ether;
	int i, size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_ETHER);

	if (len < ETH_ALEN*3)
		return -1;

	ether = ipset_data_get(data, opt);
	assert(ether);

	size = snprintf(buf, len, "%02X", ether[0]);
	SNPRINTF_FAILURE(size, len, offset);
	for (i = 1; i < ETH_ALEN; i++) {
		size = snprintf(buf + offset, len, ":%02X", ether[i]);
		SNPRINTF_FAILURE(size, len, offset);
	}

	return offset;
}

/**
 * ipset_print_family - print INET family
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print INET family string to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_family(char *buf, unsigned int len,
		   const struct ipset_data *data,
		   enum ipset_opt opt ASSERT_UNUSED,
		   uint8_t env UNUSED)
{
	uint8_t family;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_FAMILY);

	if (len < strlen("inet6") + 1)
		return -1;

	family = ipset_data_family(data);

	return snprintf(buf, len, "%s",
			family == AF_INET ? "inet" :
			family == AF_INET6 ? "inet6" : "any");
}

/**
 * ipset_print_type - print ipset type string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print ipset module string identifier to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_type(char *buf, unsigned int len,
		 const struct ipset_data *data, enum ipset_opt opt,
		 uint8_t env UNUSED)
{
	const struct ipset_type *type;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_TYPE);

	type = ipset_data_get(data, opt);
	assert(type);
	if (len < strlen(type->name) + 1)
		return -1;

	return snprintf(buf, len, "%s", type->name);
}

static inline int
__getnameinfo4(char *buf, unsigned int len,
	       int flags, const union nf_inet_addr *addr)
{
	struct sockaddr_in saddr;
	int err;

	memset(&saddr, 0, sizeof(saddr));
	in4cpy(&saddr.sin_addr, &addr->in);
	saddr.sin_family = NFPROTO_IPV4;

	err = getnameinfo((const struct sockaddr *)&saddr,
			  sizeof(saddr),
			  buf, len, NULL, 0, flags);

	if (!(flags & NI_NUMERICHOST) && (err == EAI_AGAIN))
		err = getnameinfo((const struct sockaddr *)&saddr,
				  sizeof(saddr),
				  buf, len, NULL, 0,
				  flags | NI_NUMERICHOST);
	D("getnameinfo err: %i, errno %i", err, errno);
	if (err == 0 && strstr(buf, IPSET_RANGE_SEPARATOR) != NULL) {
		const char escape[] = IPSET_ESCAPE_START;
		/* Escape hostname */
		if (strlen(buf) + 2 > len) {
			err = EAI_OVERFLOW;
			return -1;
		}
		memmove(buf + 1, buf, strlen(buf) + 1);
		buf[0] = escape[0];
		strcat(buf, IPSET_ESCAPE_END);
	}
	return (err == 0 ? (int)strlen(buf) :
	       (err == EAI_OVERFLOW || err == EAI_SYSTEM) ? (int)len : -1);
}

static inline int
__getnameinfo6(char *buf, unsigned int len,
	       int flags, const union nf_inet_addr *addr)
{
	struct sockaddr_in6 saddr;
	int err;

	memset(&saddr, 0, sizeof(saddr));
	in6cpy(&saddr.sin6_addr, &addr->in6);
	saddr.sin6_family = NFPROTO_IPV6;

	err = getnameinfo((const struct sockaddr *)&saddr,
			  sizeof(saddr),
			  buf, len, NULL, 0, flags);

	if (!(flags & NI_NUMERICHOST) && (err == EAI_AGAIN))
		err = getnameinfo((const struct sockaddr *)&saddr,
				  sizeof(saddr),
				  buf, len, NULL, 0,
				  flags | NI_NUMERICHOST);
	D("getnameinfo err: %i, errno %i", err, errno);
	if (err == 0 && strstr(buf, IPSET_RANGE_SEPARATOR) != NULL) {
		const char escape[] = IPSET_ESCAPE_START;
		/* Escape hostname */
		if (strlen(buf) + 2 > len) {
			err = EAI_OVERFLOW;
			return -1;
		}
		memmove(buf + 1, buf, strlen(buf) + 1);
		buf[0] = escape[0];
		strcat(buf, IPSET_ESCAPE_END);
	}
	return (err == 0 ? (int)strlen(buf) :
	       (err == EAI_OVERFLOW || err == EAI_SYSTEM) ? (int)len : -1);
}

#define SNPRINTF_IP(mask, f)						\
static int								\
snprintf_ipv##f(char *buf, unsigned int len, int flags,			\
	       const union nf_inet_addr *ip, uint8_t cidr)		\
{									\
	int size, offset = 0;						\
									\
	size = __getnameinfo##f(buf, len, flags, ip);			\
	SNPRINTF_FAILURE(size, len, offset);				\
									\
	D("cidr %u mask %u", cidr, mask);				\
	if (cidr == mask)						\
		return offset;						\
	D("print cidr");						\
	size = snprintf(buf + offset, len,				\
			"%s%u", IPSET_CIDR_SEPARATOR, cidr);		\
	SNPRINTF_FAILURE(size, len, offset);				\
	return offset;							\
}

SNPRINTF_IP(32, 4)

SNPRINTF_IP(128, 6)

/**
 * ipset_print_ip - print IPv4|IPv6 address to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print IPv4|IPv6 address, address/cidr or address range to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_ip(char *buf, unsigned int len,
	       const struct ipset_data *data, enum ipset_opt opt,
	       uint8_t env)
{
	const union nf_inet_addr *ip;
	uint8_t family, cidr;
	int flags, size, offset = 0;
	enum ipset_opt cidropt;
	const char *quoted = env & IPSET_ENV_QUOTED ? "\"" : "";

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2 || opt == IPSET_OPT_BITMASK);

	D("len: %u", len);
	family = ipset_data_family(data);
	cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2;
	if (ipset_data_test(data, cidropt)) {
		cidr = *(const uint8_t *) ipset_data_get(data, cidropt);
		D("CIDR: %u", cidr);
	} else
		cidr = family == NFPROTO_IPV6 ? 128 : 32;
	flags = (env & IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST;

	size = snprintf(buf, len, "%s", quoted);
	SNPRINTF_FAILURE(size, len, offset);

	ip = ipset_data_get(data, opt);
	assert(ip);
	if (family == NFPROTO_IPV4)
		size = snprintf_ipv4(buf + offset, len, flags, ip, cidr);
	else if (family == NFPROTO_IPV6)
		size = snprintf_ipv6(buf + offset, len, flags, ip, cidr);
	else
		return -1;
	D("size %i, len %u", size, len);
	SNPRINTF_FAILURE(size, len, offset);

	D("len: %u, offset %u", len, offset);
	if (!ipset_data_test(data, IPSET_OPT_IP_TO)) {
		size = snprintf(buf + offset, len, "%s", quoted);
		SNPRINTF_FAILURE(size, len, offset);
		return offset;
	}

	size = snprintf(buf + offset, len, "%s", IPSET_RANGE_SEPARATOR);
	SNPRINTF_FAILURE(size, len, offset);

	ip = ipset_data_get(data, IPSET_OPT_IP_TO);
	if (family == NFPROTO_IPV4)
		size = snprintf_ipv4(buf + offset, len, flags, ip, cidr);
	else if (family == NFPROTO_IPV6)
		size = snprintf_ipv6(buf + offset, len, flags, ip, cidr);
	else
		return -1;

	SNPRINTF_FAILURE(size, len, offset);

	size = snprintf(buf + offset, len, "%s", quoted);
	SNPRINTF_FAILURE(size, len, offset);

	return offset;
}

/**
 * ipset_print_ipaddr - print IPv4|IPv6 address to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print IPv4|IPv6 address or address/cidr to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_ipaddr(char *buf, unsigned int len,
		   const struct ipset_data *data, enum ipset_opt opt,
		   uint8_t env)
{
	const union nf_inet_addr *ip;
	uint8_t family, cidr;
	enum ipset_opt cidropt;
	int flags;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_IP ||
	       opt == IPSET_OPT_IP_TO ||
	       opt == IPSET_OPT_IP2);

	family = ipset_data_family(data);
	cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2;
	if (ipset_data_test(data, cidropt))
		cidr = *(const uint8_t *) ipset_data_get(data, cidropt);
	else
		cidr = family == NFPROTO_IPV6 ? 128 : 32;
	flags = (env & IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST;

	ip = ipset_data_get(data, opt);
	assert(ip);
	if (family == NFPROTO_IPV4)
		return snprintf_ipv4(buf, len, flags, ip, cidr);
	else if (family == NFPROTO_IPV6)
		return snprintf_ipv6(buf, len, flags, ip, cidr);

	return -1;
}

/**
 * ipset_print_number - print number to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print number to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_number(char *buf, unsigned int len,
		   const struct ipset_data *data, enum ipset_opt opt,
		   uint8_t env UNUSED)
{
	size_t maxsize;
	const void *number;

	assert(buf);
	assert(len > 0);
	assert(data);

	number = ipset_data_get(data, opt);
	maxsize = ipset_data_sizeof(opt, AF_INET);
	D("opt: %u, maxsize %zu", opt, maxsize);
	if (maxsize == sizeof(uint8_t))
		return snprintf(buf, len, "%u", *(const uint8_t *) number);
	else if (maxsize == sizeof(uint16_t))
		return snprintf(buf, len, "%u", *(const uint16_t *) number);
	else if (maxsize == sizeof(uint32_t))
		return snprintf(buf, len, "%lu",
				(long unsigned) *(const uint32_t *) number);
	else if (maxsize == sizeof(uint64_t))
		return snprintf(buf, len, "%llu",
				(long long unsigned) *(const uint64_t *) number);
	else
		assert(0);
	return 0;
}

/**
 * ipset_print_hexnumber - print number in hex to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print number in hex to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_hexnumber(char *buf, unsigned int len,
		      const struct ipset_data *data, enum ipset_opt opt,
		      uint8_t env)
{
	size_t maxsize;
	const void *number;
	const char *quoted = env & IPSET_ENV_QUOTED ? "\"" : "";

	assert(buf);
	assert(len > 0);
	assert(data);

	number = ipset_data_get(data, opt);
	maxsize = ipset_data_sizeof(opt, AF_INET);
	D("opt: %u, maxsize %zu", opt, maxsize);
	if (maxsize == sizeof(uint8_t))
		return snprintf(buf, len, "%s0x%02"PRIx8"%s",
				quoted, *(const uint8_t *) number, quoted);
	else if (maxsize == sizeof(uint16_t))
		return snprintf(buf, len, "%s0x%04"PRIx16"%s",
				quoted, *(const uint16_t *) number, quoted);
	else if (maxsize == sizeof(uint32_t))
		return snprintf(buf, len, "%s0x%08"PRIx32"%s",
				quoted, *(const uint32_t *) number, quoted);
	else if (maxsize == sizeof(uint64_t))
		return snprintf(buf, len, "%s0x%016"PRIx64"%s",
				quoted, *(const uint64_t *) number, quoted);
	else
		assert(0);
	return 0;
}

/**
 * ipset_print_name - print setname element string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print setname element string to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_name(char *buf, unsigned int len,
		 const struct ipset_data *data, enum ipset_opt opt,
		 uint8_t env UNUSED)
{
	const char *name;
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_NAME);

	if (len < 2*IPSET_MAXNAMELEN + 2 + strlen("before"))
		return -1;

	name = ipset_data_get(data, opt);
	assert(name);
	size = snprintf(buf, len, "%s", name);
	SNPRINTF_FAILURE(size, len, offset);

	if (ipset_data_test(data, IPSET_OPT_NAMEREF)) {
		bool before = false;
		if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FLAGS))) {
			const uint32_t *flags =
				ipset_data_get(data, IPSET_OPT_FLAGS);
			before = (*flags) & IPSET_FLAG_BEFORE;
		}
		size = snprintf(buf + offset, len,
				" %s %s", before ? "before" : "after",
				(const char *) ipset_data_get(data,
							IPSET_OPT_NAMEREF));
		SNPRINTF_FAILURE(size, len, offset);
	}

	return offset;
}

/**
 * ipset_print_port - print port or port range
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print port or port range to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_port(char *buf, unsigned int len,
		 const struct ipset_data *data,
		 enum ipset_opt opt ASSERT_UNUSED,
		 uint8_t env)
{
	const uint16_t *port;
	int size, offset = 0;
	const char *quoted = ipset_data_test(data, IPSET_OPT_PORT_TO) &&
			     env & IPSET_ENV_QUOTED ? "\"" : "";

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_PORT);

	if (len < 2*strlen("65535") + 2)
		return -1;

	port = ipset_data_get(data, IPSET_OPT_PORT);
	assert(port);
	size = snprintf(buf, len, "%s%u", quoted, *port);
	SNPRINTF_FAILURE(size, len, offset);

	if (ipset_data_test(data, IPSET_OPT_PORT_TO)) {
		port = ipset_data_get(data, IPSET_OPT_PORT_TO);
		size = snprintf(buf + offset, len,
				"%s%u%s",
				IPSET_RANGE_SEPARATOR, *port, quoted);
		SNPRINTF_FAILURE(size, len, offset);
	}

	return offset;
}

/**
 * ipset_print_mark - print mark to string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print mark to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_mark(char *buf, unsigned int len,
		   const struct ipset_data *data,
		   enum ipset_opt opt ASSERT_UNUSED,
		   uint8_t env UNUSED)
{
	const uint32_t *mark;
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_MARK || opt == IPSET_OPT_MARKMASK);

	mark = ipset_data_get(data, opt);
	assert(mark);

	size = snprintf(buf, len, "0x%08"PRIx32, *mark);
	SNPRINTF_FAILURE(size, len, offset);

	return offset;
}

/**
 * ipset_print_iface - print interface element string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print interface element string to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_iface(char *buf, unsigned int len,
		  const struct ipset_data *data, enum ipset_opt opt,
		  uint8_t env UNUSED)
{
	const char *name;
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_IFACE);

	if (len < IFNAMSIZ + strlen("physdev:"))
		return -1;

	if (ipset_data_test(data, IPSET_OPT_PHYSDEV)) {
		size = snprintf(buf, len, "physdev:");
		SNPRINTF_FAILURE(size, len, offset);
	}
	name = ipset_data_get(data, opt);
	assert(name);
	size = snprintf(buf + offset, len, "%s", name);
	SNPRINTF_FAILURE(size, len, offset);
	return offset;
}

/**
 * ipset_print_comment - print arbitrary parameter string
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print arbitrary string to output buffer.
 *
 * Return length of printed string or error size.
 */
int ipset_print_comment(char *buf, unsigned int len,
		       const struct ipset_data *data, enum ipset_opt opt,
		       uint8_t env UNUSED)
{
	const char *comment;
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_ADT_COMMENT);

	comment = ipset_data_get(data, opt);
	assert(comment);
	size = snprintf(buf + offset, len, "\"%s\"", comment);
	SNPRINTF_FAILURE(size, len, offset);
	return offset;
}

int
ipset_print_skbmark(char *buf, unsigned int len,
		    const struct ipset_data *data, enum ipset_opt opt,
		    uint8_t env UNUSED)
{
	int size, offset = 0;
	const uint64_t *skbmark;
	uint32_t mark, mask;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_SKBMARK);

	skbmark = ipset_data_get(data, IPSET_OPT_SKBMARK);
	assert(skbmark);
	mark = *skbmark >> 32;
	mask = *skbmark & 0xffffffff;
	if (mask == 0xffffffff)
		size = snprintf(buf + offset, len, "0x%"PRIx32, mark);
	else
		size = snprintf(buf + offset, len,
				"0x%"PRIx32"/0x%"PRIx32, mark, mask);
	SNPRINTF_FAILURE(size, len, offset);
	return offset;
}

int
ipset_print_skbprio(char *buf, unsigned int len,
		    const struct ipset_data *data, enum ipset_opt opt,
		    uint8_t env UNUSED)
{
	int size, offset = 0;
	const uint32_t *skbprio;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_SKBPRIO);

	skbprio = ipset_data_get(data, opt);
	assert(skbprio);
	size = snprintf(buf + offset, len, "%x:%x",
			*skbprio >> 16, *skbprio & 0xffff);
	SNPRINTF_FAILURE(size, len, offset);
	return offset;
}


/**
 * ipset_print_proto - print protocol name
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print protocol name to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_proto(char *buf, unsigned int len,
		  const struct ipset_data *data,
		  enum ipset_opt opt ASSERT_UNUSED,
		  uint8_t env UNUSED)
{
	const struct protoent *protoent;
	uint8_t proto;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_PROTO);

	proto = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO);
	assert(proto);

	protoent = getprotobynumber(proto);
	if (protoent)
		return snprintf(buf, len, "%s", protoent->p_name);

	/* Should not happen */
	return snprintf(buf, len, "%u", proto);
}

/**
 * ipset_print_icmp - print ICMP code name or type/code
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print ICMP code name or type/code name to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_icmp(char *buf, unsigned int len,
		 const struct ipset_data *data,
		 enum ipset_opt opt ASSERT_UNUSED,
		 uint8_t env UNUSED)
{
	const char *name;
	uint16_t typecode;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_PORT);

	typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT);
	name = icmp_to_name(typecode >> 8, typecode & 0xFF);
	if (name != NULL)
		return snprintf(buf, len, "%s", name);
	else
		return snprintf(buf, len, "%u/%u",
				typecode >> 8, typecode & 0xFF);
}

/**
 * ipset_print_icmpv6 - print ICMPv6 code name or type/code
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print ICMPv6 code name or type/code name to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_icmpv6(char *buf, unsigned int len,
		   const struct ipset_data *data,
		   enum ipset_opt opt ASSERT_UNUSED,
		   uint8_t env UNUSED)
{
	const char *name;
	uint16_t typecode;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_PORT);

	typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT);
	name = icmpv6_to_name(typecode >> 8, typecode & 0xFF);
	if (name != NULL)
		return snprintf(buf, len, "%s", name);
	else
		return snprintf(buf, len, "%u/%u",
				typecode >> 8, typecode & 0xFF);
}

/**
 * ipset_print_proto_port - print proto:port
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print protocol and port to output buffer.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_proto_port(char *buf, unsigned int len,
		       const struct ipset_data *data,
		       enum ipset_opt opt ASSERT_UNUSED,
		       uint8_t env UNUSED)
{
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);
	assert(opt == IPSET_OPT_PORT);

	if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_PROTO))) {
		uint8_t proto = *(const uint8_t *) ipset_data_get(data,
							IPSET_OPT_PROTO);
		size = ipset_print_proto(buf, len, data, IPSET_OPT_PROTO, env);
		SNPRINTF_FAILURE(size, len, offset);
		if (len < 2)
			return -ENOSPC;
		size = snprintf(buf + offset, len, IPSET_PROTO_SEPARATOR);
		SNPRINTF_FAILURE(size, len, offset);

		switch (proto) {
		case IPPROTO_TCP:
		case IPPROTO_SCTP:
		case IPPROTO_UDP:
		case IPPROTO_UDPLITE:
			break;
		case IPPROTO_ICMP:
			size = ipset_print_icmp(buf + offset, len, data,
						IPSET_OPT_PORT, env);
			goto out;
		case IPPROTO_ICMPV6:
			size = ipset_print_icmpv6(buf + offset, len, data,
						  IPSET_OPT_PORT, env);
			goto out;
		default:
			break;
		}
	}
	size = ipset_print_port(buf + offset, len, data, IPSET_OPT_PORT, env);
out:
	SNPRINTF_FAILURE(size, len, offset);
	return offset;
}

#define print_second(data)	\
ipset_data_flags_test(data,	\
	IPSET_FLAG(IPSET_OPT_PORT)|IPSET_FLAG(IPSET_OPT_ETHER))

#define print_third(data)	\
ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_IP2))

/**
 * ipset_print_elem - print ADT elem according to settype
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print (multipart) element according to settype
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_elem(char *buf, unsigned int len,
		 const struct ipset_data *data, enum ipset_opt opt UNUSED,
		 uint8_t env)
{
	const struct ipset_type *type;
	int size, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);

	type = ipset_data_get(data, IPSET_OPT_TYPE);
	if (!type)
		return -1;

	size = type->elem[IPSET_DIM_ONE - 1].print(buf, len, data,
			type->elem[IPSET_DIM_ONE - 1].opt, env);
	SNPRINTF_FAILURE(size, len, offset);
	IF_D(ipset_data_test(data, type->elem[IPSET_DIM_TWO - 1].opt),
	     "print second elem");
	if (type->dimension == IPSET_DIM_ONE ||
	    (type->last_elem_optional &&
	     !ipset_data_test(data, type->elem[IPSET_DIM_TWO - 1].opt)))
		return offset;

	size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR);
	SNPRINTF_FAILURE(size, len, offset);
	size = type->elem[IPSET_DIM_TWO - 1].print(buf + offset, len, data,
			type->elem[IPSET_DIM_TWO - 1].opt, env);
	SNPRINTF_FAILURE(size, len, offset);
	if (type->dimension == IPSET_DIM_TWO ||
	    (type->last_elem_optional &&
	     !ipset_data_test(data, type->elem[IPSET_DIM_THREE - 1].opt)))
		return offset;

	size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR);
	SNPRINTF_FAILURE(size, len, offset);
	size = type->elem[IPSET_DIM_THREE - 1].print(buf + offset, len, data,
			type->elem[IPSET_DIM_THREE - 1].opt, env);
	SNPRINTF_FAILURE(size, len, offset);

	return offset;
}

/**
 * ipset_print_flag - print a flag
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Print a flag, i.e. option without value
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_flag(char *buf UNUSED, unsigned int len UNUSED,
		 const struct ipset_data *data UNUSED,
		 enum ipset_opt opt UNUSED, uint8_t env UNUSED)
{
	return 0;
}

/**
 * ipset_print_data - print data, generic fuction
 * @buf: printing buffer
 * @len: length of available buffer space
 * @data: data blob
 * @opt: the option kind
 * @env: environment flags
 *
 * Generic wrapper of the printing functions.
 *
 * Return lenght of printed string or error size.
 */
int
ipset_print_data(char *buf, unsigned int len,
		 const struct ipset_data *data, enum ipset_opt opt,
		 uint8_t env)
{
	int size = 0, offset = 0;

	assert(buf);
	assert(len > 0);
	assert(data);

	switch (opt) {
	case IPSET_OPT_FAMILY:
		size = ipset_print_family(buf, len, data, opt, env);
		break;
	case IPSET_OPT_TYPE:
		size = ipset_print_type(buf, len, data, opt, env);
		break;
	case IPSET_SETNAME:
		size = snprintf(buf, len, "%s", ipset_data_setname(data));
		break;
	case IPSET_OPT_ELEM:
		size = ipset_print_elem(buf, len, data, opt, env);
		break;
	case IPSET_OPT_IP:
	case IPSET_OPT_BITMASK:
		size = ipset_print_ip(buf, len, data, opt, env);
		break;
	case IPSET_OPT_PORT:
		size = ipset_print_port(buf, len, data, opt, env);
		break;
	case IPSET_OPT_IFACE:
		size = ipset_print_iface(buf, len, data, opt, env);
		break;
	case IPSET_OPT_INITVAL:
	case IPSET_OPT_HASHSIZE:
	case IPSET_OPT_MAXELEM:
	case IPSET_OPT_MARKMASK:
	case IPSET_OPT_NETMASK:
	case IPSET_OPT_BUCKETSIZE:
	case IPSET_OPT_RESIZE:
	case IPSET_OPT_TIMEOUT:
	case IPSET_OPT_REFERENCES:
	case IPSET_OPT_ELEMENTS:
	case IPSET_OPT_SIZE:
		size = ipset_print_number(buf, len, data, opt, env);
		break;
	default:
		return -1;
	}
	SNPRINTF_FAILURE(size, len, offset);

	return offset;
}
