/* This file is part of GNU Pies
   Copyright (C) 2009-2022 Sergey Poznyakoff
  
   GNU Pies is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Pies is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "libpies.h"
#include <limits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int
str2port (char *str)
{
  struct servent *serv;
  char *p;
  int port;

  /* First try to read it from /etc/services */
  serv = getservbyname (str, "tcp");

  if (serv != NULL)
    port = ntohs (serv->s_port);
  else
    {
      unsigned long l;
      /* Not in services, maybe a number? */
      l = strtoul (str, &p, 0);

      if (*p || l < 0 || l > USHRT_MAX)
	return -1;
      
      port = l;
    }

  return port;
}

static size_t
_my_stpcpy (char **pbuf, size_t *psize, const char *src)
{
  size_t slen = strlen (src);
  if (pbuf == NULL || *pbuf == NULL)
    return slen;
  else
    {
      char *buf = *pbuf;
      size_t size = *psize;
      if (size > slen)
	size = slen;
      memcpy (buf, src, size);
      *psize -= size;
      *pbuf += size;
      if (*psize)
	**pbuf = 0;
      else
	(*pbuf)[-1] = 0;
      return size;
    }
}

#define S_UN_NAME(sa, salen)						\
  ((salen <= offsetof (struct sockaddr_un,sun_path)) ? "" : (sa)->sun_path)

static size_t
format_uint (char **bufptr, size_t *buflen, unsigned n)
{
  char *buf = *bufptr;
  size_t len = *buflen;
  size_t i;

  if (buf && len == 0)
    return 0;
  
  for (i = 0;;)
    {
      unsigned x = n % 10;
      if (buf)
	{
	  if (len == 1)
	    break;
	  *buf++ = x + '0';
	  --len;
	}
      n /= 10;
      ++i;
      if (n == 0)
	break;
    }
  
  if (buf)
    {
      char *p = *bufptr;
      *bufptr = buf;
      *buf = 0;
      while (--buf > p)
	{
	  char c = *p;
	  *p++ = *buf;
	  *buf = c;
	}
    }
  return i;
}

void
sockaddr_to_str (const struct sockaddr *sa, int salen,
		 char *bufptr, size_t buflen,
		 size_t *plen)
{
  size_t len = 0;
  switch (sa->sa_family)
    {
    case AF_INET:
      {
	struct sockaddr_in s_in = *(struct sockaddr_in *)sa;
	len += _my_stpcpy (&bufptr, &buflen, inet_ntoa (s_in.sin_addr));
	len += _my_stpcpy (&bufptr, &buflen, ":");
	len += format_uint (&bufptr, &buflen, ntohs (s_in.sin_port));
	break;
      }

    case AF_UNIX:
      {
	struct sockaddr_un *s_un = (struct sockaddr_un *)sa;
	if (S_UN_NAME (s_un, salen)[0] == 0)
	  len += _my_stpcpy (&bufptr, &buflen, "somebody");
	else
	  {
	    len += _my_stpcpy (&bufptr, &buflen, "socket ");
	    len += _my_stpcpy (&bufptr, &buflen, s_un->sun_path);
	  }
	break;
    }

    default:
      len += _my_stpcpy (&bufptr, &buflen, "{Unsupported family: ");
      len += format_uint (&bufptr, &buflen, sa->sa_family);
      len += _my_stpcpy (&bufptr, &buflen, "}");
    }
  if (plen)
    *plen = len + 1;
}

char *
sockaddr_to_astr (const struct sockaddr *sa, int salen)
{
  size_t size;
  char *p;
  
  sockaddr_to_str (sa, salen, NULL, 0, &size);
  p = malloc (size);
  if (!p)
    grecs_alloc_die ();
  sockaddr_to_str (sa, salen, p, size, NULL);
  return p;
}
