/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: ctrackrouter.c,v 1.5.2.2 2001/09/19 19:22:16 bazsi Exp $
 *
 ***************************************************************************/

#include <zorp/zorp.h>
#include <zorp/log.h>
#include <zorp/ctrack.h>
#include <zorp/policy.h>
#include <zorp/pyvars.h>
#include <zorp/poll.h>
#include <zorp/thread.h>
#include <zorp/packsock.h>
#include <zorp/pyproxy.h>
#include <zorp/pyctrack.h>

#if ENABLE_CONNTRACK

typedef struct _ZRealConntrackRouter
{
  ZSockAddr *local;
  ZPoll *poll;
  guint polled;
  GSList *streams;
  GHashTable *router;
  gboolean have_stream;
  GMutex *lock;
  GCond *restart;
//  ZCtrackCallback callback;
  gpointer user_data;
} ZRealConntrackRouter;

typedef struct _ZConntrackRouting
{
  guint fd;
  gpointer user_data;
} ZConntrackRouting;


static guint z_conntrack_router_read(ZStream *stream, GIOCondition cond);

static ZStream *
z_conntrack_router_searchstream(ZRealConntrackRouter *self, int fd)
{
  GSList *tmplist;
  ZStream *mystream = NULL;
  
  z_enter();
  tmplist = self->streams;
  
  while (tmplist != NULL)
    {
      if (tmplist->data && ((ZStream *)tmplist->data)->fd == fd)
        break;
      
      tmplist = g_slist_next(tmplist);
    }
  if (tmplist != NULL)
    {
      mystream = (ZStream *)tmplist->data;
      self->streams = g_slist_remove_link(self->streams, tmplist);
    }
  
  z_leave();
  return mystream;
}

ZCTRouter *
z_conntrack_router_new(void)
{
  ZRealConntrackRouter *self = g_new0(ZRealConntrackRouter, 1);
  
  z_enter();
  
  self->poll = z_poll_new();
  self->polled = 0;
  self->lock = g_mutex_new();
  self->restart = g_cond_new();
  self->streams = g_slist_alloc();
  self->have_stream = FALSE;
  self->user_data = NULL;
  self->router = g_hash_table_new(g_int_hash, g_int_equal);
  
  z_leave();
  return (ZCTRouter *)self;
}

void
z_conntrack_router_start(gpointer s)
{
  ZRealConntrackRouter *self= (ZRealConntrackRouter *) s;
  int rc;

  z_enter();
  if (!self->poll)
    {
      z_leave();
      return;
    }

  while (!self->have_stream || !self->user_data)
    {
      g_mutex_lock(self->lock);
      g_cond_wait(self->restart, self->lock);
      g_mutex_unlock(self->lock);
    }

  while ((rc=z_poll_iter(self->poll)))
    {
#if 0  // Ugyanabban a thread-ben vagyunk...
      if (rc==2)
        {
          g_mutex_lock(self->lock);
          if (self->polled == 0)
            {
              g_cond_wait(self->restart, self->lock);
            }
          self->polled = 0;
          g_mutex_unlock(self->lock);
        }
#endif
    }
  z_leave();
}

gboolean
z_conntrack_router_newfd(ZCTRouter *s, int fd)
{
  ZRealConntrackRouter *self= (ZRealConntrackRouter *) s;
  ZStream *newstream = g_new0(ZStream, 1);
  
  z_enter();

  z_poll_wakeup(self->poll);

  newstream = z_stream_new(fd, "Kakukk");
  newstream->want_read = TRUE;
  newstream->want_write = FALSE;
  newstream->want_pri = FALSE;
  newstream->user_data = (gpointer) self;
  newstream->read = z_conntrack_router_read;
  
  self->streams = g_slist_prepend(self->streams, newstream);

  g_mutex_lock(self->lock);

    self->polled = 1;
    z_poll_add_stream(self->poll, newstream);
    self->have_stream = TRUE;
    g_cond_signal(self->restart);

  g_mutex_unlock(self->lock);
  
  z_leave();
  return TRUE;
}

gboolean
z_conntrack_router_newfd_with_data(ZCTRouter *s, int fd, gpointer user_data)
{
  ZRealConntrackRouter *self = (ZRealConntrackRouter *) s;
  ZConntrackRouting *router = g_new0(ZConntrackRouting, 1);
  
  z_enter();

  router->fd = fd;
  router->user_data = user_data;
  
  g_hash_table_insert(self->router, &router->fd, router);

  z_conntrack_router_newfd(s, fd);
  
  return TRUE;
}

void
z_conntrack_router_remove_fd(ZCTRouter *s, int fd)
{
  ZRealConntrackRouter *self = (ZRealConntrackRouter *) s;
  ZConntrackRouting *router;
  ZStream *mystream;
  
  z_enter();
  mystream = z_conntrack_router_searchstream(self, fd);
  if (mystream)
    {
      z_poll_wakeup(self->poll);
  
      g_mutex_lock(self->lock);

        self->polled = 1;
        z_poll_remove_stream(self->poll, mystream);
        g_cond_signal(self->restart);

      g_mutex_unlock(self->lock);
  
      g_free(mystream);
    }

  router = g_hash_table_lookup(self->router, &fd);
  
  if (router)
    {
      g_hash_table_remove(self->router, &fd);
      
      g_free(router);
    }
    
  z_leave();
  return;
}

gboolean
z_conntrack_router_sethandler(ZCTRouter *s, gpointer user_data)
{
  ZRealConntrackRouter *self = (ZRealConntrackRouter *) s;

  z_enter();
  g_mutex_lock(self->lock);

    self->user_data = user_data;
    g_cond_signal(self->restart);

  g_mutex_unlock(self->lock);
  
  z_leave();
  return TRUE;
}

static guint
z_conntrack_router_read(ZStream *stream, GIOCondition cond)
{
  int fd = stream->fd;
  ZPacket *packet=g_new0(ZPacket, 1);
  ZRealConntrackRouter *self;
  ZConntrackRouting *routing;
  
  z_enter();
  self=(ZRealConntrackRouter *)stream->user_data;
  z_packsock_receive(fd, packet);
  
  routing = g_hash_table_lookup(self->router, &fd);
  
  if (routing)
    z_tracker_read(routing->user_data, packet, fd);
  else
    z_tracker_read(self->user_data, packet, fd);
  
  z_leave();
  return G_IO_ERROR_NONE;
}

#endif
