#include <u.h>
#define NOPLAN9DEFINES
#include <libc.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <errno.h>

#undef sun
#define sun sockun

extern int _p9netfd(char*);

static char *unknown = "unknown";

static int
convert(int s, struct sockaddr *sa, char **lsys, char **lserv, char **laddr)
{
	struct sockaddr_un *sun;
	struct sockaddr_in *sin;
	struct sockaddr_in6 *sin6;
	uchar *ip;
	u32int ipl;
	socklen_t sn;
	int n;
	char *net;

	switch(sa->sa_family){
	case AF_INET:
		sin = (void*)sa;
		ip = (uchar*)&sin->sin_addr;
		ipl = *(u32int*)ip;
		if(ipl == 0)
			*lsys = strdup("*");
		else
			*lsys = smprint("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
		*lserv = smprint("%d", ntohs(sin->sin_port));
		sn = sizeof n;
		if(getsockopt(s, SOL_SOCKET, SO_TYPE, (void*)&n, &sn) < 0)
			return -1;
		if(n == SOCK_STREAM)
			net = "tcp";
		else if(n == SOCK_DGRAM)
			net = "udp";
		else{
			werrstr("unknown network type");
			return -1;
		}
		*laddr = smprint("%s!%s!%s", net, *lsys, *lserv);
		if(*lsys == nil || *lserv == nil || *laddr == nil)
			return -1;
		return 0;
	case AF_INET6:
		sin6 = (void*)sa;
		if (memcmp(&sin6->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0)
			*lsys = strdup("*");
		else{
			*lsys = malloc(INET6_ADDRSTRLEN);
			inet_ntop(AF_INET6, &sin6->sin6_addr, *lsys, INET6_ADDRSTRLEN);
		}
		*lserv = smprint("%d", ntohs(sin6->sin6_port));
		sn = sizeof n;
		if(getsockopt(s, SOL_SOCKET, SO_TYPE, (void*)&n, &sn) < 0)
			return -1;
		if(n == SOCK_STREAM)
			net = "tcp";
		else if(n == SOCK_DGRAM)
			net = "udp";
		else{
			werrstr("unknown network type");
			return -1;
		}
		*laddr = smprint("%s!%s!%s", net, *lsys, *lserv);
		if(*lsys == nil || *lserv == nil || *laddr == nil)
			return -1;
		return 0;
	case AF_UNIX:
		sun = (void*)sa;
		*lsys = unknown;
		*lserv = unknown;
		*laddr = smprint("unix!%s", sun->sun_path);
		if(*laddr == nil)
			return -1;
		return 0;
	default:
		werrstr("unknown socket family");
		return -1;
	}
}

NetConnInfo*
getnetconninfo(char *dir, int fd)
{
	socklen_t sn;
	union {
		struct sockaddr sa;
		struct sockaddr_in sin;
		struct sockaddr_in6 sin6;
		struct sockaddr_un sun;
	} u;
	NetConnInfo *nci;

	if(dir){
		if((fd = _p9netfd(dir)) < 0){
			werrstr("no such network connection %s", dir);
			return nil;
		}
	}

	nci = mallocz(sizeof *nci, 1);
	if(nci == nil)
		goto err;
	nci->dir = smprint("/dev/fd/%d", fd);
	nci->root = strdup("/net");
	nci->spec = unknown;
	if(nci->dir == nil || nci->root == nil)
		goto err;
	sn = sizeof u;
	if(getsockname(fd, &u.sa, &sn) < 0)
		goto err;
	if(convert(fd, &u.sa, &nci->lsys, &nci->lserv, &nci->laddr) < 0)
		goto err;
	sn = sizeof u;
	if(getpeername(fd, &u.sa, &sn) < 0)
		goto err;
	if(convert(fd, &u.sa, &nci->rsys, &nci->rserv, &nci->raddr) < 0)
		goto err;
	return nci;

err:
	freenetconninfo(nci);
	return nil;
}

static void
xfree(void *v)
{
	if(v != nil && v != unknown)
		free(v);
}

void
freenetconninfo(NetConnInfo *nci)
{
	if(nci == nil)
		return;
	xfree(nci->dir);
	xfree(nci->root);
	xfree(nci->spec);
	xfree(nci->lsys);
	xfree(nci->lserv);
	xfree(nci->rsys);
	xfree(nci->rserv);
	xfree(nci->laddr);
	xfree(nci->raddr);
	free(nci);
}
