/* tcpserver.c
 * copyright © 2010-2015 IOhannes m zmölnig, IEM
 * copyright (c) 2006-2010 Martin Peach
 * copyright (c) 2004 Olaf Matthes
 */

/*                                                                              */
/* A server for bidirectional communication from within Pd.                     */
/* Allows to send back data to specific clients connected to the server.        */
/*                                                                              */
/* 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               */
/* of the License, 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, see                                         */
/*     http://www.gnu.org/licenses/                                             */
/*                                                                              */

/* ---------------------------------------------------------------------------- */
#define DEBUGLEVEL 1

#include "iemnet.h"
#include "iemnet_data.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
/* needed for TCP_NODELAY */
# include <netinet/tcp.h>
#endif

#define MAX_CONNECT 32 /* maximum number of connections */

/* ----------------------------- tcpserver ------------------------- */

static t_class *tcpserver_class;
static const char objName[] = "tcpserver";

typedef struct _tcpserver_socketreceiver {
  struct _tcpserver *sr_owner;

  long sr_host;
  unsigned short sr_port;
  int sr_fd;
  t_iemnet_sender*sr_sender;
  t_iemnet_receiver*sr_receiver;
} t_tcpserver_socketreceiver;

typedef struct _tcpserver {
  t_object x_obj;
  t_outlet*x_msgout;
  t_outlet*x_connectout;
  t_outlet*x_sockout; /* legacy */
  t_outlet*x_addrout; /* legacy */
  t_outlet*x_statusout;

  int x_serialize;

  t_tcpserver_socketreceiver*x_sr[MAX_CONNECT]; /* socket per connection */
  unsigned int x_nconnections;

  int x_connectsocket; /* socket waiting for new connections */
  int x_port;

  /* the default connection to send to; 0 = broadcast; >0 use this client; <0 exclude this client */
  int x_defaulttarget; 
  t_iemnet_floatlist*x_floatlist;
} t_tcpserver;

/* forward declarations */
static void tcpserver_receive_callback(void*x, t_iemnet_chunk*);

static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(
  t_tcpserver *owner, int sockfd, struct sockaddr_in*addr)
{
  t_tcpserver_socketreceiver *x = (t_tcpserver_socketreceiver *)getbytes(
                                    sizeof(*x));
  if(NULL == x) {
    iemnet_log(x, IEMNET_FATAL, "unable to allocate %d bytes", (int)sizeof(*x));
    return NULL;
  }
  x->sr_owner = owner;

  x->sr_fd = sockfd;

  x->sr_host = ntohl(addr->sin_addr.s_addr);
  x->sr_port = ntohs(addr->sin_port);

  x->sr_sender = iemnet__sender_create(sockfd, NULL, NULL, 0);
  x->sr_receiver = iemnet__receiver_create(sockfd, x,
                                         tcpserver_receive_callback, 0);
  return (x);
}

static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x)
{
  DEBUG("freeing %x", x);
  if (x != NULL) {
    int sockfd = x->sr_fd;
    t_iemnet_sender*sender = x->sr_sender;
    t_iemnet_receiver*receiver = x->sr_receiver;

    x->sr_owner = NULL;
    x->sr_sender = NULL;
    x->sr_receiver = NULL;

    x->sr_fd = -1;

    if(sender) {
      iemnet__sender_destroy(sender, 0);
    }
    if(receiver) {
      iemnet__receiver_destroy(receiver, 0);
    }

    iemnet__closesocket(sockfd, 1);

    freebytes(x, sizeof(*x));
  }
  DEBUG("freed %x", x);
}

static int tcpserver_socket2index(t_tcpserver*x, int sockfd)
{
  unsigned int i = 0;
  for(i = 0; i < x->x_nconnections; i++) { /* check if connection exists */
    if(x->x_sr[i]->sr_fd == sockfd) {
      return i;
    }
  }
  return -1;
}

/* checks whether client is a valid (1-based) index
 *  if the id is invalid, returns -1
 *  if the id is valid, return the 0-based index (client-1)
 */
static int tcpserver_fixindex(t_tcpserver*x, int client)
{
  if(x->x_nconnections <= 0) {
    iemnet_log(x, IEMNET_ERROR, "no clients connected");
    return -1;
  }

  if (!((client > 0) && ((unsigned int)client <= x->x_nconnections))) {
    iemnet_log(x, IEMNET_ERROR,
               "client:%d out of range [1..%d]",
               client, (int)(x->x_nconnections));
    return -1;
  }
  return (client-1);
}

/* ---------------- tcpserver info ---------------------------- */
static void tcpserver_info_client(t_tcpserver *x, unsigned int client)
{
  /*
    "client <id> <socket> <IP> <port>"
    "bufsize <id> <insize> <outsize>"
  */
  static t_atom output_atom[4];
  if(x&&client<MAX_CONNECT&&x->x_sr[client]) {
    int sockfd = x->x_sr[client]->sr_fd;
    unsigned short port = x->x_sr[client]->sr_port;
    long address = x->x_sr[client]->sr_host;
    char hostname[MAXPDSTRING];

    int insize = iemnet__receiver_getsize(x->x_sr[client]->sr_receiver);
    int outsize = iemnet__sender_getsize(x->x_sr[client]->sr_sender);

    snprintf(hostname, MAXPDSTRING-1, "%d.%d.%d.%d",
             (unsigned char)((address & 0xFF000000)>>24),
             (unsigned char)((address & 0x0FF0000)>>16),
             (unsigned char)((address & 0x0FF00)>>8),
             (unsigned char)((address & 0x0FF)));
    hostname[MAXPDSTRING-1] = 0;

    SETFLOAT(output_atom+0, client+1);
    SETFLOAT(output_atom+1, sockfd);
    SETSYMBOL(output_atom+2, gensym(hostname));
    SETFLOAT(output_atom+3, port);

    outlet_anything( x->x_statusout, gensym("client"), 4, output_atom);

    SETFLOAT(output_atom+0, client+1);
    SETFLOAT(output_atom+1, insize);
    SETFLOAT(output_atom+2, outsize);
    outlet_anything( x->x_statusout, gensym("bufsize"), 3, output_atom);
  }
}

static void tcpserver_info(t_tcpserver *x)
{
  static t_atom output_atom[4];
  int sockfd = x->x_connectsocket;

  int port = x->x_port;

  if(sockfd<0) {
    iemnet_log(x, IEMNET_ERROR, "no open socket");
    return;
  }

  if(x->x_port <= 0) {
    struct sockaddr_in server;
    socklen_t serversize = sizeof(server);
    if(!getsockname(sockfd, (struct sockaddr *)&server, &serversize)) {
      x->x_port = ntohs(server.sin_port);
      port = x->x_port;
    } else {
      iemnet_log(x, IEMNET_ERROR, "unable to get socket name for %d", sockfd);
      sys_sockerror("getsockname");
      return;
    }
  }

  iemnet__socket2addressout(sockfd, x->x_statusout, gensym("local_address"));
  SETFLOAT(output_atom+0, port);
  outlet_anything( x->x_statusout, gensym("port"), 1, output_atom);
}

static void tcpserver_info_connection(t_tcpserver *x,
                                      t_tcpserver_socketreceiver*y)
{
  iemnet__addrout(x->x_statusout, x->x_addrout, y->sr_host, y->sr_port);
  outlet_float(x->x_sockout, y->sr_fd);
}

/* ---------------- main tcpserver (send) stuff --------------------- */
static void tcpserver_disconnect_socket(t_tcpserver *x,
                                        t_floatarg fsocket);

static void tcpserver_send_bytes_client(t_tcpserver*x,
                                        t_tcpserver_socketreceiver*sr, int client, t_iemnet_chunk*chunk)
{
  if(sr) {
    t_atom output_atom[3];
    int size = -1;

    t_iemnet_sender*sender = sr->sr_sender;
    int sockfd = sr->sr_fd;
    if(sender) {
      size = iemnet__sender_send(sender, chunk);
    }

    SETFLOAT(&output_atom[0], client+1);
    SETFLOAT(&output_atom[1], size);
    SETFLOAT(&output_atom[2], sockfd);
    outlet_anything( x->x_statusout, gensym("sendbuffersize"), 3, output_atom);

    if(size<0) {
      /* disconnected! */
      tcpserver_disconnect_socket(x, sockfd);
    }
  }
}

static void tcpserver_send_bytes(t_tcpserver*x, int client,
                                 t_iemnet_chunk*chunk)
{
  t_tcpserver_socketreceiver*sr = NULL;
  if(x&&client<MAX_CONNECT) {
    sr = x->x_sr[client];
  }
  DEBUG("send_bytes to %p[%d] -> %p", x, client, sr);
  tcpserver_send_bytes_client(x, sr, client, chunk);
}

/* send the chunk to all non-null clients */
static void tcpserver_send_bytes_clients(t_tcpserver*x,
    t_tcpserver_socketreceiver**sr, unsigned int nsr, t_iemnet_chunk*chunk)
{
  unsigned int i = 0;
  for(i = 0; i<nsr; i++) {
    tcpserver_send_bytes_client(x, sr[i], i, chunk);
  }
}

/* broadcasts a message to all connected clients but the given one */
static void tcpserver_send_butclient(t_tcpserver *x, unsigned int but,
                                     int argc, t_atom *argv)
{
  unsigned int client = 0;
  t_iemnet_chunk*chunk = NULL;
  t_tcpserver_socketreceiver**sr = NULL;
  if(!x || !x->x_nconnections) {
    return;
  }

  chunk = iemnet__chunk_create_list(argc, argv);
  sr = (t_tcpserver_socketreceiver**)calloc(x->x_nconnections,
                                          sizeof(t_tcpserver_socketreceiver*));

  for(client = 0; client<x->x_nconnections; client++) {
    sr[client] = x->x_sr[client];
  }

  if(but<x->x_nconnections) {
    sr[but] = NULL;
  }

  tcpserver_send_bytes_clients(x, sr, x->x_nconnections, chunk);

  free(sr);
  iemnet__chunk_destroy(chunk);
}
/* sends a message to a given client */
static void tcpserver_send_toclient(t_tcpserver *x, unsigned int client,
                                    int argc, t_atom *argv)
{
  t_iemnet_chunk*chunk = iemnet__chunk_create_list(argc, argv);
  tcpserver_send_bytes(x, client, chunk);
  iemnet__chunk_destroy(chunk);
}

/* send message to client using client number
   note that the client numbers might change in case a client disconnects! */
/* clients start at 1 but our index starts at 0 */
static void tcpserver_send_client(t_tcpserver *x, t_symbol *s, int argc,
                                  t_atom *argv)
{
  (void)s; /* ignore unused variable */
  if (argc > 0) {
    int client = tcpserver_fixindex(x, atom_getint(argv));
    if(client<0) {
      return;
    }
    if(argc == 1) {
      tcpserver_info_client(x, client);
    } else {
      tcpserver_send_toclient(x, client, argc-1, argv+1);
    }
    return;
  } else {
    unsigned int client = 0;
    for(client = 0; client<x->x_nconnections; client++) {
      tcpserver_info_client(x, client);
    }
  }
}

/* broadcasts a message to all connected clients */
static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc,
                                t_atom *argv)
{
  unsigned int client = 0;
  t_iemnet_chunk*chunk = NULL;
  t_tcpserver_socketreceiver**sr = NULL;
  (void)s; /* ignore unused variable */
  if(!x || !x->x_nconnections) {
    return;
  }

  chunk = iemnet__chunk_create_list(argc, argv);
  sr = (t_tcpserver_socketreceiver**)calloc(x->x_nconnections,
                                          sizeof(t_tcpserver_socketreceiver*));

  for(client = 0; client<x->x_nconnections; client++) {
    sr[client] = x->x_sr[client];
  }

  tcpserver_send_bytes_clients(x, sr, x->x_nconnections, chunk);

  free(sr);
  iemnet__chunk_destroy(chunk);
}

static void tcpserver_defaultsend(t_tcpserver *x, t_symbol *s, int argc,
                                  t_atom *argv)
{
  int client = -1;
  int sockfd = x->x_defaulttarget;
  if(sockfd>0) {
    client = tcpserver_socket2index(x, sockfd);
    if(client >= 0) {
      tcpserver_send_toclient(x, client, argc, argv);
      return;
    }
    iemnet_log(x, IEMNET_ERROR, "illegal socket:%d, switching to broadcast mode", sockfd);
    x->x_defaulttarget = 0;
  } else if(sockfd<0) {
    client = tcpserver_socket2index(x, -sockfd);
    if(client >= 0) {
      tcpserver_send_butclient(x, client, argc, argv);
      return;
    }
    iemnet_log(x, IEMNET_ERROR, "illegal socket:%d excluded, switching to broadcast mode", sockfd);
    x->x_defaulttarget = 0;
  }

  tcpserver_broadcast(x, s, argc, argv);
}
static void tcpserver_defaulttarget(t_tcpserver *x, t_floatarg f)
{
  int sockfd = 0;
  int rawclient = f;
  unsigned int client = (rawclient<0)?(-rawclient):rawclient;

  if(client > x->x_nconnections) {
    iemnet_log(x, IEMNET_ERROR,
               "target %d out of range [0..%d]",
               client, (int)(x->x_nconnections));
    return;
  }

  /* map the client to a persistant socket */
  if(client>0) {
    sockfd = x->x_sr[client-1]->sr_fd;
  }

  if(rawclient<0) {
    sockfd = -sockfd;
  }

  x->x_defaulttarget = sockfd;
}
static void tcpserver_targetsocket(t_tcpserver *x, t_floatarg f)
{
  int sockfd = f;
  x->x_defaulttarget = sockfd;
}

/* send message to client using socket number */
static void tcpserver_send_socket(t_tcpserver *x, t_symbol *s, int argc,
                                  t_atom *argv)
{
  int client = -1;
  t_iemnet_chunk*chunk = NULL;
  (void)s; /* ignore unused variable */
  if(argc) {
    client = tcpserver_socket2index(x, atom_getint(argv));
    if(client<0) {
      return;
    }
  } else {
    iemnet_log(x, IEMNET_ERROR, "no socket specified");
    return;
  }

  /* get socket number of connection (first element in list) */
  if(argc && argv->a_type == A_FLOAT) {
    int sockfd = atom_getint(argv);
    client = tcpserver_socket2index(x, sockfd);
    if(client < 0) {
      iemnet_log(x, IEMNET_ERROR, "no connection on socket %d", sockfd);
      return;
    }
  } else {
    iemnet_log(x, IEMNET_ERROR, "only numeric sockets allowed");
    return;
  }

  chunk = iemnet__chunk_create_list(argc-1, argv+1);
  tcpserver_send_bytes(x, client, chunk);
  iemnet__chunk_destroy(chunk);
}

static void tcpserver_disconnect(t_tcpserver *x, unsigned int client)
{
  unsigned int k;
  DEBUG("disconnect %x %d", x, client);
  tcpserver_info_connection(x, x->x_sr[client]);

  tcpserver_socketreceiver_free(x->x_sr[client]);
  x->x_sr[client] = NULL;

  /* rearrange list now: move entries to close the gap */
  for(k = client; k < x->x_nconnections; k++) {
    x->x_sr[k] = x->x_sr[k + 1];
  }
  x->x_sr[k + 1] = NULL;
  x->x_nconnections--;

  iemnet__numconnout(x->x_statusout, x->x_connectout, x->x_nconnections);
}

/* disconnect a client by number */
static void tcpserver_disconnect_client(t_tcpserver *x, t_floatarg fclient)
{
  int client = tcpserver_fixindex(x, fclient);

  if(client<0) {
    return;
  }
  tcpserver_disconnect(x, client);
}

/* disconnect a client by socket */
static void tcpserver_disconnect_socket(t_tcpserver *x, t_floatarg fsocket)
{
  int id = tcpserver_socket2index(x, (int)fsocket);
  if(id >= 0) {
    tcpserver_disconnect_client(x, id+1);
  }
}

/* disconnect a client by socket */
static void tcpserver_disconnect_all(t_tcpserver *x)
{
  unsigned int id;
  for(id = 0; id<x->x_nconnections; id++) {
    tcpserver_disconnect(x, id);
  }
}

/* ---------------- main tcpserver (receive) stuff --------------------- */
static void tcpserver_receive_callback(void *y0,
                                       t_iemnet_chunk*c)
{
  t_tcpserver_socketreceiver *y = (t_tcpserver_socketreceiver*)y0;
  t_tcpserver*x = NULL;
  if(NULL == y || NULL == (x = y->sr_owner)) {
    return;
  }

  if(c) {
    tcpserver_info_connection(x, y);
    /* get's destroyed in the dtor */
    x->x_floatlist = iemnet__chunk2list(c, x->x_floatlist);
    iemnet__streamout(x->x_msgout, x->x_floatlist->argc, x->x_floatlist->argv,
                      x->x_serialize);
  } else {
    /* disconnected */
    int sockfd = y->sr_fd;
    iemnet_log(x, IEMNET_VERBOSE, "got disconnection for socket:%d", sockfd);
    tcpserver_disconnect_socket(x, sockfd);
  }
}

static void tcpserver_connectpoll(t_tcpserver *x, int fd)
{
  struct sockaddr_in incomer_address;
  socklen_t sockaddrl = sizeof( struct sockaddr );
  int i;
  if(fd != x->x_connectsocket) {
    iemnet_log(x, IEMNET_FATAL, "callback received for socket:%d on listener for socket:%d", fd, x->x_connectsocket);
    return;
  }

  fd = accept(fd, (struct sockaddr*)&incomer_address, &sockaddrl);

  if (fd < 0) {
    post("%s: accept failed", objName);
  } else {
    t_tcpserver_socketreceiver *y = NULL;
    if(x->x_nconnections >= MAX_CONNECT) {
      iemnet_log(x, IEMNET_ERROR,
                 "cannot handle more than %d connections, dropping!",
                 x->x_nconnections);
      iemnet__closesocket(fd, 1);
      return;
    }

    y = tcpserver_socketreceiver_new((void *)x, fd, &incomer_address);
    if (!y) {
      iemnet__closesocket(fd, 1);
      return;
    }
    x->x_nconnections++;
    i = x->x_nconnections - 1;
    x->x_sr[i] = y;

    tcpserver_info_connection(x, y);
  }
  iemnet__numconnout(x->x_statusout, x->x_connectout, x->x_nconnections);
}

static void tcpserver_port(t_tcpserver*x, t_floatarg fportno)
{
  static t_atom ap[1];
  int portno = fportno;
  struct sockaddr_in server;
  socklen_t serversize = sizeof(server);
  int sockfd = x->x_connectsocket;
  int intarg;
  memset(&server, 0, sizeof(server));

  SETFLOAT(ap, -1);
  if(x->x_port == portno) {
    return;
  }

  /* cleanup any open ports */
  if(sockfd >= 0) {
    sys_rmpollfn(sockfd);
    iemnet__closesocket(sockfd, 1);
    x->x_connectsocket = -1;
    x->x_port = -1;
  }

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if(sockfd<0) {
    iemnet_log(x, IEMNET_ERROR, "unable to create TCP/IP socket");
    sys_sockerror("socket");
    return;
  }

  /* ask OS to allow another Pd to reopen this port after we close it. */
#ifdef SO_REUSEADDR
  intarg = 1;
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
                 (char *)&intarg, sizeof(intarg))
      < 0) {
    iemnet_log(x, IEMNET_ERROR, "unable to enable address re-using");
    sys_sockerror("setsockopt:SO_REUSEADDR");
  }
#endif /* SO_REUSEADDR */
#ifdef SO_REUSEPORT
  intarg = 1;
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,
                 (char *)&intarg, sizeof(intarg))
      < 0) {
    iemnet_log(x, IEMNET_ERROR, "unable to enable port re-using");
    sys_sockerror("setsockopt:SO_REUSEPORT");
  }
#endif /* SO_REUSEPORT */

  /* Stream (TCP) sockets are set NODELAY */
  intarg = 1;
  if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
                 (char *)&intarg, sizeof(intarg)) < 0) {
    iemnet_log(x, IEMNET_ERROR, "unable to enable immediate sending");
    sys_sockerror("setsockopt:TCP_NODELAY");
  }

  server.sin_family = AF_INET;

  /* LATER allow setting of inaddr */
  server.sin_addr.s_addr = INADDR_ANY;

  /* assign server port number */
  server.sin_port = htons((u_short)portno);
  /* name the socket */
  if (bind(sockfd, (struct sockaddr *)&server, serversize) < 0) {
    iemnet_log(x, IEMNET_ERROR, "unable to bind to TCP/IP socket");
    sys_sockerror("bind");
    iemnet__closesocket(sockfd, 1);
    outlet_anything(x->x_statusout, gensym("port"), 1, ap);
    return;
  }

  /* streaming protocol */
  if (listen(sockfd, 5) < 0) {
    iemnet_log(x, IEMNET_ERROR, "unable to listen on TCP/IP socket");
    sys_sockerror("listen");
    iemnet__closesocket(sockfd, 1);
    sockfd = -1;
    outlet_anything(x->x_statusout, gensym("port"), 1, ap);
    return;
  } else {
      /* wait for new connections */
      sys_addpollfn(sockfd, (t_fdpollfn)tcpserver_connectpoll, x);
  }

  x->x_connectsocket = sockfd;
  x->x_port = portno;

  /* find out which port is actually used (useful when assigning "0") */
  if(!getsockname(sockfd, (struct sockaddr *)&server, &serversize)) {
    x->x_port = ntohs(server.sin_port);
  }

  iemnet__socket2addressout(sockfd, x->x_statusout, gensym("local_address"));

  SETFLOAT(ap, x->x_port);
  outlet_anything(x->x_statusout, gensym("port"), 1, ap);
}

static void tcpserver_serialize(t_tcpserver *x, t_floatarg doit)
{
  x->x_serialize = doit;
}


static void *tcpserver_new(t_floatarg fportno)
{
  t_tcpserver*x;
  int i;

  x = (t_tcpserver *)pd_new(tcpserver_class);

  x->x_msgout = outlet_new(&x->x_obj, 0); /* 1st outlet for received data */
  x->x_connectout = outlet_new(&x->x_obj,
                               gensym("float")); /* 2nd outlet for number of connected clients */
  x->x_sockout = outlet_new(&x->x_obj, gensym("float"));
  x->x_addrout = outlet_new(&x->x_obj, gensym("list" ));
  x->x_statusout = outlet_new(&x->x_obj,
                            0);/* 5th outlet for everything else */

  x->x_serialize = 1;

  x->x_connectsocket = -1;
  x->x_port = -1;
  x->x_nconnections = 0;

  for(i = 0; i < MAX_CONNECT; i++) {
    x->x_sr[i] = NULL;
  }

  x->x_defaulttarget = 0;
  x->x_floatlist = iemnet__floatlist_create(1024);

  tcpserver_port(x, fportno);

  return (x);
}

static void tcpserver_free(t_tcpserver *x)
{
  int i;

  for(i = 0; i < MAX_CONNECT; i++) {
    if (NULL != x->x_sr[i]) {
      DEBUG("[%s] free %x", objName, x);
      tcpserver_socketreceiver_free(x->x_sr[i]);
      x->x_sr[i] = NULL;
    }
  }
  if (x->x_connectsocket >= 0) {
    sys_rmpollfn(x->x_connectsocket);
    iemnet__closesocket(x->x_connectsocket, 1);
  }
  if(x->x_floatlist) {
    iemnet__floatlist_destroy(x->x_floatlist);
  }
  x->x_floatlist = NULL;
}

IEMNET_EXTERN void tcpserver_setup(void)
{
  if(!iemnet__register(objName)) {
    return;
  }

  tcpserver_class = class_new(gensym(objName),(t_newmethod)tcpserver_new,
                              (t_method)tcpserver_free,
                              sizeof(t_tcpserver), 0, A_DEFFLOAT, 0);
  class_addmethod(tcpserver_class, (t_method)tcpserver_disconnect_client,
                  gensym("disconnectclient"), A_DEFFLOAT, 0);
  class_addmethod(tcpserver_class, (t_method)tcpserver_disconnect_socket,
                  gensym("disconnectsocket"), A_DEFFLOAT, 0);
  class_addmethod(tcpserver_class, (t_method)tcpserver_disconnect_all,
                  gensym("disconnect"), 0);

  class_addmethod(tcpserver_class, (t_method)tcpserver_send_socket,
                  gensym("send"), A_GIMME, 0);
  class_addmethod(tcpserver_class, (t_method)tcpserver_send_client,
                  gensym("client"), A_GIMME, 0);

  class_addmethod(tcpserver_class, (t_method)tcpserver_broadcast,
                  gensym("broadcast"), A_GIMME, 0);

  class_addmethod(tcpserver_class, (t_method)tcpserver_defaulttarget,
                  gensym("target"), A_DEFFLOAT, 0);
  class_addmethod(tcpserver_class, (t_method)tcpserver_targetsocket,
                  gensym("targetsocket"), A_DEFFLOAT, 0);
  class_addlist(tcpserver_class, (t_method)tcpserver_defaultsend);

  class_addmethod(tcpserver_class, (t_method)tcpserver_serialize,
                  gensym("serialize"), A_FLOAT, 0);


  class_addmethod(tcpserver_class, (t_method)tcpserver_port, gensym("port"),
                  A_DEFFLOAT, 0);
  class_addbang(tcpserver_class, (t_method)tcpserver_info);

  DEBUGMETHOD(tcpserver_class);
}

IEMNET_INITIALIZER(tcpserver_setup);

/* end of tcpserver.c */
