/*
    EIBD eib bus access and management daemon
    Copyright (C) 2005-2007 Martin Koegler <mkoegler@auto.tuwien.ac.at>

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "eibnetserver.h"
#include "emi.h"

#define NAME "eibd"

EIBnetServer::EIBnetServer (const char *multicastaddr, int port, bool Tunnel,
			    bool Route, bool Discover, Layer3 * layer3,
			    Trace * tr)
{
  struct sockaddr_in baddr;
  struct ip_mreq mcfg;
  t = tr;
  l3 = layer3;

  TRACEPRINTF (t, 8, this, "Open");
  baddr.sin_family = AF_INET;
  baddr.sin_port = htons (port);
  baddr.sin_addr.s_addr = htonl (INADDR_ANY);

  if (GetHostIP (&maddr, multicastaddr) == 0)
    throw Exception (DEV_OPEN_FAIL);
  maddr.sin_port = htons (port);

  sock = new EIBNetIPSocket (baddr, 1, t);
  mcfg.imr_multiaddr = maddr.sin_addr;
  mcfg.imr_interface.s_addr = htonl (INADDR_ANY);
  sock->SetMulticast (mcfg);
  sock->recvall = 2;
  if (!GetSourceAddress (&maddr, &sock->localaddr))
    throw Exception (DEV_OPEN_FAIL);
  sock->localaddr.sin_port = htons (port);
  tunnel = Tunnel;
  route = Route;
  discover = Discover;
  Port = htons (port);
  if (route || tunnel)
    {
      if (!l3->registerBroadcastCallBack (this))
	throw Exception (DEV_OPEN_FAIL);
      if (!l3->registerGroupCallBack (this, 0))
	throw Exception (DEV_OPEN_FAIL);
      if (!l3->registerIndividualCallBack (this, Individual_Lock_None, 0, 0))
	throw Exception (DEV_OPEN_FAIL);
    }
  Start ();
  TRACEPRINTF (t, 8, this, "Opened");
}


EIBnetServer::~EIBnetServer ()
{
  TRACEPRINTF (t, 8, this, "Close");
  if (route || tunnel)
    {
      l3->deregisterBroadcastCallBack (this);
      l3->deregisterGroupCallBack (this, 0);
      l3->deregisterIndividualCallBack (this, 0, 0);
    }
  Stop ();
  delete sock;
}

void
EIBnetServer::Get_L_Data (L_Data_PDU * l)
{
  if (route)
    {
      TRACEPRINTF (t, 8, this, "Send_Route %s", l->Decode ()());
      sock->sendaddr = maddr;
      EIBNetIPPacket p;
      p.service = ROUTING_INDICATION;
      p.data = L_Data_ToCEMI (0x29, *l);
      sock->Send (p);
    }
  for (int i = 0; i < state (); i++)
    {
      state[i].out.put (L_Data_ToCEMI (0x29, *l));
      pth_sem_inc (state[i].outsignal, 0);
    }
  delete l;
}

void
EIBnetServer::Run (pth_sem_t * stop1)
{
  EIBNetIPPacket *p1;
  EIBNetIPPacket p;
  int i;
  pth_event_t stop = pth_event (PTH_EVENT_SEM, stop1);

  while (pth_event_status (stop) != PTH_STATUS_OCCURRED)
    {
      for (i = 0; i < state (); i++)
	{
	  pth_event_concat (stop, state[i].timeout, NULL);
	  if (state[i].state)
	    pth_event_concat (stop, state[i].sendtimeout, NULL);
	  else
	    pth_event_concat (stop, state[i].outwait, NULL);

	}
      p1 = sock->Get (stop);
      for (i = 0; i < state (); i++)
	{
	  pth_event_isolate (state[i].timeout);
	  pth_event_isolate (state[i].sendtimeout);
	  pth_event_isolate (state[i].outwait);
	}
      if (p1)
	{
	  if (p1->service == SEARCH_REQUEST && discover)
	    {
	      EIBnet_SearchRequest r1;
	      EIBnet_SearchResponse r2;
	      DIB_service_Entry d;
	      if (parseEIBnet_SearchRequest (*p1, r1))
		goto out;
	      TRACEPRINTF (t, 8, this, "SEARCH");
	      r2.KNXmedium = 2;
	      r2.devicestatus = 0;
	      r2.individual_addr = 0;
	      r2.installid = 0;
	      r2.multicastaddr = maddr.sin_addr;
	      strcpy ((char *) r2.name, NAME);
	      d.version = 1;
	      d.family = 2;
	      if (discover)
		r2.services.add (d);
	      d.family = 4;
	      if (tunnel)
		r2.services.add (d);
	      d.family = 5;
	      if (route)
		r2.services.add (d);
	      if (!GetSourceAddress (&r1.caddr, &r2.caddr))
		goto out;
	      r2.caddr.sin_port = Port;
	      sock->sendaddr = r1.caddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == DESCRIPTION_REQUEST && discover)
	    {
	      EIBnet_DescriptionRequest r1;
	      EIBnet_DescriptionResponse r2;
	      DIB_service_Entry d;
	      if (parseEIBnet_DescriptionRequest (*p1, r1))
		goto out;
	      TRACEPRINTF (t, 8, this, "DESCRIBE");
	      r2.KNXmedium = 2;
	      r2.devicestatus = 0;
	      r2.individual_addr = 0;
	      r2.installid = 0;
	      r2.multicastaddr = maddr.sin_addr;
	      strcpy ((char *) r2.name, NAME);
	      d.version = 1;
	      d.family = 2;
	      if (discover)
		r2.services.add (d);
	      d.family = 4;
	      if (tunnel)
		r2.services.add (d);
	      d.family = 5;
	      if (route)
		r2.services.add (d);
	      sock->sendaddr = r1.caddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == ROUTING_INDICATION && route)
	    {
	      if (p1->data () < 2 || p1->data[0] != 0x29)
		goto out;
	      const CArray data = p1->data;
	      L_Data_PDU *c = CEMI_to_L_Data (data);
	      if (c)
		{
		  TRACEPRINTF (t, 8, this, "Recv_Route %s", c->Decode ()());
		  l3->send_L_Data (c);
		}
	    }
	  if (p1->service == CONNECTIONSTATE_REQUEST && tunnel)
	    {
	      uchar res = 21;
	      EIBnet_ConnectionStateRequest r1;
	      EIBnet_ConnectionStateResponse r2;
	      if (parseEIBnet_ConnectionStateRequest (*p1, r1))
		goto out;
	      for (i = 0; i < state (); i++)
		if (state[i].channel = r1.channel)
		  {
		    res = 0;
		    pth_event (PTH_EVENT_TIME | PTH_MODE_REUSE,
			       state[i].timeout, pth_timeout (120, 0));
		  }
	      r2.channel = r1.channel;
	      r2.status = res;
	      sock->sendaddr = r1.caddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == DISCONNECT_REQUEST && tunnel)
	    {
	      uchar res = 0x21;
	      EIBnet_DisconnectRequest r1;
	      EIBnet_DisconnectResponse r2;
	      if (parseEIBnet_DisconnectRequest (*p1, r1))
		goto out;
	      for (i = 0; i < state (); i++)
		if (state[i].channel = r1.channel)
		  {
		    res = 0;
		    pth_event_free (state[i].timeout, PTH_FREE_THIS);
		    pth_event_free (state[i].sendtimeout, PTH_FREE_THIS);
		    pth_event_free (state[i].outwait, PTH_FREE_THIS);
		    delete state[i].outsignal;
		    state.deletepart (i, 1);
		    break;
		  }
	      r2.channel = r1.channel;
	      r2.status = res;
	      sock->sendaddr = r1.caddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == CONNECTION_REQUEST && tunnel)
	    {
	      EIBnet_ConnectRequest r1;
	      EIBnet_ConnectResponse r2;
	      if (parseEIBnet_ConnectRequest (*p1, r1))
		goto out;
	      r2.CRD.resize (3);
	      r2.CRD[0] = 0x04;
	      r2.CRD[1] = 0x00;
	      r2.CRD[2] = 0x00;
	      r2.status = 0x22;
	      if (r1.CRI () == 3 && r1.CRI[0] == 4 && r1.CRI[1] == 2)
		{
		  int id = 1;
		rt:
		  for (i = 0; i < state (); i++)
		    if (state[i].channel == id)
		      {
			id++;
			goto rt;
		      }
		  if (id <= 0xff)
		    {
		      int pos = state ();
		      state.resize (state () + 1);
		      state[pos].timeout =
			pth_event (PTH_EVENT_TIME, pth_timeout (120, 0));
		      state[pos].outsignal = new pth_sem_t;
		      pth_sem_init (state[pos].outsignal);
		      state[pos].outwait =
			pth_event (PTH_EVENT_SEM, state[pos].outsignal);
		      state[pos].sendtimeout =
			pth_event (PTH_EVENT_TIME, pth_timeout (1, 0));
		      state[pos].channel = id;
		      state[pos].daddr = r1.daddr;
		      state[pos].caddr = r1.caddr;
		      state[pos].state = 0;
		      state[pos].sno = 0;
		      state[pos].rno = 0;
		      r2.channel = id;
		      r2.status = 0;
		    }
		}
	      if (!GetSourceAddress (&r1.caddr, &r2.daddr))
		goto out;
	      r2.daddr.sin_port = Port;
	      sock->sendaddr = r1.caddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == TUNNEL_REQUEST && tunnel)
	    {
	      EIBnet_TunnelRequest r1;
	      EIBnet_TunnelACK r2;
	      if (parseEIBnet_TunnelRequest (*p1, r1))
		goto out;
	      TRACEPRINTF (t, 8, this, "TUNNEL_REQ");
	      for (i = 0; i < state (); i++)
		if (state[i].channel == r1.channel)
		  goto reqf;
	      goto out;
	    reqf:
	      if (state[i].rno != r1.seqno)
		{
		  TRACEPRINTF (t, 8, this, "Wrong sequence %d<->%d",
			       r1.seqno, state[i].rno);
		  goto out;
		}
	      r2.channel = r1.channel;
	      r2.seqno = r1.seqno;
	      L_Data_PDU *c = CEMI_to_L_Data (r1.CEMI);
	      if (c)
		{
		  r2.status = 0;
		  if (r1.CEMI[0] == 0x11)
		    {
		      state[i].out.put (L_Data_ToCEMI (0x2E, *c));
		      pth_sem_inc (state[i].outsignal, 0);
		    }
		  if (r1.CEMI[0] == 0x11 || r1.CEMI[0] == 0x29)
		    l3->send_L_Data (c);
		  else
		    delete c;

		}
	      else
		r2.status = 0x29;
	      state[i].rno++;
	      if (state[i].rno > 0xff)
		state[i].rno = 0;
	      sock->sendaddr = state[i].daddr;
	      sock->Send (r2.ToPacket ());
	    }
	  if (p1->service == TUNNEL_RESPONSE && tunnel)
	    {
	      EIBnet_TunnelACK r1;
	      if (parseEIBnet_TunnelACK (*p1, r1))
		goto out;
	      TRACEPRINTF (t, 8, this, "TUNNEL_ACK");
	      for (i = 0; i < state (); i++)
		if (state[i].channel == r1.channel)
		  goto reqf1;
	      goto out;
	    reqf1:
	      if (state[i].sno != r1.seqno)
		{
		  TRACEPRINTF (t, 8, this, "Wrong sequence %d<->%d",
			       r1.seqno, state[i].sno);
		  goto out;
		}
	      if (r1.status != 0)
		{
		  TRACEPRINTF (t, 8, this, "Wrong status %d", r1.status);
		  goto out;
		}
	      if (!state[i].state)
		{
		  TRACEPRINTF (t, 8, this, "Unexpected ACK");
		  goto out;
		}
	      state[i].sno++;
	      if (state[i].sno > 0xff)
		state[i].sno = 0;
	      state[i].state = 0;
	      state[i].out.get ();
	      pth_sem_dec (state[i].outsignal);
	    }
	out:
	  delete p1;
	}
      for (i = 0; i < state (); i++)
	if (pth_event_status (state[i].timeout) == PTH_STATUS_OCCURRED)
	  {
	    pth_event_free (state[i].timeout, PTH_FREE_THIS);
	    pth_event_free (state[i].sendtimeout, PTH_FREE_THIS);
	    pth_event_free (state[i].outwait, PTH_FREE_THIS);
	    delete state[i].outsignal;
	    state.deletepart (i, 1);
	    break;
	  }
      for (i = 0; i < state (); i++)
	{
	  if ((state[i].state
	       && pth_event_status (state[i].sendtimeout) ==
	       PTH_STATUS_OCCURRED) || (!state[i].state
					&& !state[i].out.isempty ()))
	    {
	      TRACEPRINTF (t, 8, this, "TunnelSend %d", state[i].channel);
	      state[i].state++;
	      if (state[i].state > 10)
		{
		  state[i].out.get ();
		  pth_sem_dec (state[i].outsignal);
		  state[i].state = 0;
		  continue;
		}
	      EIBnet_TunnelRequest r;
	      r.channel = state[i].channel;
	      r.seqno = state[i].sno;
	      r.CEMI = state[i].out.top ();
	      pth_event (PTH_EVENT_TIME | PTH_MODE_REUSE,
			 state[i].sendtimeout, pth_timeout (1, 0));
	      sock->sendaddr = state[i].daddr;
	      sock->Send (r.ToPacket ());
	    }

	}
    }
  for (i = 0; i < state (); i++)
    {
      EIBnet_DisconnectRequest r;
      r.channel = state[i].channel;
      if (!GetSourceAddress (&state[i].caddr, &r.caddr))
	continue;
      r.caddr.sin_port = Port;
      sock->sendaddr = state[i].caddr;
      sock->Send (r.ToPacket ());
      pth_event_free (state[i].timeout, PTH_FREE_THIS);
      pth_event_free (state[i].sendtimeout, PTH_FREE_THIS);
      pth_event_free (state[i].outwait, PTH_FREE_THIS);
      delete state[i].outsignal;
    }
  pth_event_free (stop, PTH_FREE_THIS);
}


syntax highlighted by Code2HTML, v. 0.9.1