/*
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