/* Implementation of socket protocol family PF_LOCAL
   Copyright (C) 1994 Free Software Foundation

   This program 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 2, or (at
   your option) any later version.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */


struct user
{
  struct port_info pi;

  struct address *localaddr;	/* our address */
  struct address *remoteaddr;	/* address of destination of send */

  struct user *peer;		/* peer for STREAM socket */

  struct user *listenq;		/* queue of pending connections */
  struct user *listennext;	/* next pending connection on listenq */
  int listenlimit;		/* max size of listenq */
  int listenqlen;		/* current size of listenq */
  struct accept_req *acc, *acclast; /* pending accept operations FIFO */
  int type;			/* SOCK_STREAM or SOCK_DGRAM */
  struct dataq *recvq;		/* data to be read */
  struct read_req *rd, *rdlast;	/* pending read operations FIFO */
  struct write_req *wr, *wrlast; /* pending write operations FIFO */
};

struct accept_req
{
  mach_port_t replyport;
  mach_msg_type_name_t replyporttype;
  struct accept_req *next;
};

struct read_req
{
  mach_port_t replyport;
  mach_msg_type_name_t replyporttype;
  int amount;
  int flags;
};

struct write_req
{
  mach_port_t replyport;
  mach_msg_type_name_t replyporttype;
  int flags;
  char *data;
  mach_port_t *ports;
  char *control;
  u_int controllen;
};

struct address
{
  struct port_info pi;
  struct user *stream_bound;
  struct user *dgram_bound;
};

/* Port types */
enum port_types
{
  PT_USER,
  PT_ADDR,
  PT_MASTER,
  PT_CTL,
};

/* Return a new user socket structure of type TYPE. */
struct user *
make_socket (int type)
{
  u = ports_allocate_port (sizeof *u, PT_USER);
  u->localaddr = 0;
  u->remoteaddr = 0;
  u->peer = 0;
  u->listennext = 0;
  u->listenq = 0;
  u->listenlimit = 0;
  u->listenqlen;
  u->acceptwait = 0;
  u->acceptport = MACH_PORT_NULL;
  u->acceptporttype = MACH_MSG_TYPE_COPY_SEND;
  u->type = type;
  u->recvq = allocate_recvq ();
  return u;
}

  

/* Implement socket_create from <hurd/socket.defs>. */
error_t
S_socket_create (struct user *foo,
		 int sock_type,
		 int protocol,
		 mach_port_t *socket,
		 mach_msg_type_name_t *sockettype)
{
  struct user *u;

  if (sock_type != SOCK_DGRAM && sock_type != SOCK_STREAM)
    return ESOCKTNOSUPPORT;
  if (protocol != 0)
    return EPROTONOSUPPORT;
  
  u = make_socket (sock_type);

  *socket = ports_get_right (u);
  *sockettype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_listen (struct user *u,
	       int queuelen)
{
  if (!u)
    return EOPNOTSUPP;
  if (u->type != SOCK_STREAM)
    return EOPNOTSUPP;
  
  if (queuelen <= 0)
    return EINVAL;
  if (queuelen > 5)
    queuelen = 5;

  if (u->listenlimit)
    return EBUSY;		/* FIXME */
  u->listenlimit = queuelen;
  
  return 0;
}

/* Return a connection socket with state inherited from parent
   listening socket S. */
struct user *
inherit_sock (struct user *s)
{
  struct user *u;
  
  u = make_socket (s->type);
  u->localaddr = s->localaddr;
  return u;
}


error_t
S_socket_accept (struct user *u,
	       mach_port_t reply,
	       mach_msg_type_name_t replytype,
	       mach_port_t *conn_sock,
	       mach_msg_type_name_t *conn_sock_type,
	       mach_port_t *addr,
	       mach_msg_type_name_t *addrtype)
{
  struct user *newu, *peer;
  
  if (!u)
    return 0;
  
  if (u->type != SOCK_STREAM)
    return EOPNOTSUPP;
  
  if (!u->listenq)
    {
      struct accept_req *acc= malloc (sizeof (struct accept_req));
      acc->replyport = reply;
      acc->replyporttype = replytype;
      acc->next = 0;
      if (!u->acclast)
	  u->acc = acc;
      else
	u->acclast->next = acc;
      u->acclast = acc;
      return MIG_NO_REPLY;
    }
  
  assert (!u->acc);

  peer = u->listenq;
  u->listenq = peer->listennext;
  u->listenqlen--;

  newu = inherit_sock (u);

  if (!peer->localaddr)
    {
      peer->localaddr = make_addr ();
      peer->localaddr->stream_bound = peer;
    }

  peer->peer = newu;
  peer->remoteaddr = newu->localaddr;
  newu->peer = peer;
  newu->remoteaddr = peer->localaddr;

  *conn_sock = ports_get_right (newu);
  *conn_sock_type = MACH_MSG_TYPE_MAKE_SEND;
  *addr = ports_get_right (peer->localaddr);
  *addrtype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_connect (struct user *u,
		  struct address *a)
{
  struct user *peer, *newu;
  if (!a || !u)
    return EOPNOTSUPP;
  
  if (u->type == SOCK_STREAM)
    {
      if (u->peer)
	return EISCONNECTED;

      if (!a->stream_bound)
	return ECONNREFUSED;
    }
  else
    {
      if (!a->dgram_bound)
	return ECONNREFUSED;
    }

  u->remoteaddr = a;

  if (u->type == SOCK_STREAM)
    {
      peer = a->stream_bound;
      if (peer->acc)
	{
	  struct accept_req *acc = peer->acc;
	  peer->acc = acc->next;
	  if (acc == peer->acclast)
	    peer->acclast = 0;

	  if (!u->localaddr)
	    {
	      u->localaddr = make_addr ();
	      u->localaddr->stream_bound = u;
	    }
	  newu = inherit_sock (peer);

	  u->remoteaddr = newu->localaddr;
	  u->peer = newu;
	  newu->peer = u;
	  newu->remoteaddr = u->localaddr;
	  
	  socket_accept_reply (acc->replyport, acc->replyporttype, 0,
			       ports_get_right (newu), MACH_MSG_TYPE_MAKE_SEND,
			       ports_get_right (u->localaddr),
			       MACH_MSG_TYPE_MAKE_SEND);
	  free (acc);
	}
      else
	{
	  if (peer->listenlimit == peer->listenqlen)
	    return ECONNREFUSED;
	  
	  peer = a->stream_bound;
	  u->peer = peer;
	  u->listennext = peer->listenq;
	  peer->listenq = u;
	  peer->listenqlen++;
	}
    }
  return 0;
}

error_t  
S_socket_bind (struct user *u,
	     struct address *a)
{
  if (!u || !a)
    return EOPNOTSUPP;
  
  if ((u->type == SOCK_STREAM && a->stream_bound)
      || (u->type == SOCK_DGRAM && a->dgram_bound))
    return EADDRINUSE;
  
  if (u->peer)
    return EISCONNECTED;
  
  if (u->localaddr)
    {
      if (u->type == SOCK_STREAM)
	return EINVAL;
      else
	u->localaddr->dgram_bound = 0;
    }

  u->localaddr = a;
  if (u->type == SOCK_STREAM)
    a->stream_bound = u;
  else
    a->dgram_bound = u;
  return 0;
}

error_t
S_socket_name (struct user *u,
	       mach_port_t *addr,
	       mach_msg_type_name_t *addrtype)
{
  if (!u)
    return EOPNOTSUPP;
  if (!u->addr)
    return ENOTCONNECTED;
  if (!u->localaddr)
    {
      u->localaddr = make_addr ();
      if (u->type == SOCK_DGRAM)
	u->localaddr->dgram_bound = u;
      else
	u->localaddr->stream_bound = u;
    }
  
  *addr = ports_get_right (u->localaddr);
  *addrtype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_peername (struct user *u,
		 mach_port_t *addr,
		 mach_msg_type_name_t *addrtype)
{
  if (!u)
    return EOPNOTSUPP;
  
  if (!u->remoteaddr)
    return ENOTCONNECTED;
  *addr = ports_get_right (u->remoteaddr);
  *addrtype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_connect2 (struct user *u1,
		   struct user *u2)
{
  if (!u1 || !u2)
    return EOPNOTSUPP;
  
  if (u1->type != SOCK_STREAM
      || u2->type != SOCK_STREAM)
    return EOPNOTSUPP;

  if (u1->peer || u2->peer)
    return EISCONNECTED;
  
  if (!u1->localaddr)
    {
      u1->localaddr = make_addr();
      u1->localaddr->stream_bound = u1;
    }
  if (!u2->localaddr)
    {
      u2->localaddr = make_addr();
      u2->localaddr->stream_bound = u2;
    }

  u1->peer = u2;
  u1->remoteaddr = u2->localaddr;
  u2->peer = u1;
  u2->remateaddr = u1->localaddr;
  return 0;
}

error_t
S_socket_create_address (struct user *u,
			 int type,
			 char *data,
			 u_int datalen,
			 mach_pnort_t *addr,
			 mach_msg_type_name_t *addrtype,
			 int binding)
{
  if (!u)
    return EOPNOTSUPP;
  
  *addr = ports_get_right (make_addr ());
  *addrtype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_fabricate_address (struct user *u,
			    mach_port_t *addr,
			    mach_msg_type_name_t *addrtype)
{
  if (!u)
    return EOPNOTSUPP;
  
  *addr = ports_get_right (make_addr ());
  *addrtype = MACH_MSG_TYPE_MAKE_SEND;
  return 0;
}

error_t
S_socket_whatis_address (struct address *a,
			 int *type,
			 char **data,
			 u_int *datalen)
{
  if (!a)
    return EOPNOTSUPP;
  
  *type = AF_LOCAL;
  *datalen = 0;
  return 0;
}

