// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * qpb.h
 *
 * @copyright Copyright (C) 2016 Sproute Networks, Inc.
 *
 * @author Avneesh Sachdev <avneesh@sproute.com>
 */

/*
 * Main public header file for the quagga protobuf library.
 */

#ifndef _QPB_H
#define _QPB_H

#include "nexthop.h"
#include "prefix.h"

#include "qpb/qpb.pb-c.h"

#include "qpb/qpb_allocator.h"

/*
 * qpb__address_family__set
 */
#define qpb_address_family_set qpb__address_family__set
static inline int qpb__address_family__set(Qpb__AddressFamily *pb_family,
					   uint8_t family)
{
	switch (family) {
	case AF_INET:
		*pb_family = QPB__ADDRESS_FAMILY__IPV4;
		return 1;

	case AF_INET6:
		*pb_family = QPB__ADDRESS_FAMILY__IPV6;
		return 1;

	default:
		*pb_family = QPB__ADDRESS_FAMILY__UNKNOWN_AF;
	}

	return 0;
}

/*
 * qpb__address_family__get
 */
#define qpb_address_family_get qpb__address_family__get
static inline int qpb__address_family__get(Qpb__AddressFamily pb_family,
					   uint8_t *family)
{

	switch (pb_family) {
	case QPB__ADDRESS_FAMILY__IPV4:
		*family = AF_INET;
		return 1;

	case QPB__ADDRESS_FAMILY__IPV6:
		*family = AF_INET6;
		return 1;

	case QPB__ADDRESS_FAMILY__UNKNOWN_AF:
		return 0;
	case _QPB__ADDRESS_FAMILY_IS_INT_SIZE:
		return 0;
	}

	return 0;
}

/*
 * qpb__l3_prefix__create
 */
#define qpb_l3_prefix_create qpb__l3_prefix__create
static inline Qpb__L3Prefix *qpb__l3_prefix__create(qpb_allocator_t *allocator,
						    struct prefix *p)
{
	Qpb__L3Prefix *prefix;

	prefix = QPB_ALLOC(allocator, typeof(*prefix));
	if (!prefix) {
		return NULL;
	}
	qpb__l3_prefix__init(prefix);
	prefix->length = p->prefixlen;
	prefix->bytes.len = (p->prefixlen + 7) / 8;
	prefix->bytes.data = qpb_alloc(allocator, prefix->bytes.len);
	if (!prefix->bytes.data) {
		return NULL;
	}

	memcpy(prefix->bytes.data, &p->u.prefix, prefix->bytes.len);

	return prefix;
}

/*
 * qpb__l3_prefix__get
 */
#define qpb_l3_prefix_get qpb__l3_prefix__get
static inline int qpb__l3_prefix__get(const Qpb__L3Prefix *pb_prefix,
				      uint8_t family, struct prefix *prefix)
{

	switch (family) {

	case AF_INET:
		memset((struct prefix_ipv4 *)prefix, 0,
		       sizeof(struct prefix_ipv4));
		break;

	case AF_INET6:
		memset((struct prefix_ipv6 *)prefix, 0,
		       sizeof(struct prefix_ipv6));
		break;

	default:
		memset(prefix, 0, sizeof(*prefix));
	}

	prefix->prefixlen = pb_prefix->length;
	prefix->family = family;
	memcpy(&prefix->u.prefix, pb_prefix->bytes.data, pb_prefix->bytes.len);
	return 1;
}

/*
 * qpb__protocol__set
 *
 * Translate a quagga route type to a protobuf protocol.
 */
#define qpb_protocol_set qpb__protocol__set
static inline int qpb__protocol__set(Qpb__Protocol *pb_proto, int route_type)
{
	switch (route_type) {
	case ZEBRA_ROUTE_KERNEL:
		*pb_proto = QPB__PROTOCOL__KERNEL;
		break;

	case ZEBRA_ROUTE_CONNECT:
		*pb_proto = QPB__PROTOCOL__CONNECTED;
		break;

	case ZEBRA_ROUTE_STATIC:
		*pb_proto = QPB__PROTOCOL__STATIC;
		break;

	case ZEBRA_ROUTE_RIP:
		*pb_proto = QPB__PROTOCOL__RIP;
		break;

	case ZEBRA_ROUTE_RIPNG:
		*pb_proto = QPB__PROTOCOL__RIPNG;
		break;

	case ZEBRA_ROUTE_OSPF:
	case ZEBRA_ROUTE_OSPF6:
		*pb_proto = QPB__PROTOCOL__OSPF;
		break;

	case ZEBRA_ROUTE_ISIS:
		*pb_proto = QPB__PROTOCOL__ISIS;
		break;

	case ZEBRA_ROUTE_BGP:
		*pb_proto = QPB__PROTOCOL__BGP;
		break;

	case ZEBRA_ROUTE_HSLS:
	case ZEBRA_ROUTE_OLSR:
	case ZEBRA_ROUTE_MAX:
	case ZEBRA_ROUTE_SYSTEM:
	default:
		*pb_proto = QPB__PROTOCOL__OTHER;
	}

	return 1;
}

/*
 * qpb__ipv4_address__create
 */
static inline Qpb__Ipv4Address *
qpb__ipv4_address__create(qpb_allocator_t *allocator, struct in_addr *addr)
{
	Qpb__Ipv4Address *v4;

	v4 = QPB_ALLOC(allocator, typeof(*v4));
	if (!v4) {
		return NULL;
	}
	qpb__ipv4_address__init(v4);

	v4->value = ntohl(addr->s_addr);
	return v4;
}

/*
 * qpb__ipv4_address__get
 */
static inline int qpb__ipv4_address__get(const Qpb__Ipv4Address *v4,
					 struct in_addr *addr)
{
	addr->s_addr = htonl(v4->value);
	return 1;
}

/*
 * qpb__ipv6_address__create
 */
static inline Qpb__Ipv6Address *
qpb__ipv6_address__create(qpb_allocator_t *allocator, struct in6_addr *addr)
{
	Qpb__Ipv6Address *v6;

	v6 = QPB_ALLOC(allocator, typeof(*v6));
	if (!v6)
		return NULL;

	qpb__ipv6_address__init(v6);
	v6->bytes.len = 16;
	v6->bytes.data = qpb_alloc(allocator, 16);
	if (!v6->bytes.data)
		return NULL;

	memcpy(v6->bytes.data, addr->s6_addr, v6->bytes.len);
	return v6;
}

/*
 * qpb__ipv6_address__get
 *
 * Read out information from a protobuf ipv6 address structure.
 */
static inline int qpb__ipv6_address__get(const Qpb__Ipv6Address *v6,
					 struct in6_addr *addr)
{
	if (v6->bytes.len != 16)
		return 0;

	memcpy(addr->s6_addr, v6->bytes.data, v6->bytes.len);
	return 1;
}

/*
 * qpb__l3_address__create
 */
#define qpb_l3_address_create qpb__l3_address__create
static inline Qpb__L3Address *
qpb__l3_address__create(qpb_allocator_t *allocator, union g_addr *addr,
			uint8_t family)
{
	Qpb__L3Address *l3_addr;

	l3_addr = QPB_ALLOC(allocator, typeof(*l3_addr));
	if (!l3_addr)
		return NULL;

	qpb__l3_address__init(l3_addr);

	switch (family) {

	case AF_INET:
		l3_addr->v4 = qpb__ipv4_address__create(allocator, &addr->ipv4);
		if (!l3_addr->v4)
			return NULL;

		break;

	case AF_INET6:
		l3_addr->v6 = qpb__ipv6_address__create(allocator, &addr->ipv6);
		if (!l3_addr->v6)
			return NULL;

		break;
	}
	return l3_addr;
}

/*
 * qpb__l3_address__get
 *
 * Read out a gateway address from a protobuf l3 address.
 */
#define qpb_l3_address_get qpb__l3_address__get
static inline int qpb__l3_address__get(const Qpb__L3Address *l3_addr,
				       uint8_t *family, union g_addr *addr)
{
	if (l3_addr->v4) {
		qpb__ipv4_address__get(l3_addr->v4, &addr->ipv4);
		*family = AF_INET;
		return 1;
	}

	if (l3_addr->v6) {
		qpb__ipv6_address__get(l3_addr->v6, &addr->ipv6);
		*family = AF_INET6;
		return 1;
	}

	return 0;
}

/*
 * qpb__if_identifier__create
 */
#define qpb_if_identifier_create qpb__if_identifier__create
static inline Qpb__IfIdentifier *
qpb__if_identifier__create(qpb_allocator_t *allocator, uint if_index)
{
	Qpb__IfIdentifier *if_id;

	if_id = QPB_ALLOC(allocator, typeof(*if_id));
	if (!if_id) {
		return NULL;
	}
	qpb__if_identifier__init(if_id);
	if_id->has_index = 1;
	if_id->index = if_index;
	return if_id;
}

/*
 * qpb__if_identifier__get
 *
 * Get interface name and/or if_index from an if identifier.
 */
#define qpb_if_identifier_get qpb__if_identifier__get
static inline int qpb__if_identifier__get(Qpb__IfIdentifier *if_id,
					  uint *if_index, char **name)
{
	char *str;
	uint ix;

	if (!if_index)
		if_index = &ix;

	if (!name)
		name = &str;

	if (if_id->has_index)
		*if_index = if_id->index;
	else
		*if_index = 0;

	*name = if_id->name;
	return 1;
}

#endif
