/*	$OpenBSD: util.c,v 1.1 2006/01/03 22:19:59 claudio Exp $ */

/*
 * Copyright (c) 2006 Claudio Jeker <claudio@openbsd.org>
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "bgpd.h"
#include "rde.h"

const char *
log_addr(const struct bgpd_addr *addr)
{
	static char	buf[48];

	if (inet_ntop(addr->af, &addr->ba, buf, sizeof(buf)) == NULL)
		return ("?");
	else
		return (buf);
}

const char *
log_in6addr(const struct in6_addr *addr)
{
	struct sockaddr_in6	sa_in6;
	u_int16_t		tmp16;

	bzero(&sa_in6, sizeof(sa_in6));
	sa_in6.sin6_len = sizeof(sa_in6);
	sa_in6.sin6_family = AF_INET6;
	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));

	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
		sa_in6.sin6_scope_id = ntohs(tmp16);
		sa_in6.sin6_addr.s6_addr[2] = 0;
		sa_in6.sin6_addr.s6_addr[3] = 0;
	}

	return (log_sockaddr((struct sockaddr *)&sa_in6));
}

const char *
log_sockaddr(struct sockaddr *sa)
{
	static char	buf[NI_MAXHOST];

	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
	    NI_NUMERICHOST))
		return ("(unknown)");
	else
		return (buf);
}

int
aspath_snprint(char *buf, size_t size, void *data, u_int16_t len, int as_size)
{
#define UPDATE()				\
	do {					\
		if (r == -1)			\
			return (-1);		\
		total_size += r;		\
		if ((unsigned int)r < size) {	\
			size -= r;		\
			buf += r;		\
		} else {			\
			buf += size;		\
			size = 0;		\
		}				\
	} while (0)
	u_int8_t	*seg;
	int		 r, total_size;
	u_int16_t	 seg_size;
	u_int8_t	 i, seg_type, seg_len;
        u_int32_t        as;

	total_size = 0;
	seg = data;
	for (; len > 0; len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + as_size * seg_len;

		if (seg_type == AS_SET) {
			if (total_size != 0)
				r = snprintf(buf, size, " { ");
			else
				r = snprintf(buf, size, "{ ");
			UPDATE();
		} else if (total_size != 0) {
			r = snprintf(buf, size, " ");
			UPDATE();
		}

		for (i = 0; i < seg_len; i++) {
		        as = aspath_extract(seg,i,as_size);
                        if (as_size == 4)
			        r = snprintf(buf, size,"%u.%u",
					   ((as >> 16) & 65535),
                                           (as & 65535));
                        else
			        r = snprintf(buf,size,"%u",as) ;
			UPDATE();
			if (i + 1 < seg_len) {
				r = snprintf(buf, size, " ");
				UPDATE();
			}
		}
		if (seg_type == AS_SET) {
			r = snprintf(buf, size, " }");
			UPDATE();
		}
	}
	/* ensure that we have a valid C-string especially for emtpy as path */
	if (size > 0)
		*buf = '\0';

	return (total_size);
#undef UPDATE
}

int
aspath_asprint(char **ret, void *data, u_int16_t len, int as_size)
{
	size_t	slen;
	int	plen;

	slen = aspath_strlen(data, len, as_size) + 1;
	*ret = malloc(slen);
	if (*ret == NULL)
		return (-1);

	plen = aspath_snprint(*ret, slen, data, len, as_size);
	if (plen == -1) {
		free(*ret);
		*ret = NULL;
		return (-1);
	}

	return (0);
}

size_t
aspath_strlen(void *data, u_int16_t len, int as_size)
{
	u_int8_t	*seg;
	int		 total_size;
	u_int32_t	 as; 
        u_int16_t        seg_size;
	u_int8_t	 i, seg_type, seg_len;

	total_size = 0;
	seg = data;
	for (; len > 0; len -= seg_size, seg += seg_size) {
		seg_type = seg[0];
		seg_len = seg[1];
		seg_size = 2 + as_size * seg_len;

		if (seg_type == AS_SET)
			if (total_size != 0)
				total_size += 3;
			else
				total_size += 2;
		else if (total_size != 0)
			total_size += 1;

		for (i = 0; i < seg_len; i++) {
                        as = aspath_extract(seg, i, as_size);
                        if (((as >> 16) & 65535) >= 10000)
                                total_size += 11;
                        else if (((as >> 16) & 65535) >= 1000)
                                total_size += 10;
                        else if (((as >> 16) & 65535) >= 100)
                                total_size += 9;
                        else if (((as >> 16) & 65535) >= 10)
                                total_size += 8;
                        else if (as >= 65535)
                                total_size += 7;
 			else if (as >= 1000)
                                total_size += 6;
 			else if (as >= 100)
                                total_size += 5;
 			else if (as >= 10)
                                total_size += 4;
 			else
                                total_size += 3;
 
 			if (i + 1 < seg_len)
 				total_size += 1;
                        if (as_size == 2) total_size -= 2 ;
		}

		if (seg_type == AS_SET)
			total_size += 2;
	}
	return (total_size);
}

/*
 * Extract the asnum out of the as segment at the specified position.
 * Direct access is not possible because of non-aligned reads.
 * ATTENTION: no bounds check are done.
 */
u_int32_t
aspath_extract(const void *seg, int pos, int as_size)
{
	const u_char	*ptr = seg;
	u_int16_t	 as = 0;

	ptr += 2 + as_size * pos;
	as = *ptr++;
	as <<= 8;
	as |= *ptr;
	return (as);
}

u_int32_t
as_extract(const void *seg, int as_size)
{
        const u_char    *ptr = seg;
        u_int32_t        as = 0;
  
        if (as_size == 4) {
                as |=  ((*ptr++) << 24);
                as |= ((*ptr++) << 16);
        }
        as |= ((*ptr++) << 8);
	as |= *ptr;
 	return (as);
 }
