/* SCTP kernel Implementation: User API extensions.
 *
 * addrs.c
 *
 * Distributed under the terms of the LGPL v2.1 as described in
 *    http://www.gnu.org/copyleft/lesser.txt 
 *
 * This file is part of the user library that offers support for the
 * SCTP kernel Implementation. The main purpose of this
 * code is to provide the SCTP Socket API mappings for user
 * application to interface with the SCTP in kernel.
 *
 * This implementation is based on the Socket API Extensions for SCTP
 * defined in <draft-ietf-tsvwg-sctpsocket-10.txt.
 *
 * (C) Copyright IBM Corp. 2003
 * Copyright (c) 2001-2002 Intel Corp.
 *
 * Written or modified by:
 *  Ardelle Fan     <ardelle.fan@intel.com>
 *  Sridhar Samudrala <sri@us.ibm.com>
 *  Ivan Skytte Jørgensen <isj-sctp@i1.dk>
 */

#include <malloc.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <string.h>
#include <errno.h>

/* 
 * Common getsockopt() layer 
 * If the NEW getsockopt() API fails this function will fall back to using
 * the old API
 */
static int
sctp_getaddrs(int sd, sctp_assoc_t id, int optname_new,
	      struct sockaddr **addrs)
{
	int cnt, err;
	socklen_t len;
	size_t bufsize = 4096; /*enough for most cases*/

	struct sctp_getaddrs *getaddrs = (struct sctp_getaddrs*)malloc(bufsize);
	if(!getaddrs)
		return -1;
	
	for(;;) {
		char *new_buf;

		len = bufsize;
		getaddrs->assoc_id = id;
		err = getsockopt(sd, SOL_SCTP, optname_new, getaddrs, &len);
		if (err == 0) {
			/*got it*/
			break;
		}
		if (errno != ENOMEM ) {
			/*unknown error*/
			free(getaddrs);
			return -1;
		}
		/*expand buffer*/
		if (bufsize > 128*1024) {
			/*this is getting ridiculous*/
			free(getaddrs);
			errno = ENOBUFS;
			return -1;
		}
		new_buf = realloc(getaddrs, bufsize+4096);
		if (!new_buf) {
			free(getaddrs);
			return -1;
		}
		bufsize += 4096;
		getaddrs = (struct sctp_getaddrs*)new_buf;
	}

	/* we skip traversing the list, allocating a new buffer etc. and enjoy
	 * a simple hack*/
	cnt = getaddrs->addr_num;
	memmove(getaddrs, getaddrs + 1, len);
	*addrs = (struct sockaddr*)getaddrs;

	return cnt;
} /* sctp_getaddrs() */

/* Get all peer address on a socket.  This is a new SCTP API
 * described in the section 8.3 of the Sockets API Extensions for SCTP.
 * This is implemented using the getsockopt() interface.
 */
int
sctp_getpaddrs(int sd, sctp_assoc_t id, struct sockaddr **addrs)
{
	return sctp_getaddrs(sd, id,
			     SCTP_GET_PEER_ADDRS,
			     addrs);
} /* sctp_getpaddrs() */

/* Frees all resources allocated by sctp_getpaddrs().  This is a new SCTP API
 * described in the section 8.4 of the Sockets API Extensions for SCTP.
 */
int
sctp_freepaddrs(struct sockaddr *addrs)
{
	free(addrs);
	return 0;

} /* sctp_freepaddrs() */

/* Get all locally bound address on a socket.  This is a new SCTP API
 * described in the section 8.5 of the Sockets API Extensions for SCTP.
 * This is implemented using the getsockopt() interface.
 */
int
sctp_getladdrs(int sd, sctp_assoc_t id, struct sockaddr **addrs)
{
	return sctp_getaddrs(sd, id,
			     SCTP_GET_LOCAL_ADDRS,
			     addrs);
} /* sctp_getladdrs() */

/* Frees all resources allocated by sctp_getladdrs().  This is a new SCTP API
 * described in the section 8.6 of the Sockets API Extensions for SCTP.
 */
int
sctp_freeladdrs(struct sockaddr *addrs)
{
	free(addrs);
	return 0;

} /* sctp_freeladdrs() */

int
sctp_getaddrlen(sa_family_t family)
{
	/* We could call into the kernel to see what it thinks the size should
	 * be, but hardcoding the address families here is: (a) faster,
	 * (b) easier, and (c) probably good enough for forseeable future.
	 */
	switch(family) {
	case AF_INET:
		return sizeof(struct sockaddr_in);
	case AF_INET6:
		return sizeof(struct sockaddr_in6);
	default:
		/* Currently there is no defined error handling in
		 * draft-ietf-tsvwg-sctpsocket-13.txt.
		 * -1 might cause the application to overwrite buffer
		 * or misinterpret data. 0 is more likely to cause
		 * an endless loop.
		 */
		return 0;
	}
}