/*
    PIP - Platform Independent Primitives
    Ethernet, UDP/TCP Broadcast/Multicast
    Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

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

#include "piethernet.h"
#include "piconfig.h"
#include "pisysteminfo.h"
#ifdef QNX
#  include <net/if.h>
#  include <net/if_dl.h>
#  include <hw/nicinfo.h>
#  include <netdb.h>
#  include <sys/socket.h>
#  include <sys/time.h>
#  include <sys/types.h>
#  include <sys/ioctl.h>
#  include <netinet/in.h>
#  include <arpa/inet.h>
#  include <ifaddrs.h>
#  include <fcntl.h>
#  ifdef BLACKBERRY
#    include <netinet/in.h>
#  else
#    include <sys/dcmd_io-net.h>
#  endif
#  define ip_mreqn ip_mreq
#  define imr_address imr_interface
#else
#  ifdef WINDOWS
#    include <io.h>
#    include <winsock2.h>
#    include <iphlpapi.h>
#    include <psapi.h>
#    include <ws2tcpip.h>
#    define ip_mreqn ip_mreq
#    define imr_address imr_interface
#  else
#    include <fcntl.h>
#    include <sys/ioctl.h>
#    include <netinet/in.h>
#    include <arpa/inet.h>
#    include <sys/socket.h>
#    include <net/if.h>
#    include <netdb.h>
#    ifndef ANDROID
#      include <ifaddrs.h>
#    endif
#  endif
#endif
#include <errno.h>


/** \class PIEthernet
 * \brief Ethernet device
 * \details 
 * \section PIEthernet_sec0 Synopsis
 * %PIEthernet designed to work with IPv4 network via two protocols:
 * UDP and TCP. This class allow you send and receive packets to/from
 * another computer through network. Also it supports broadcast and
 * multicast extensions.
 * 
 * \section PIEthernet_sec1 IPv4
 * 
 * 
 * \section PIEthernet_sec2 UDP
 * User Datagram Protocol
 * 
 * \section PIEthernet_sec3 TCP
 * Transmission Control Protocol
 * 
 * */

#ifndef WINDOWS
PIString getSockAddr(sockaddr * s) {
	return s == 0 ? PIString() : PIStringAscii(inet_ntoa(((sockaddr_in*)s)->sin_addr));
}
#endif


REGISTER_DEVICE(PIEthernet)


PRIVATE_DEFINITION_START(PIEthernet)
	sockaddr_in addr_;
	sockaddr_in saddr_;
	sockaddr_in raddr_;
PRIVATE_DEFINITION_END(PIEthernet)


PIEthernet::PIEthernet(): PIIODevice("", ReadWrite) {
	construct();
	setType(UDP);
	setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection);
	//if (type_ != UDP) init();
}


PIEthernet::PIEthernet(PIEthernet::Type type_, const PIString & ip_port, const PIFlags<PIEthernet::Parameters> params_): PIIODevice(ip_port, ReadWrite) {
	construct();
	parseAddress(ip_port, &ip_, &port_);
	setType(type_);
	setParameters(params_);
	if (type_ != UDP) init();
}


PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
	construct();
	parseAddress(ip_port, &ip_s, &port_s);
	sock = sock_;
	init_ = opened_ = connected_ = true;
	setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop);
	setType(TCP_Client, false);
	setPath(ip_port);
	//piCoutObj << "new tcp client" << sock_;
}


PIEthernet::~PIEthernet() {
	//piCoutObj << "~PIEthernet ...";
	piMonitor.ethernets--;
	stop();
	closeDevice();
	//piCoutObj << "~PIEthernet done";
}


void PIEthernet::construct() {
	piMonitor.ethernets++;
	connected_ = connecting_ = listen_threaded = server_bounded = false;
	port_ = port_s = port_r = 0;
	sock = sock_s = -1;
	setReadTimeout(10000.);
	setWriteTimeout(10000.);
	setTTL(64);
	setMulticastTTL(1);
	server_thread_.setData(this);
	server_thread_.setName("__S__server_thread");
	setThreadedReadBufferSize(65536);
	setPriority(piHigh);
}


bool PIEthernet::init() {
	//cout << "init " << type_ << endl;
	if (sock_s == sock)
		sock_s = -1;
	closeSocket(sock);
	closeSocket(sock_s);
	int st = 0, pr = 0;
	if (type() == UDP) {
		st = SOCK_DGRAM;
		pr = IPPROTO_UDP;
	} else {
		st = SOCK_STREAM;
		pr = IPPROTO_TCP;
	}
	PIFlags<Parameters> params = parameters();
	sock = socket(AF_INET, st, pr);
	if (params[SeparateSockets])
		sock_s = socket(AF_INET, st, pr);
	else
		sock_s = sock;
	if (sock == -1 || sock_s == -1) {
		piCoutObj << "Can`t create socket, " << ethErrorString();
		return false;
	}
	if (params[PIEthernet::ReuseAddress]) ethSetsockoptBool(sock, SOL_SOCKET, SO_REUSEADDR);
	if (params[PIEthernet::Broadcast]) ethSetsockoptBool(sock, SOL_SOCKET, SO_BROADCAST);
	//if (type() == PIEthernet::TCP_Client) ethSetsockoptBool(sock, SOL_SOCKET, SO_KEEPALIVE);
	applyTimeouts();
	applyOptInt(IPPROTO_IP, IP_TTL, TTL());
//	piCoutObj << "inited" << path();
	//cout << "inited " << sock << ": bc = " << params << endl;
	//fcntl(sock, F_SETFL, 0/*O_NONBLOCK*/);
	//piCoutObj << "init" << sock;
	return true;
}


void PIEthernet::parseAddress(const PIString & ipp, PIString * ip, int * port) {
	//piCout << "parse" << ipp;
	if (ip != 0) *ip = ipp.left(ipp.find(":"));
	if (port != 0) *port = ipp.right(ipp.length() - ipp.find(":") - 1).toInt();
}


PIString PIEthernet::macFromBytes(const PIByteArray & mac) {
	PIString r;
	for (int i = 0; i < mac.size_s(); ++i) {
		r += PIString::fromNumber(mac[i], 16).expandLeftTo(2, '0');
		if (i < mac.size_s() - 1) r += ":";
	}
	return r;
}


 PIByteArray PIEthernet::macToBytes(const PIString & mac) {
	PIByteArray r;
	PIStringList sl = mac.split(PIStringAscii(":"));
	piForeachC (PIString & i, sl)
		r << uchar(i.toInt(16));
	return r;
}


PIString PIEthernet::applyMask(const PIString & ip, const PIString & mask) {
	struct in_addr ia;
	ia.s_addr = inet_addr(ip.dataAscii()) & inet_addr(mask.dataAscii());
	return PIStringAscii(inet_ntoa(ia));
}


PIString PIEthernet::getBroadcast(const PIString & ip, const PIString & mask) {
	struct in_addr ia;
	ia.s_addr = inet_addr(ip.dataAscii()) | ~inet_addr(mask.dataAscii());
	return PIStringAscii(inet_ntoa(ia));
}


bool PIEthernet::openDevice() {
	if (connected_) return true;
	init();
	if (sock == -1 || path().isEmpty()) return false;
	parseAddress(path(), &ip_, &port_);
	if (type() == TCP_Client)
		connecting_ = true;
	if (type() != UDP || mode() == PIIODevice::WriteOnly)
		return true;
	memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
	PRIVATE->addr_.sin_family = AF_INET;
	PRIVATE->addr_.sin_port = htons(port_);
	PIFlags<Parameters> params = parameters();
	if (params[PIEthernet::Broadcast]) PRIVATE->addr_.sin_addr.s_addr = INADDR_ANY;
	else PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
	//piCout << "bind to" << (params[PIEthernet::Broadcast] ? "255.255.255.255" : ip_) << ":" << port_ << " ...";
	int tries = 0;
	while ((bind(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
		init();
		tries++;
	}
	if (tries == 2) {
		piCoutObj << "Can`t bind to " << ip_ << ":" << port_ << ", " << ethErrorString();
		return false;
	}
	opened_ = true;
	while (!mcast_queue.isEmpty())
		joinMulticastGroup(mcast_queue.dequeue());
	//cout << "!" << endl;
	applyTimeouts();
	applyOptInt(IPPROTO_IP, IP_TTL, TTL());
	port_r = 0;
	ip_r.clear();
	return true;
}


bool PIEthernet::closeDevice() {
	//cout << "close\n";
	if (server_thread_.isRunning()) {
		server_thread_.stop();
		if (!server_thread_.waitForFinish(100))
			server_thread_.terminate();
	}
	if (sock_s == sock)
		sock_s = -1;
	closeSocket(sock);
	closeSocket(sock_s);
	while (!clients_.isEmpty())
		delete clients_.back();
	if (connected_) disconnected(false);
	connected_ = connecting_ = false;
	return true;
}


void PIEthernet::closeSocket(int & sd) {
	if (sd != -1)
		ethClosesocket(sd, type() != PIEthernet::UDP);
	sd = -1;
}


void PIEthernet::applyTimeouts() {
	if (sock < 0) return;
	double rtm = readTimeout(), wtm = writeTimeout();
	applyTimeout(sock, SO_RCVTIMEO, rtm);
	applyTimeout(sock, SO_SNDTIMEO, wtm);
	if (sock_s != sock && sock_s != -1) {
		applyTimeout(sock_s, SO_RCVTIMEO, rtm);
		applyTimeout(sock_s, SO_SNDTIMEO, wtm);
	}
}


void PIEthernet::applyTimeout(int fd, int opt, double ms) {
	if (fd == 0) return;
	//piCoutObj << "setReadIsBlocking" << yes;
#ifdef WINDOWS
	DWORD _tm = ms;
#else
	double s = ms / 1000.;
	timeval _tm;
	_tm.tv_sec = piFloord(s); s -= _tm.tv_sec;
	_tm.tv_usec = s * 1000000.;
#endif
	ethSetsockopt(fd, SOL_SOCKET, opt, &_tm, sizeof(_tm));
}


void PIEthernet::applyOptInt(int level, int opt, int val) {
	if (sock < 0) return;
	ethSetsockoptInt(sock, level, opt, val);
	if (sock_s != sock && sock_s != -1)
		ethSetsockoptInt(sock_s, level, opt, val);
}


void PIEthernet::setParameter(PIEthernet::Parameters parameter, bool on) {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
	cp.setFlag(parameter, on);
	setParameters(cp);
}


bool PIEthernet::joinMulticastGroup(const PIString & group) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (type() != UDP) {
		piCoutObj << "Only UDP sockets can join multicast groups";
		return false;
	}
	if (!opened_) {
		if (mcast_queue.contains(group))
			return false;
		mcast_queue.enqueue(group);
		if (!mcast_groups.contains(group)) mcast_groups << group;
		return true;
	}
	PIFlags<Parameters> params = parameters();
//#ifndef QNX
	//if (!params[Broadcast])
		//;piCoutObj << "Warning: \"Broadcast\" parameter not set, \"joinMulticastGroup(\"" << group << "\")\" may be useless!";
	parseAddress(path(), &ip_, &port_);
	struct ip_mreqn mreq;
	memset(&mreq, 0, sizeof(mreq));
#ifdef LINUX
	//mreq.imr_address.s_addr = INADDR_ANY;
	/*PIEthernet::InterfaceList il = interfaces();
	const PIEthernet::Interface * ci = il.getByAddress(ip_);
	if (ci != 0) mreq.imr_ifindex = ci->index;*/
#endif
	if (params[PIEthernet::Broadcast]) mreq.imr_address.s_addr = INADDR_ANY;
	else mreq.imr_address.s_addr = inet_addr(ip_.dataAscii());
/*#ifndef WINDOWS
	PIEthernet::InterfaceList il = interfaces();
	const PIEthernet::Interface * ci = il.getByAddress(ip_);
	if (ci != 0) mreq.imr_ifindex = ci->index;
#endif*/
	//piCout << "join group" << group << "ip" << ip_ << "with index" << mreq.imr_ifindex << "socket" << sock;
	mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
	if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
		piCoutObj << "Can`t join multicast group " << group << ", " << ethErrorString();
		return false;
	}
	if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
	applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
	if (!mcast_groups.contains(group)) mcast_groups << group;
//#else
//	parseAddress(path(), &ip_, &port_);
//	struct ip_mreq mreq;
//	memset(&mreq, 0, sizeof(mreq));
//	mreq.imr_interface.s_addr = inet_addr(ip_.dataAscii());
//	mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
//	if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
//		piCoutObj << "Can`t join multicast group " << group << ", " << ethErrorString();
//		return false;
//	}
//	if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
//	applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
//	if (!mcast_groups.contains(group)) mcast_groups << group;
//#endif
	return true;
}


bool PIEthernet::leaveMulticastGroup(const PIString & group) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (type() != UDP) {
		piCoutObj << "Only UDP sockets can leave multicast groups";
		return false;
	}
	PIFlags<Parameters> params = parameters();
	/// TODO windows
	parseAddress(path(), &ip_, &port_);
	struct ip_mreqn mreq;
	memset(&mreq, 0, sizeof(mreq));
	if (params[PIEthernet::Broadcast]) mreq.imr_address.s_addr = INADDR_ANY;
	else mreq.imr_address.s_addr = inet_addr(ip_.dataAscii());
	mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
	if (ethSetsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
		piCoutObj << "Can`t leave multicast group " << group << ", " << ethErrorString();
		return false;
	}
	mcast_groups.removeAll(group);
	return true;
}


bool PIEthernet::connect() {
	connecting_ = true;
	return true;
	/*if (sock == -1) return false;
	memset(addr_, 0, sizeof(*addr_));
	parseAddress(path_, &ip_, &port_);
	PRIVATE->addr_.sin_port = htons(port_);
	PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.data());
	PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(*addr_);
#endif
	//piCoutObj << "connect to " << ip << ":" << port_;
	connected_ = (::connect(sock, (sockaddr * )addr_, sizeof(*addr_)) == 0);
	if (!connected_)
		piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
	opened_ = connected_;
	if (connected_) connected();
	return connected_;*/
}


bool PIEthernet::listen(bool threaded) {
	if (sock == -1) init();
	if (sock == -1) return false;
	if (threaded) {
		if (server_thread_.isRunning()) {
			if (!server_bounded) return true;
			server_thread_.stop();
			if (!server_thread_.waitForFinish(100))
				server_thread_.terminate();
		}
		listen_threaded = true;
		server_bounded = false;
		server_thread_.start(server_func);
		return true;
	}
	listen_threaded = server_bounded = false;
	parseAddress(path(), &ip_, &port_);
	memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
	PRIVATE->addr_.sin_port = htons(port_);
	PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
	PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
	PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
	opened_ = false;
	int tries = 0;
	while ((bind(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
		init();
		tries++;
	}
	if (tries == 2) {
		piCoutObj << "Can`t bind to " << ip_ << ":" << port_ << ", " << ethErrorString();
		return false;
	}
	if (::listen(sock, 64) == -1) {
		piCoutObj << "Can`t listen on "<< ip_ << ":" << port_ << ", " << ethErrorString();
		return false;
	}
	opened_ = server_bounded = true;
	//piCoutObj << "listen on " << ip_ << ":" << port_;
	server_thread_.start(server_func);
	return true;
}

//#include <QDebug>
int PIEthernet::read(void * read_to, int max_size) {
	//piCout << "read" << sock;
	if (sock == -1) init();
	if (sock == -1 || read_to == 0) return -1;
	int rs = 0, s = 0, lerr = 0;
	sockaddr_in client_addr;
	socklen_t slen = sizeof(client_addr);
//	piCoutObj << "read from " << ip_ << ":" << port_;
	switch (type()) {
	case TCP_SingleTCP:
		::listen(sock, 64);
		s = accept(sock, (sockaddr * )&client_addr, &slen);
		if (s == -1) {
			//piCoutObj << "Can`t accept new connection, " << ethErrorString();
			msleep(1);
			return -1;
		}
		rs = ethRecv(s, read_to, max_size);
		closeSocket(s);
		return rs;
	case TCP_Client:
		if (connecting_) {
#ifdef ANDROID
			/*if (sock_s == sock)
				sock_s = -1;
			closeSocket(sock);
			closeSocket(sock_s);
			init();
			qDebug() << "init() in read thread";*/
#endif
			memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
			parseAddress(path(), &ip_, &port_);
			PRIVATE->addr_.sin_port = htons(port_);
			PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
			PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
			PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
			//piCoutObj << "connect to " << ip_ << ":" << port_ << "...";
			//qDebug() << "connect to " << ip_.data() << ":" << port_ << sock << PRIVATE->addr_.sin_addr.s_addr << "...";
			connected_ = (::connect(sock, (sockaddr * )&(PRIVATE->addr_), sizeof(PRIVATE->addr_)) == 0);
			//piCoutObj << "connect to " << ip_ << ":" << port_ << connected_;
			//qDebug() << "connect to " << ip_.data() << ":" << port_ << connected_;
			if (!connected_)
				piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
			opened_ = connected_;
			if (connected_) {
				connecting_ = false;
				connected();
			} else
				piMSleep(10);
			//piCout << "connected to" << path();
			//qDebug() << "connected to" << path().data();
		}
		if (!connected_) return -1;
		errorClear();
		rs = ethRecv(sock, read_to, max_size);
		//piCoutObj << "readed" << rs;
		//qDebug() << "readed" << rs;
		if (rs <= 0) {
			lerr = ethErrorCore();
			//piCoutObj << "readed error" << lerr << errorString().data() << parameters()[DisonnectOnTimeout];
#ifdef WINDOWS
			if ((lerr == WSAEWOULDBLOCK || lerr == WSAETIMEDOUT) && !parameters()[DisonnectOnTimeout]) {
#else
			if ((lerr == EWOULDBLOCK || lerr == EAGAIN) && !parameters()[DisonnectOnTimeout]) {
#endif
			//piCoutObj << errorString();
				piMSleep(10);
				return -1;
			}
			if (connected_) {
				init();
				connected_ = false;
				disconnected(rs < 0);
			}
			if (parameters()[KeepConnection])
				connect();
			//piCoutObj << "eth" << ip_ << "disconnected";
		}
		if (rs > 0) received(read_to, rs);
		//qDebug() << "return from read" << rs;
		return rs;
	case UDP:
		memset(&PRIVATE->raddr_, 0, sizeof(PRIVATE->raddr_));
		rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr*)&PRIVATE->raddr_);
		if (rs > 0) {
			port_r = ntohs(PRIVATE->raddr_.sin_port);
			ip_r = PIStringAscii(inet_ntoa(PRIVATE->raddr_.sin_addr));
			//piCoutObj << "read from" << ip_r << ":" << port_r << rs << "bytes";
			//piCout << "received from" << lastReadAddress();
			received(read_to, rs);
		}
		//else piCoutObj << "read returt" << rs << ", error" << ethErrorString();
		return rs;
		//return ::read(sock, read_to, max_size);
	default: break;
		//return ::read(sock, (char * )read_to, max_size);
	}
	return -1;
}


int PIEthernet::write(const void * data, int max_size) {
	if (sock == -1) init();
	if (sock == -1 || !isWriteable()) {
		//piCoutObj << "Can`t send to uninitialized socket";
		return -1;
	}
	//piCoutObj << "sending to " << ip_s << ":" << port_s << "  " << max_size << " bytes";
	int ret = 0;
	switch (type()) {
	case TCP_SingleTCP:
		memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
		PRIVATE->addr_.sin_port = htons(port_s);
		PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_s.dataAscii());
		PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
		PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
		//piCoutObj << "connect SingleTCP" << ip_s << ":" << port_s << "...";
		if (::connect(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) != 0) {
			//piCoutObj << "Can`t connect to " << ip_s << ":" << port_s << ", " << ethErrorString();
			msleep(1);
			return -1;
		}
		//piCoutObj << "ok, write SingleTCP" << int(data) << max_size << "bytes ...";
		ret = ::send(sock, (const char *)data, max_size, 0);
		//piCoutObj << "ok, ret" << ret;
		closeSocket(sock);
		init();
		return ret;
	case UDP:
		PRIVATE->saddr_.sin_port = htons(port_s);
		/*if (params[PIEthernet::Broadcast]) PRIVATE->saddr_.sin_addr.s_addr = INADDR_BROADCAST;
		else*/ PRIVATE->saddr_.sin_addr.s_addr = inet_addr(ip_s.dataAscii());
		PRIVATE->saddr_.sin_family = AF_INET;
		//piCoutObj << "write to" << ip_s << ":" << port_s << "socket" << sock_s << max_size << "bytes ...";
		return ethSendto(sock_s, data, max_size, 0, (sockaddr * )&PRIVATE->saddr_, sizeof(PRIVATE->saddr_));
		//piCout << "[PIEth] write to" << ip_s << ":" << port_s << "ok";
	case TCP_Client:
		if (connecting_) {
			memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
			parseAddress(path(), &ip_, &port_);
			PRIVATE->addr_.sin_port = htons(port_);
			PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
			PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
			PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
			//piCoutObj << "connect to " << ip << ":" << port_;
			connected_ = (::connect(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == 0);
			if (!connected_)
				piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
			opened_ = connected_;
			if (connected_) {
				connecting_ = false;
				connected();
			}
		}
		if (!connected_) return -1;
		ret = ::send(sock, (const char *)data, max_size, 0);
		if (ret < 0) {
			connected_ = false; {
				init();
				disconnected(true);
			}
		}
		return ret;
	default: break;
		//return ::read(sock, read_to, max_size);
	}
	return -1;
}


void PIEthernet::clientDeleted() {
	clients_mutex.lock();
	clients_.removeOne((PIEthernet*)emitter());
	clients_mutex.unlock();
}


void PIEthernet::server_func(void * eth) {
	PIEthernet * ce = (PIEthernet * )eth;
	if (ce->listen_threaded) {
		if (!ce->server_bounded) {
			if (!ce->listen(false)) {
				ce->listen_threaded = true;
				piMSleep(100);
				return;
			}
		}
	}
	sockaddr_in client_addr;
	socklen_t slen = sizeof(client_addr);
	int s = accept(ce->sock, (sockaddr * )&client_addr, &slen);
	if (s == -1) {
		int lerr = ethErrorCore();
#ifdef WINDOWS
		if (lerr == WSAETIMEDOUT) {
#else
		if (lerr == EAGAIN) {
#endif
			piMSleep(10);
			return;
		}
		if (ce->debug()) piCout << "[PIEthernet] Can`t accept new connection, " << ethErrorString();
		piMSleep(10);
		return;
	}
	PIString ip = PIStringAscii(inet_ntoa(client_addr.sin_addr));
	ip += ":" + PIString::fromNumber(htons(client_addr.sin_port));
	PIEthernet * e = new PIEthernet(s, ip);
	ce->clients_mutex.lock();
	CONNECTU(e, deleted, ce, clientDeleted)
	ce->clients_ << e;
	ce->clients_mutex.unlock();
	ce->newConnection(e);
	//cout << "connected " << ip << endl;
	//char d[256];
	//cout << " recv " << recv(s, d, 256, 0) << endl;
	//cout << recv(ce->clients_.back()->sock, d, 256, 0) << endl;

}


bool PIEthernet::configureDevice(const void * e_main, const void * e_parent) {
	PIConfig::Entry * em = (PIConfig::Entry * )e_main;
	PIConfig::Entry * ep = (PIConfig::Entry * )e_parent;
	setReadIP(readDeviceSetting<PIString>("ip", readIP(), em, ep));
	setReadPort(readDeviceSetting<int>("port", readPort(), em, ep));
	setParameter(PIEthernet::Broadcast, readDeviceSetting<bool>("broadcast", isParameterSet(PIEthernet::Broadcast), em, ep));
	setParameter(PIEthernet::ReuseAddress, readDeviceSetting<bool>("reuseAddress", isParameterSet(PIEthernet::ReuseAddress), em, ep));
	return true;
}


void PIEthernet::propertyChanged(const PIString & name) {
	if (name.endsWith("Timeout")) applyTimeouts();
	if (name == "TTL") applyOptInt(IPPROTO_IP, IP_TTL, TTL());
	if (name == "MulticastTTL") applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
}


PIString PIEthernet::constructFullPath() const {
	PIString ret(fullPathPrefix() + "://");
	ret << (type() == PIEthernet::UDP ? "UDP" : "TCP") << ":" << readIP() << ":" << readPort();
	if (type() == PIEthernet::UDP) {
		ret << ":" << sendIP() << ":" << sendPort();
		piForeachC (PIString & m, multicastGroups())
			ret << ":mcast:" << m;
	}
	return ret;
}


void PIEthernet::configureFromFullPath(const PIString & full_path) {
	PIStringList pl = full_path.split(":");
	bool mcast = false;
	for (int i = 0; i < pl.size_s(); ++i) {
		PIString p(pl[i]);
		switch (i) {
		case 0:
			p = p.toLowerCase();
			if (p == "udp") setType(UDP);
			if (p == "tcp") setType(TCP_Client);
			break;
		case 1: setReadIP(p); setSendIP(p); break;
		case 2: setReadPort(p.toInt()); setSendPort(p.toInt()); break;
		case 3: setSendIP(p); break;
		case 4: setSendPort(p.toInt()); break;
		}
		if (i <= 4) continue;
		if (i % 2 == 1) {if (p.toLowerCase() == "mcast") mcast = true;}
		else {if (mcast) {joinMulticastGroup(p); mcast = false;}}
	}
}


PIEthernet::InterfaceList PIEthernet::interfaces() {
	PIEthernet::InterfaceList il;
	Interface ci;
	ci.index = -1;
	ci.mtu = 1500;
#ifdef WINDOWS
	PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0;
	int ret = 0;
	ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
	pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO)));
	if (pAdapterInfo == 0) {
		piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
		return il;
	}
	if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
		HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
		pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen));
		if (pAdapterInfo == 0) {
			piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
			return il;
		}
	}
	if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
		pAdapter = pAdapterInfo;
		while (pAdapter) {
			ci.name = PIString(pAdapter->AdapterName);
			ci.index = pAdapter->Index;
			ci.mac = macFromBytes(PIByteArray(pAdapter->Address, pAdapter->AddressLength));
			ci.flags = PIEthernet::ifActive | PIEthernet::ifRunning;
			//if (ret->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
			//if (ret->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
			if (pAdapter->Type == MIB_IF_TYPE_PPP) ci.flags |= PIEthernet::ifPTP;
			if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
			ci.broadcast.clear();
			ci.ptp.clear();
			IP_ADDR_STRING * as = &(pAdapter->IpAddressList);
			while (as) {
//				piCout << "[pAdapter]" << ci.name << PIString(as->IpAddress.String);
				ci.address = PIString(as->IpAddress.String);
				ci.netmask = PIString(as->IpMask.String);
				if (ci.address == "0.0.0.0") {
					as = as->Next;
					continue;
				}
				il << ci;
				as = as->Next;
			}
			/*if (ci.flags[PIEthernet::ifBroadcast])
				ci.broadcast = getSockAddr(ret->ifa_broadaddr);
			if (ci.flags[PIEthernet::ifPTP])
				ci.ptp = getSockAddr(ret->ifa_dstaddr);*/
			pAdapter = pAdapter->Next;
		}
	} else
		piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret;
	if (pAdapterInfo)
		HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
#else
/*#  ifdef QNX
	PIStringList il, sl;
	PIProcess proc;
	proc.setGrabOutput(true);
	proc.exec(ifconfigPath.c_str(), "-l");
	if (!proc.waitForFinish(1000)) return sl;
	PIString out(proc.readOutput());
	il = out.split(" ");
	il.removeAll("");
	piForeachC (PIString & i, il) {
		proc.exec(ifconfigPath.c_str(), i);
		if (!proc.waitForFinish(1000)) return il;
		sl << i.trimmed();
		out = proc.readOutput();
		int al = out.length();
		al = (al - out.replaceAll("alias", "").length()) / 5;
		for (int j = 0; j < al; ++j)
			sl << i.trimmed() + ":" + PIString::fromNumber(j);
	}
	return sl;
#  else
	PIStringList sl;
	PIProcess proc;
	proc.setGrabOutput(true);
	proc.exec(ifconfigPath.c_str(), "-s");
	if (!proc.waitForFinish(1000)) return sl;
	PIString out(proc.readOutput());
	out.cutLeft(out.find('\n') + 1);
	while (!out.isEmpty()) {
		sl << out.left(out.find(' '));
		out.cutLeft(out.find('\n') + 1);
	}
	return sl;
#  endif*/
#  ifdef ANDROID
	struct ifconf ifc;
	int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	ifc.ifc_len = 256;
	ifc.ifc_buf = new char[ifc.ifc_len];
	if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
		piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
		delete[] ifc.ifc_buf;
		return il;
	}
	int icnt = ifc.ifc_len / sizeof(ifreq);
	PIStringList inl;
	struct ifreq ir;
	for (int i = 0; i < icnt; ++i) {
		PIString in = PIStringAscii(ifc.ifc_req[i].ifr_name);
		if (in.isEmpty()) continue;
		ci.name = in;
		strcpy(ir.ifr_name, in.dataAscii());
		if (ioctl(s, SIOCGIFHWADDR, &ir) == 0)
		ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
		if (ioctl(s, SIOCGIFADDR, &ir) >= 0)
			ci.address = getSockAddr(&ir.ifr_addr);
		if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0)
			ci.netmask = getSockAddr(&ir.ifr_addr);
		ioctl(s, SIOCGIFMTU, &ci.mtu);
		il << ci;
	}
	delete ifc.ifc_buf;
#  else
	struct ifaddrs * ret;
	int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (getifaddrs(&ret) == 0) {
		while (ret != 0) {
			if (ret->ifa_addr == 0) {
				ret = ret->ifa_next;
				continue;
			}
			if (ret->ifa_addr->sa_family != AF_INET) {
				ret = ret->ifa_next;
				continue;
			}
			ci.name = PIString(ret->ifa_name);
			ci.address = getSockAddr(ret->ifa_addr);
			ci.netmask = getSockAddr(ret->ifa_netmask);
			ci.mac.clear();
#    ifdef QNX
#      ifndef BLACKBERRY
			int fd = ::open((PIString("/dev/io-net/") + ci.name).dataAscii(), O_RDONLY);
			if (fd != 0) {
				nic_config_t nic;
				devctl(fd, DCMD_IO_NET_GET_CONFIG, &nic, sizeof(nic), 0);
				::close(fd);
				ci.mac = macFromBytes(PIByteArray(nic.permanent_address, 6));
			}
#      endif
#    else
#      ifdef MAC_OS
			PIString req = PISystemInfo::instance()->ifconfigPath + " " + ci.name + " | grep ether";
			FILE * fp = popen(req.dataAscii(), "r");
			if (fp != 0) {
				char in[256];
				if (fgets(in, 256, fp) != 0) {
					req = PIString(in).trim();
					ci.mac = req.cutLeft(req.find(" ") + 1).trim().toUpperCase();
				}
				pclose(fp);
			}
#      else
			if (s != -1) {
				struct ifreq ir;
				strcpy(ir.ifr_name, ret->ifa_name);
				if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) {
					ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
					ci.mtu = ir.ifr_mtu;
				}
			}
#      endif
#    endif
			ci.flags = 0;
			if (ret->ifa_flags & IFF_UP) ci.flags |= PIEthernet::ifActive;
			if (ret->ifa_flags & IFF_RUNNING) ci.flags |= PIEthernet::ifRunning;
			if (ret->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
			if (ret->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
			if (ret->ifa_flags & IFF_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
			if (ret->ifa_flags & IFF_POINTOPOINT) ci.flags |= PIEthernet::ifPTP;
			ci.broadcast.clear();
			ci.ptp.clear();
			if (ci.flags[PIEthernet::ifBroadcast])
				ci.broadcast = getSockAddr(ret->ifa_broadaddr);
			if (ci.flags[PIEthernet::ifPTP])
				ci.ptp = getSockAddr(ret->ifa_dstaddr);
			ci.index = if_nametoindex(ret->ifa_name);
			il << ci;
			ret = ret->ifa_next;
		}
		freeifaddrs(ret);
	} else
		piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
	if (s != -1) ::close(s);
#  endif
#endif
	return il;
}


PIString PIEthernet::interfaceAddress(const PIString & interface_) {
#ifdef WINDOWS
	piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead";
	return PIString();
#else
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, interface_.dataAscii());
	int s = socket(AF_INET, SOCK_DGRAM, 0);
	ioctl(s, SIOCGIFADDR, &ifr);
	::close(s);
	struct sockaddr_in * sa = (struct sockaddr_in * )&ifr.ifr_addr;
	return PIStringAscii(inet_ntoa(sa->sin_addr));
#endif

}


PIStringList PIEthernet::allAddresses() {
/*#ifdef WINDOWS
	PIStringList al;
	PIString ca;
	PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0;
	int ret = 0;
	ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
	pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO)));
	if (pAdapterInfo == 0) {
		piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
		return PIStringList();
	}
	if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
		HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
		pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen));
		if (pAdapterInfo == 0) {
			piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
			return PIStringList();
		}
	}
	if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
		pAdapter = pAdapterInfo;
		while (pAdapter) {
			ca = PIString(pAdapter->IpAddressList.IpAddress.String);
			if (ca != "0.0.0.0") al << ca;
			pAdapter = pAdapter->Next;
		}
	} else
		piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret;
	if (pAdapterInfo)
		HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
	return al;
#else*/
	PIEthernet::InterfaceList il = interfaces();
	PIStringList al;
	piForeachC (PIEthernet::Interface & i, il)
		al << i.address;
//	piCout << "[PIEthernet::allAddresses]" << al;
	if (!al.contains("127.0.0.1")) al << "127.0.0.1";
	return al.removeStrings("0.0.0.0");
	//#endif
}


PIString PIEthernet::resolve(const PIString & host) {
	PIString ip(host), port;
	int i = host.find(':');
	if (i >= 0) {
		ip = host.left(i);
		port = host.right(host.length() - i - 1);
	}
	PIString ret = PIStringAscii("0.0.0.0");
	if (!port.isEmpty()) ret += PIStringAscii(":") + port;
//#ifdef WINDOWS
	hostent * he = gethostbyname(ip.dataAscii());
	if (!he)
		return ret;
	struct in_addr addr;
	if (he->h_addr_list[0]) {
		addr.s_addr = *((uint*)(he->h_addr_list[0]));
		ret = inet_ntoa(addr);
		if (!port.isEmpty()) ret += PIStringAscii(":") + port;
	}
//#else
//#endif
	return ret;
}


// System wrap


int PIEthernet::ethErrorCore() {
#ifdef WINDOWS
	return WSAGetLastError();
#else
	return errno;
#endif
}


PIString PIEthernet::ethErrorString() {
#ifdef WINDOWS
	char * msg;
	int err = WSAGetLastError();
	FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
	return "code " + PIString::fromNumber(err) + " - " + PIString(msg);
#else
	return errorString();
#endif
}


int PIEthernet::ethRecv(int sock, void * buf, int size, int flags) {
	if (sock < 0) return -1;
	return recv(sock,
#ifdef WINDOWS
					 (char*)
#endif
							buf, size, flags);
}


int PIEthernet::ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr) {
	if (sock < 0) return -1;
#ifdef QNX
	return recv(sock, buf, size, flags);
#else
	socklen_t len = sizeof(sockaddr);
	return recvfrom(sock,
#  ifdef WINDOWS
					 (char*)
#  endif
							buf, size, flags, addr, &len);
#endif
}


int PIEthernet::ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len) {
	if (sock < 0) return -1;
	return sendto(sock,
#ifdef WINDOWS
					   (const char*)
#endif
									buf, size, flags, addr, addr_len);
}


void PIEthernet::ethClosesocket(int sock, bool shutdown) {
	//piCout << "close socket" << sock << shutdown;
	if (sock < 0) return;
		if (shutdown) ::shutdown(sock,
#ifdef WINDOWS
					SD_BOTH);
		closesocket(sock);
#else
					SHUT_RDWR);
		::close(sock);
#endif
}


int PIEthernet::ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen) {
	if (sock < 0) return -1;
	return setsockopt(sock, level, optname,
#ifdef WINDOWS
											(char*)
#endif
													optval, optlen);
}


int PIEthernet::ethSetsockoptInt(int sock, int level, int optname, int value) {
	if (sock < 0) return -1;
#ifdef WINDOWS
	DWORD
#else
	int
#endif
			so = value;
	return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}


int PIEthernet::ethSetsockoptBool(int sock, int level, int optname, bool value) {
	if (sock < 0) return -1;
#ifdef WINDOWS
	BOOL
#else
	int
#endif
			so = (value ? 1 : 0);
	return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}
