/*
 * struct sockaddr
 *
 * syntaxes:
 *    ddd.ddd.ddd.ddd,nnnn       IPv4 address and port
 *                                 ... host or port may be `*'
 *    /abs/path/to/socket        AF_UNIX
 *    ./rel/path/to/socket       AF_UNIX
 */

#include "dgram.h"

#define SOCKADDR_LEN(sa) ((sa)->end - (sa)->begin)

/* parsing */

int cht_pat_sockaddr(Tcl_Interp *ip, Tcl_Obj *o, SockAddr_Value *val) {
  int rc;

  rc= Tcl_ConvertToType(ip,o,&sockaddr_type);
  if (rc) return rc;

  *val= *OBJ_SOCKADDR(o);
  return TCL_OK;
}
  
Tcl_Obj *cht_ret_sockaddr(Tcl_Interp *ip, SockAddr_Value val) {
  Tcl_Obj *o;

  o= Tcl_NewObj();
  Tcl_InvalidateStringRep(o);
  *OBJ_SOCKADDR(o)= val;
  o->typePtr= &sockaddr_type;
  return o;
}

/* native type methods */

void cht_sockaddr_clear(SockAddr_Value *v) { v->begin= v->end= 0; }

void cht_sockaddr_create(SockAddr_Value *v, const struct sockaddr *a, int al) {
  Byte *begin;
  
  v->begin= begin= TALLOC(al);
  memcpy(begin, a, al);
  v->end= begin + al;
}  

int cht_sockaddr_len(const SockAddr_Value *v) {
  return SOCKADDR_LEN(v);
}

const struct sockaddr *cht_sockaddr_addr(const SockAddr_Value *v) {
  return (const void*)v->begin;
}
  
void cht_sockaddr_free(const SockAddr_Value *v) {
  TFREE(v->begin);
}

/* Sockaddr Tcl type */

static void sockaddr_t_dup(Tcl_Obj *src, Tcl_Obj *dup) {
  cht_sockaddr_create(OBJ_SOCKADDR(dup),
		  cht_sockaddr_addr(OBJ_SOCKADDR(src)),
		  cht_sockaddr_len(OBJ_SOCKADDR(src)));
  dup->typePtr= &cht_hbytes_type;
}

static void sockaddr_t_free(Tcl_Obj *o) {
  cht_sockaddr_free(OBJ_SOCKADDR(o));
}

static void sockaddr_t_ustr(Tcl_Obj *o) {
  const struct sockaddr *sa;
  char i46buf[INET6_ADDRSTRLEN], portbuf[50];
  const struct sockaddr_in *sin;
  int al;
  const char *string, *prepend;
  
  sa= cht_sockaddr_addr(OBJ_SOCKADDR(o));
  al= cht_sockaddr_len(OBJ_SOCKADDR(o));
    
  switch (sa->sa_family) {
  case AF_INET:
  case AF_INET6:
    assert(sizeof(i46buf) >= INET_ADDRSTRLEN);
    assert(al >= sizeof(struct sockaddr_in));
    sin= (const void*)sa;
    inet_ntop(sa->sa_family, &sin->sin_addr, i46buf, al);
    snprintf(portbuf,sizeof(portbuf),",%d",(int)ntohs(sin->sin_port));
    prepend= i46buf;
    string= portbuf;
    break;

  case AF_UNIX:
    string= ((const struct sockaddr_un*)sa)->sun_path;
    prepend= "";
    if (!string[0]) string="//"; 
    else if (string[0] != '/' || string[1] == '/') prepend= "./";
    break;

  default: /* ouch ! */
    cht_obj_updatestr_array_prefix(o,(const void*)sa,al,"?");
    return;
  }

  cht_obj_updatestr_vstringls(o,
			  prepend, strlen(prepend),
			  string, strlen(string),
			  (char*)0);
}

static int sockaddr_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
  union {
    struct sockaddr_un sun;
    struct sockaddr_in sin;
  } s;
  unsigned long port_l;
  
  char *str, *ep, *copy;
  int sl, pl, iprv;
  const char *comma, *path;

  str= Tcl_GetStringFromObj(o,0);  assert(str);
  cht_objfreeir(o);
  memset(&s,0,sizeof(s));

  if (str[0]=='/' || (str[0]=='.' && str[1]=='/')) {
    
    sl= sizeof(s.sun);
    s.sun.sun_family= AF_UNIX;

    if (!strcmp(str,"//")) path= "";
    else if (!memcmp(str,"./",2) && str[2]) path= str+2;
    else { assert(str[0]=='/' && str[1]!='/'); path=str; }

    if (strlen(str) >= sizeof(s.sun.sun_path))
      return cht_staticerr(ip, "AF_UNIX path too long", "SOCKADDR AFUNIX LENGTH");

    strcpy(s.sun.sun_path, path);

  } else if ((comma= strchr(str, ','))) {

    sl= sizeof(s.sin);
    s.sin.sin_family= AF_INET;

    pl= comma - str;
    copy= TALLOC(pl+1);
    memcpy(copy, str, pl);
    copy[pl]= 0;

    if (!strcmp(copy,"*")) {
      s.sin.sin_addr.s_addr= INADDR_ANY;
      iprv= 1;
    } else {
      iprv= inet_pton(AF_INET, copy, &s.sin.sin_addr);
    }
    TFREE(copy);

    if (!iprv)
      return cht_staticerr(ip, "bad IPv4 address syntax", "SOCKADDR SYNTAX IPV4");

    comma++;
    if (!strcmp(comma,"*")) {
      s.sin.sin_port= 0;
    } else {
      errno=0; port_l=strtoul(comma,&ep,10);
      if (errno || *ep)
	return cht_staticerr(ip, "bad IPv4 port", "SOCKADDR SYNTAX IPV4");
      if (port_l > 65535)
	return cht_staticerr(ip, "IPv4 port out of range", "SOCKADDR SYNTAX IPV4");
      s.sin.sin_port= htons(port_l);
    }

  } else {

    return cht_staticerr(ip, "bad socket address syntax", "SOCKADDR SYNTAX OTHER");

  }

  cht_sockaddr_create(OBJ_SOCKADDR(o), (void*)&s, sl);
  
  o->typePtr = &sockaddr_type;
  return TCL_OK;
}

Tcl_ObjType sockaddr_type = {
  "sockaddr",
  sockaddr_t_free, sockaddr_t_dup, sockaddr_t_ustr, sockaddr_t_sfa
};
